From 7ec4753c203855c29ad24a67b21deea7d322c755 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Mon, 4 Sep 2023 23:32:34 +0200 Subject: [PATCH 01/13] docs: create ofetch 101 article --- .../1.learn/1.ofetch-101-first-hand.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 content/4.resources/1.learn/1.ofetch-101-first-hand.md diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md new file mode 100644 index 00000000..28114c83 --- /dev/null +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -0,0 +1,49 @@ +--- +title: ofetch 101 - First hand +description: Discover ofetch, a better API for fetch that works on node, browser and workers. +authors: + - name: Barbapapazes + picture: https://esteban-soubiran.site/esteban.webp + twitter: soubiran_ +packages: + - ofetch +publishedAt: 2023-09-04 +modifiedAt: 2023-09-04 +layout: learn-post +--- + +## Introduction + + + + + +## Installation + + + +## First request + + + +## Type safety + + + +## Options + +### Method + +### Body + +### Query string + +### Retry + +### Error handling + + + +## Conclusion + + From 372e96ca75edf377398e3db52f42620b65bc7023 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Tue, 5 Sep 2023 18:32:27 +0200 Subject: [PATCH 02/13] feat: ofetch article --- .../1.learn/1.ofetch-101-first-hand.md | 380 +++++++++++++++++- 1 file changed, 369 insertions(+), 11 deletions(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 28114c83..00c34564 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -1,12 +1,12 @@ --- -title: ofetch 101 - First hand +title: ofetch 101 - first hand description: Discover ofetch, a better API for fetch that works on node, browser and workers. authors: - name: Barbapapazes picture: https://esteban-soubiran.site/esteban.webp twitter: soubiran_ packages: - - ofetch + - ofetch # TODO: Add links to the package page (we could add metada) publishedAt: 2023-09-04 modifiedAt: 2023-09-04 layout: learn-post @@ -14,36 +14,394 @@ layout: learn-post ## Introduction - +`ofetch` is a utility package to make HTTP requests. It exists to unify the API between node, browser and workers. - +In fact, Node.js has it's [own fetch](https://undici.nodejs.org/#/) since version 17.5.0 under an experimental flag and [full support at version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility). If we want to support order version, we will need to use a third party package. + +In the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. + +`ofetch` have been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. + + + + ## Installation - +First, let's create a new project: + +```bash +mkdir ofetch-101 +cd ofetch-101 +npm init -y +``` + +Then, install the package: + +```bash +npm install ofetch +``` + +To easily run TypeScript scripts, we can install [jiti](/packages/jiti) as a dev dependency: + +```bash +npm install -D jiti +``` + + ## First request - +In order to make our first request, let's create a file named `first-request.ts` with the following content: + +```ts [first-request.ts] +import { $fetch } from 'ofetch' + +async function main() {} + +main().catch(console.error) // Useful to catch errors +``` + +Then, we can run the script with: + +```bash +jiti first-request.ts +``` + +And _voilĂ _, we should see something like this: + +```js +{ + repo: { + id: 319960543, + name: 'ofetch', + repo: 'unjs/ofetch', + description: 'đŸ˜± A better fetch API. Works on node, browser and workers.', + createdAt: '2020-12-09T13:11:38Z', + updatedAt: '2023-09-04T17:23:36Z', + pushedAt: '2023-09-04T21:05:19Z', + stars: 2474, + watchers: 14, + forks: 80, + defaultBranch: 'main' + } +} +``` + +We have made our first request with ofetch! :tada: Easy, right? + +### Manual parsing + + ## Type safety - +Thanks to TypeScript and generics, ofetch can be type safe. Let's see how to do it. + +First, let's create another file called `type-safety.ts`. + +```bash +touch type-safety.ts +``` + +Then, let's create an interface for the response. We will use the same request as in our first request. + +```ts [type-safety.ts] +interface Repo { + id: number + name: string + repo: string + description: string + stars: number +} +``` + +Now, we can use the `$fetch` function, like in our `first-request.ts` file, but this time, we will add a generic type to it. + +```ts [type-safety.ts] +import { $fetch } from 'ofetch' + +interface Repo { + id: number + name: string + repo: string + description: string + stars: number +} + +async function main() { + const { repo } = await $fetch<{ repo: Repo }>('https://ungh.cc/api/github/unjs/ofetch') + + console.log(`The repo ${repo.name} has ${repo.stars} stars.`) // The repo object is now strongly typed. +} + +main().catch(console.error) +``` + +Nothing more to have a full typed repo object! :ok_hand: + +::alert{type="warning"} +This is **not a validation**. It's just a way to tell TypeScript what the response should look like. If we want full validation, we can use a tool like [zod](https://zod.dev) or [valibot](https://valibot.dev). +:: ## Options -### Method +With `ofetch`, we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. + +### Methods + +`ofetch` supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.ts` with the following content: + +```ts [methods.ts] +import { $fetch } from 'ofetch' + +async function main() { + const response = await $fetch('https://api.github.com/gists', { + method: 'POST', + }) // Be careful, we use the GitHub API. + + console.log(response) +} + +main().catch(console.error) +``` + +Of course, we receive an `401 Unauthorized` error, because we didn't provide any authentication. But this error means we have successfully made a `POST` request. + +Supported methods are: + +- `GET` +- `POST` +- `PUT` +- `PATCH` +- `DELETE` +- `HEAD` +- `OPTIONS` ### Body +Of course, when the method is not `GET` or `HEAD`, we can provide a body to the request. Let's see how to do it by creating a new file named `body.ts` with the following content: + +```ts [body.ts] +import { $fetch } from 'ofetch' + +async function main() { + const response = await $fetch('https://api.github.com/markdown', { + method: 'POST', + // To provide a body, we need to use the `body` option and just use an object. + body: { + text: 'UnJS is **awesome**!\n\nCheck out their [website](https://unjs.io).', + }, + }) // Be careful, we use the GitHub API. + + console.log(response) +} + +main().catch(console.error) +``` + +And we receive: + +```html +

UnJS is awesome!

+

Check out their website.

+``` + +Perfect and easy! + +The body can take several formats: + +- `string` +- `FormData` +- `URLSearchParams` +- `Blob` +- `BufferSource` +- `ReadableStream` +- `Record` + ### Query string -### Retry +We can also provide some query string parameters to the request. They can be useful to add filters or to paginate the results. + +Imagine we want to get the last 2 tags of the `unjs/ofetch` repository. We can do it by creating a new file named `query-string.ts` with the following content: + +```ts [query-string.ts] +import { $fetch } from 'ofetch' + +async function main() { + const response = await $fetch('https://api.github.com/repos/unjs/ofetch/tags', { + query: { + per_page: 2, + }, + }) // Be careful, we use the GitHub API directly. + + console.log(response) +} + +main().catch(console.error) +``` + +We receive this response (results could be different, because the tags are updated): + +```js +[ + { + name: 'v1.3.3', + zipball_url: 'https://api.github.com/repos/unjs/ofetch/zipball/refs/tags/v1.3.3', + tarball_url: 'https://api.github.com/repos/unjs/ofetch/tarball/refs/tags/v1.3.3', + commit: { + sha: '051ef83ceba6cc496ac487969c9f6611ef6bd10d', + url: 'https://api.github.com/repos/unjs/ofetch/commits/051ef83ceba6cc496ac487969c9f6611ef6bd10d' + }, + node_id: 'MDM6UmVmMzE5OTYwNTQzOnJlZnMvdGFncy92MS4zLjM=' + }, + { + name: 'v1.3.2', + zipball_url: 'https://api.github.com/repos/unjs/ofetch/zipball/refs/tags/v1.3.2', + tarball_url: 'https://api.github.com/repos/unjs/ofetch/tarball/refs/tags/v1.3.2', + commit: { + sha: '1258d51494656c9507a8bb82646c644c64f23e69', + url: 'https://api.github.com/repos/unjs/ofetch/commits/1258d51494656c9507a8bb82646c644c64f23e69' + }, + node_id: 'MDM6UmVmMzE5OTYwNTQzOnJlZnMvdGFncy92MS4zLjI=' + } +] +``` + +A query string is an object of type `{ [key: string]: any }` so it's easy to use. + +::alert{type="info"} +We can find a key `params` in the options. It's an alias for `query`. +:: + +### Headers + +In the [`methods` section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in an header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.ts` with the following content: + +```ts [headers.ts] +import { $fetch } from 'ofetch' + +async function main() { + const response = await $fetch('https://api.github.com/gists', { + method: 'POST', + headers: { + Authorization: `token ${process.env.GH_TOKEN}`, + } + }) // Be careful, we use the GitHub API directly. + + console.log(response) +} + +main().catch(console.error) +``` + +::alert{type="info"} +We can generate a new GitHub Token with the [GitHub CLI](https://cli.github.com): `gh auth token`. +:: + +We can run the script with: + +```bash +GH_TOKEN= jiti headers.ts +``` + +And we get the following error: + +```bash +FetchError: [POST] "https://api.github.com/gists": 422 Unprocessable Entity +``` + +And that's a good thing! It means we have successfully authenticated to GitHub. We just need to provide a valid body to the request but now, we know how to do it! We have to add the `body` option to the request with correct data: + +```ts [headers.ts] +import { $fetch } from 'ofetch' + +async function main() { + const response = await $fetch('https://api.github.com/gists', { + method: 'POST', + headers: { + Authorization: `token ${process.env.GH_TOKEN}`, + }, + body: { + description: 'This is a gist created by ofetch.', + public: true, + files: { + 'unjs.txt': { + content: 'UnJS is awesome!', + } + } + } + }) // Be careful, we use the GitHub API directly. + + console.log(response.url) +} + +main().catch(console.error) +``` + +Run the script again: + +```bash +GH_TOKEN= jiti headers.ts +``` + +And as a result, we get the URL of the created gist ! :raised_hands: + +::alert{type="danger"} +Never put credential token directly in code. Use an environment variable instead. +:: ### Error handling - +This part is straight forward with `ofetch`. Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: + +```ts [error-handling.ts] +import { $fetch } from 'ofetch' + +async function main() { + try { + await $fetch('https://api.github.com', { + method: 'POST' // This allow us to get an error. + }) + } + catch (error) { + console.error(error) + } +} + +main().catch(console.error) +``` + +With that, we will only see a `FetchError` in the console. We can do better. In fact, we can retrieve the returned error body using `error.data`. Let's update a little our script: + +```ts [error-handling.ts] +// ... + +async function main() { + try { + // ... + } + catch (error) { + console.log(error.data) // This allow us to get the error body. + } +} + +main().catch(console.error) +``` + +Then, run the script and we get the following error we can easily use to display a message to the user: + +```js +{ + message: 'Not Found', + documentation_url: 'https://docs.github.com/rest/reference/repos#create-a-repository-for-the-authenticated-user' +} +``` + +We can also completely disable error response using option `ignoreResponseError`. ## Conclusion - +We have seen how to use `ofetch` to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. + +With this 101, we've just scratch the surface of `ofetch`. In next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates and how to create a fetch with some default options! From cf9635f8c5788cf6d0679b789556e91e610238ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9ban?= Date: Fri, 8 Sep 2023 14:36:50 +0200 Subject: [PATCH 03/13] Update content/4.resources/1.learn/1.ofetch-101-first-hand.md Co-authored-by: Pooya Parsa --- content/4.resources/1.learn/1.ofetch-101-first-hand.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 00c34564..8ac36a0b 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -1,6 +1,6 @@ --- title: ofetch 101 - first hand -description: Discover ofetch, a better API for fetch that works on node, browser and workers. +description: Discover ofetch, a better API for fetch that works on Node.js, browser, and workers. authors: - name: Barbapapazes picture: https://esteban-soubiran.site/esteban.webp From ec03e156daaf48abf59c201b7601ded6707dc83c Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 8 Sep 2023 15:20:57 +0200 Subject: [PATCH 04/13] content: update many things and resolve comments --- .../1.learn/1.ofetch-101-first-hand.md | 133 ++++++++++++------ 1 file changed, 89 insertions(+), 44 deletions(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 8ac36a0b..49bc2559 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -12,15 +12,13 @@ modifiedAt: 2023-09-04 layout: learn-post --- -## Introduction +[`ofetch`](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between node, browser and workers and is build on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). -`ofetch` is a utility package to make HTTP requests. It exists to unify the API between node, browser and workers. - -In fact, Node.js has it's [own fetch](https://undici.nodejs.org/#/) since version 17.5.0 under an experimental flag and [full support at version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility). If we want to support order version, we will need to use a third party package. +In fact, Node.js [full support the fetch API since version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) via the [unidici project](https://github.com/nodejs/undici). If we want to support older version, we will need to use a third party package. In the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. -`ofetch` have been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. +[`ofetch`](https://github.com/unjs/ofetch) have been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. @@ -42,30 +40,24 @@ Then, install the package: npm install ofetch ``` -To easily run TypeScript scripts, we can install [jiti](/packages/jiti) as a dev dependency: - -```bash -npm install -D jiti -``` - - +::alert{type="info"} +We can use the package manager of our choice like `npm`, `yarn`, `pnpm` or `bun`. +:: ## First request -In order to make our first request, let's create a file named `first-request.ts` with the following content: - -```ts [first-request.ts] -import { $fetch } from 'ofetch' +In order to make our first request, let's create a file named `first-request.mjs` with the following content: -async function main() {} +```ts [first-request.mjs] +import { ofetch } from 'ofetch' -main().catch(console.error) // Useful to catch errors +const data = await ofetch('https://ungh.cc/repos/unjs/ofetch') ``` Then, we can run the script with: ```bash -jiti first-request.ts +node first-request.mjs ``` And _voilĂ _, we should see something like this: @@ -88,17 +80,70 @@ And _voilĂ _, we should see something like this: } ``` -We have made our first request with ofetch! :tada: Easy, right? +We have made our first request with [`ofetch`](https://github.com/unjs/ofetch)! :tada: Easy, right? ### Manual parsing - +In our first example, the result return is automatically parsed to JSON thanks to [`unjs/destr`](https://github.com/unjs/destr). + + + +This behavior is very useful but sometimes, we want to manually parse the result. + +The first way to change how received data is parsed is using the `responseType` option. [`ofetch`](https://github.com/unjs/ofetch) provides several options: + +- `text` to have the raw text +- `json` to have a JSON object +- `blob` to have a `Blob` object +- `arrayBuffer` to have an `ArrayBuffer` object +- `stream` to have a `ReadableStream` object +To try it, let's create a new file named `manual-parsing.mjs`. + +```ts [manual-parsing.mjs] +import { ofetch } from 'ofetch' + +const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { + responseType: 'blob', +}) + +console.log(data) // A blob object is returned. +``` + +We can also provide a custom function to parse the result using the `parseFunction` option. By default [`ofetch`](https://github.com/unjs/ofetch) use [`unjs/destr`](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. + +```ts [custom-parsing.mjs] +import { ofetch } from 'ofetch' + +const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { + parseFunction: (data) => { + return data // We return the raw data but we could use JSON.parse to return a JSON object. + }, +}) + +console.log(data) // The data is not parsed. +``` + +The function take the raw data as argument and must return something, ideally, data parsed. ## Type safety -Thanks to TypeScript and generics, ofetch can be type safe. Let's see how to do it. +Thanks to TypeScript and generics, [`ofetch`](https://github.com/unjs/ofetch) can be type safe. In order to easily use TypeScript to run our scripts, we will use [`unjs/jiti`](https://github.com/unjs/jiti). + + + +We can install it globally with: + +```bash +npm install -g jiti +``` + +Then, we can run our script with: + +```bash +jiti +``` -First, let's create another file called `type-safety.ts`. +Now that we are ready, let's create another file called `type-safety.ts` to type safe our [`ofetch`](https://github.com/unjs/ofetch) request: ```bash touch type-safety.ts @@ -116,10 +161,10 @@ interface Repo { } ``` -Now, we can use the `$fetch` function, like in our `first-request.ts` file, but this time, we will add a generic type to it. +Now, we can use the [`ofetch`](https://github.com/unjs/ofetch) function, like in our `first-request.ts` file, but this time, we will add a generic type to it. ```ts [type-safety.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' interface Repo { id: number @@ -130,7 +175,7 @@ interface Repo { } async function main() { - const { repo } = await $fetch<{ repo: Repo }>('https://ungh.cc/api/github/unjs/ofetch') + const { repo } = await ofetch<{ repo: Repo }>('https://ungh.cc/api/github/unjs/ofetch') console.log(`The repo ${repo.name} has ${repo.stars} stars.`) // The repo object is now strongly typed. } @@ -146,17 +191,17 @@ This is **not a validation**. It's just a way to tell TypeScript what the respon ## Options -With `ofetch`, we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. +With [`ofetch`](https://github.com/unjs/ofetch), we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. ### Methods -`ofetch` supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.ts` with the following content: +[`ofetch`](https://github.com/unjs/ofetch) supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.ts` with the following content: ```ts [methods.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' async function main() { - const response = await $fetch('https://api.github.com/gists', { + const response = await ofetch('https://api.github.com/gists', { method: 'POST', }) // Be careful, we use the GitHub API. @@ -183,10 +228,10 @@ Supported methods are: Of course, when the method is not `GET` or `HEAD`, we can provide a body to the request. Let's see how to do it by creating a new file named `body.ts` with the following content: ```ts [body.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' async function main() { - const response = await $fetch('https://api.github.com/markdown', { + const response = await ofetch('https://api.github.com/markdown', { method: 'POST', // To provide a body, we need to use the `body` option and just use an object. body: { @@ -223,13 +268,13 @@ The body can take several formats: We can also provide some query string parameters to the request. They can be useful to add filters or to paginate the results. -Imagine we want to get the last 2 tags of the `unjs/ofetch` repository. We can do it by creating a new file named `query-string.ts` with the following content: +Imagine we want to get the last 2 tags of the [`unjs/ofetch`](https://github.com/unjs/ofetch) repository. We can do it by creating a new file named `query-string.ts` with the following content: ```ts [query-string.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' async function main() { - const response = await $fetch('https://api.github.com/repos/unjs/ofetch/tags', { + const response = await ofetch('https://api.github.com/repos/unjs/ofetch/tags', { query: { per_page: 2, }, @@ -279,10 +324,10 @@ We can find a key `params` in the options. It's an alias for `query`. In the [`methods` section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in an header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.ts` with the following content: ```ts [headers.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' async function main() { - const response = await $fetch('https://api.github.com/gists', { + const response = await ofetch('https://api.github.com/gists', { method: 'POST', headers: { Authorization: `token ${process.env.GH_TOKEN}`, @@ -314,10 +359,10 @@ FetchError: [POST] "https://api.github.com/gists": 422 Unprocessable Entity And that's a good thing! It means we have successfully authenticated to GitHub. We just need to provide a valid body to the request but now, we know how to do it! We have to add the `body` option to the request with correct data: ```ts [headers.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' async function main() { - const response = await $fetch('https://api.github.com/gists', { + const response = await ofetch('https://api.github.com/gists', { method: 'POST', headers: { Authorization: `token ${process.env.GH_TOKEN}`, @@ -353,14 +398,14 @@ Never put credential token directly in code. Use an environment variable instead ### Error handling -This part is straight forward with `ofetch`. Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: +This part is straight forward with [`ofetch`](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: ```ts [error-handling.ts] -import { $fetch } from 'ofetch' +import { ofetch } from 'ofetch' async function main() { try { - await $fetch('https://api.github.com', { + await ofetch('https://api.github.com', { method: 'POST' // This allow us to get an error. }) } @@ -402,6 +447,6 @@ We can also completely disable error response using option `ignoreResponseError` ## Conclusion -We have seen how to use `ofetch` to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. +We have seen how to use [`ofetch`](https://github.com/unjs/ofetch) to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. -With this 101, we've just scratch the surface of `ofetch`. In next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates and how to create a fetch with some default options! +With this 101, we've just scratch the surface of [`ofetch`](https://github.com/unjs/ofetch). In next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates and how to create a fetch with some default options! From c0ba487fd5cb88857f57cd96654d1ec585933e38 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 8 Sep 2023 15:24:48 +0200 Subject: [PATCH 05/13] chore: lint --- content/4.resources/1.learn/1.ofetch-101-first-hand.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 49bc2559..861b2aa3 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -62,7 +62,7 @@ node first-request.mjs And _voilĂ _, we should see something like this: -```js +```sh { repo: { id: 319960543, @@ -436,7 +436,7 @@ main().catch(console.error) Then, run the script and we get the following error we can easily use to display a message to the user: -```js +```sh { message: 'Not Found', documentation_url: 'https://docs.github.com/rest/reference/repos#create-a-repository-for-the-authenticated-user' From 313839acadc4fc59083602b83cfabb55c0048ab7 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 8 Sep 2023 15:25:53 +0200 Subject: [PATCH 06/13] content: update jiti start --- content/4.resources/1.learn/1.ofetch-101-first-hand.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 861b2aa3..8992eb46 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -140,7 +140,7 @@ npm install -g jiti Then, we can run our script with: ```bash -jiti +jiti .ts ``` Now that we are ready, let's create another file called `type-safety.ts` to type safe our [`ofetch`](https://github.com/unjs/ofetch) request: From 089e226b85e159fc02b94d151dd992a473bead79 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 3 Nov 2023 01:03:17 +0100 Subject: [PATCH 07/13] chore: move ts to mjs and remove useless backticks --- .../1.learn/1.ofetch-101-first-hand.md | 194 ++++++++---------- 1 file changed, 82 insertions(+), 112 deletions(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 8992eb46..e55cf23d 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -6,23 +6,19 @@ authors: picture: https://esteban-soubiran.site/esteban.webp twitter: soubiran_ packages: - - ofetch # TODO: Add links to the package page (we could add metada) -publishedAt: 2023-09-04 -modifiedAt: 2023-09-04 -layout: learn-post + - ofetch +publishedAt: 2023-11-04 +modifiedAt: 2023-11-04 +layout: article --- -[`ofetch`](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between node, browser and workers and is build on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). +[ofetch](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between node, browser and workers and is build on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). In fact, Node.js [full support the fetch API since version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) via the [unidici project](https://github.com/nodejs/undici). If we want to support older version, we will need to use a third party package. In the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. -[`ofetch`](https://github.com/unjs/ofetch) have been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. - - - - +[ofetch](https://github.com/unjs/ofetch) have been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. ## Installation @@ -48,7 +44,7 @@ We can use the package manager of our choice like `npm`, `yarn`, `pnpm` or `bun` In order to make our first request, let's create a file named `first-request.mjs` with the following content: -```ts [first-request.mjs] +```js [first-request.mjs] import { ofetch } from 'ofetch' const data = await ofetch('https://ungh.cc/repos/unjs/ofetch') @@ -80,17 +76,17 @@ And _voilĂ _, we should see something like this: } ``` -We have made our first request with [`ofetch`](https://github.com/unjs/ofetch)! :tada: Easy, right? +We have made our first request with [ofetch](https://github.com/unjs/ofetch)! :tada: Easy, right? ### Manual parsing -In our first example, the result return is automatically parsed to JSON thanks to [`unjs/destr`](https://github.com/unjs/destr). +In our first example, the result return is automatically parsed to JSON thanks to [unjs/destr](https://github.com/unjs/destr). This behavior is very useful but sometimes, we want to manually parse the result. -The first way to change how received data is parsed is using the `responseType` option. [`ofetch`](https://github.com/unjs/ofetch) provides several options: +The first way to change how received data is parsed is using the `responseType` option. [ofetch](https://github.com/unjs/ofetch) provides several options: - `text` to have the raw text - `json` to have a JSON object @@ -99,7 +95,7 @@ The first way to change how received data is parsed is using the `responseType` - `stream` to have a `ReadableStream` object To try it, let's create a new file named `manual-parsing.mjs`. -```ts [manual-parsing.mjs] +```js [manual-parsing.mjs] import { ofetch } from 'ofetch' const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { @@ -109,9 +105,9 @@ const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { console.log(data) // A blob object is returned. ``` -We can also provide a custom function to parse the result using the `parseFunction` option. By default [`ofetch`](https://github.com/unjs/ofetch) use [`unjs/destr`](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. +We can also provide a custom function to parse the result using the `parseFunction` option. By default [ofetch](https://github.com/unjs/ofetch) use [unjs/destr](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. -```ts [custom-parsing.mjs] +```js [custom-parsing.mjs] import { ofetch } from 'ofetch' const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { @@ -127,23 +123,21 @@ The function take the raw data as argument and must return something, ideally, d ## Type safety -Thanks to TypeScript and generics, [`ofetch`](https://github.com/unjs/ofetch) can be type safe. In order to easily use TypeScript to run our scripts, we will use [`unjs/jiti`](https://github.com/unjs/jiti). - - +::alert{type="info"} +Usage of TypeScript is not mandatory to use [ofetch](https://github.com/unjs/ofetch). +:: -We can install it globally with: +Thanks to TypeScript and generics, [ofetch](https://github.com/unjs/ofetch) can be type safe. In order to easily use TypeScript to run our scripts, we will use [unjs/jiti](https://github.com/unjs/jiti). -```bash -npm install -g jiti -``` + -Then, we can run our script with: +We can use it using `npx`: ```bash -jiti .ts +npx jiti .ts ``` -Now that we are ready, let's create another file called `type-safety.ts` to type safe our [`ofetch`](https://github.com/unjs/ofetch) request: +Now that we are ready, let's create another file called `type-safety.ts` to type safe our [ofetch](https://github.com/unjs/ofetch) request: ```bash touch type-safety.ts @@ -161,7 +155,7 @@ interface Repo { } ``` -Now, we can use the [`ofetch`](https://github.com/unjs/ofetch) function, like in our `first-request.ts` file, but this time, we will add a generic type to it. +Now, we can use the [ofetch](https://github.com/unjs/ofetch) function, like in our `first-request.ts` file, but this time, we will add a generic type to it. ```ts [type-safety.ts] import { ofetch } from 'ofetch' @@ -191,24 +185,20 @@ This is **not a validation**. It's just a way to tell TypeScript what the respon ## Options -With [`ofetch`](https://github.com/unjs/ofetch), we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. +With [ofetch](https://github.com/unjs/ofetch), we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. ### Methods -[`ofetch`](https://github.com/unjs/ofetch) supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.ts` with the following content: +[ofetch](https://github.com/unjs/ofetch) supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.mjs` with the following content: -```ts [methods.ts] +```js [methods.mjs] import { ofetch } from 'ofetch' -async function main() { - const response = await ofetch('https://api.github.com/gists', { - method: 'POST', - }) // Be careful, we use the GitHub API. - - console.log(response) -} +const response = await ofetch('https://api.github.com/gists', { + method: 'POST', +}) // Be careful, we use the GitHub API. -main().catch(console.error) +console.log(response) ``` Of course, we receive an `401 Unauthorized` error, because we didn't provide any authentication. But this error means we have successfully made a `POST` request. @@ -225,24 +215,20 @@ Supported methods are: ### Body -Of course, when the method is not `GET` or `HEAD`, we can provide a body to the request. Let's see how to do it by creating a new file named `body.ts` with the following content: +Of course, when the method is not `GET` or `HEAD`, we can provide a body to the request. Let's see how to do it by creating a new file named `body.mjs` with the following content: -```ts [body.ts] +```js [body.mjs] import { ofetch } from 'ofetch' -async function main() { - const response = await ofetch('https://api.github.com/markdown', { - method: 'POST', - // To provide a body, we need to use the `body` option and just use an object. - body: { - text: 'UnJS is **awesome**!\n\nCheck out their [website](https://unjs.io).', - }, - }) // Be careful, we use the GitHub API. - - console.log(response) -} +const response = await ofetch('https://api.github.com/markdown', { + method: 'POST', + // To provide a body, we need to use the `body` option and just use an object. + body: { + text: 'UnJS is **awesome**!\n\nCheck out their [website](https://unjs.io).', + }, +}) // Be careful, we use the GitHub API. -main().catch(console.error) +console.log(response) ``` And we receive: @@ -321,23 +307,19 @@ We can find a key `params` in the options. It's an alias for `query`. ### Headers -In the [`methods` section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in an header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.ts` with the following content: +In the [`methods` section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in an header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.mjs` with the following content: -```ts [headers.ts] +```js [headers.mjs] import { ofetch } from 'ofetch' -async function main() { - const response = await ofetch('https://api.github.com/gists', { - method: 'POST', - headers: { - Authorization: `token ${process.env.GH_TOKEN}`, - } - }) // Be careful, we use the GitHub API directly. - - console.log(response) -} +const response = await ofetch('https://api.github.com/gists', { + method: 'POST', + headers: { + Authorization: `token ${process.env.GH_TOKEN}`, + } +}) // Be careful, we use the GitHub API directly. -main().catch(console.error) +console.log(response) ``` ::alert{type="info"} @@ -347,7 +329,7 @@ We can generate a new GitHub Token with the [GitHub CLI](https://cli.github.com) We can run the script with: ```bash -GH_TOKEN= jiti headers.ts +GH_TOKEN= node headers.mjs ``` And we get the following error: @@ -358,36 +340,32 @@ FetchError: [POST] "https://api.github.com/gists": 422 Unprocessable Entity And that's a good thing! It means we have successfully authenticated to GitHub. We just need to provide a valid body to the request but now, we know how to do it! We have to add the `body` option to the request with correct data: -```ts [headers.ts] +```js [headers.mjs] import { ofetch } from 'ofetch' -async function main() { - const response = await ofetch('https://api.github.com/gists', { - method: 'POST', - headers: { - Authorization: `token ${process.env.GH_TOKEN}`, - }, - body: { - description: 'This is a gist created by ofetch.', - public: true, - files: { - 'unjs.txt': { - content: 'UnJS is awesome!', - } +const response = await ofetch('https://api.github.com/gists', { + method: 'POST', + headers: { + Authorization: `token ${process.env.GH_TOKEN}`, + }, + body: { + description: 'This is a gist created by ofetch.', + public: true, + files: { + 'unjs.txt': { + content: 'UnJS is awesome!', } } - }) // Be careful, we use the GitHub API directly. + } +}) // Be careful, we use the GitHub API directly. console.log(response.url) -} - -main().catch(console.error) ``` Run the script again: ```bash -GH_TOKEN= jiti headers.ts +GH_TOKEN= node headers.mjs ``` And as a result, we get the URL of the created gist ! :raised_hands: @@ -398,40 +376,32 @@ Never put credential token directly in code. Use an environment variable instead ### Error handling -This part is straight forward with [`ofetch`](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: +This part is straight forward with [ofetch](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: -```ts [error-handling.ts] -import { ofetch } from 'ofetch' +```js [error-handling.mjs] +import { ofetch } from "ofetch"; -async function main() { - try { - await ofetch('https://api.github.com', { - method: 'POST' // This allow us to get an error. - }) - } - catch (error) { - console.error(error) - } +try { + await ofetch("https://api.github.com", { + method: "POST", + }); +} catch (error) { + // Error will be pretty printed + console.error(error); } - -main().catch(console.error) ``` With that, we will only see a `FetchError` in the console. We can do better. In fact, we can retrieve the returned error body using `error.data`. Let's update a little our script: -```ts [error-handling.ts] +```js [error-handling.mjs] // ... -async function main() { - try { - // ... - } - catch (error) { - console.log(error.data) // This allow us to get the error body. - } +try { + // ... +} +catch (error) { + console.log(error.data) // This allow us to get the error body. } - -main().catch(console.error) ``` Then, run the script and we get the following error we can easily use to display a message to the user: @@ -447,6 +417,6 @@ We can also completely disable error response using option `ignoreResponseError` ## Conclusion -We have seen how to use [`ofetch`](https://github.com/unjs/ofetch) to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. +We have seen how to use [ofetch](https://github.com/unjs/ofetch) to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. -With this 101, we've just scratch the surface of [`ofetch`](https://github.com/unjs/ofetch). In next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates and how to create a fetch with some default options! +With this 101, we've just scratch the surface of [ofetch](https://github.com/unjs/ofetch). In next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates and how to create a fetch with some default options! From d2b62237c56e1f8f9453b4f6a5eceb3da3470552 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 3 Nov 2023 01:08:57 +0100 Subject: [PATCH 08/13] fix: typo --- .../1.learn/1.ofetch-101-first-hand.md | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index e55cf23d..56f7ccb4 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -12,13 +12,13 @@ modifiedAt: 2023-11-04 layout: article --- -[ofetch](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between node, browser and workers and is build on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). +[ofetch](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between Node.js, browser and workers and is built on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). -In fact, Node.js [full support the fetch API since version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) via the [unidici project](https://github.com/nodejs/undici). If we want to support older version, we will need to use a third party package. +In fact, Node.js [fully supports the fetch API since version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) via the [unidici project](https://github.com/nodejs/undici). If we want to support older versions, we will need to use a third-party package. -In the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. +At the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. -[ofetch](https://github.com/unjs/ofetch) have been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. +[ofetch](https://github.com/unjs/ofetch) has been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. ## Installation @@ -80,7 +80,7 @@ We have made our first request with [ofetch](https://github.com/unjs/ofetch)! :t ### Manual parsing -In our first example, the result return is automatically parsed to JSON thanks to [unjs/destr](https://github.com/unjs/destr). +In our first example, the returned result is automatically parsed to JSON thanks to [unjs/destr](https://github.com/unjs/destr). @@ -93,6 +93,7 @@ The first way to change how received data is parsed is using the `responseType` - `blob` to have a `Blob` object - `arrayBuffer` to have an `ArrayBuffer` object - `stream` to have a `ReadableStream` object + To try it, let's create a new file named `manual-parsing.mjs`. ```js [manual-parsing.mjs] @@ -102,10 +103,10 @@ const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { responseType: 'blob', }) -console.log(data) // A blob object is returned. +console.log(data) // A Blob object is returned. ``` -We can also provide a custom function to parse the result using the `parseFunction` option. By default [ofetch](https://github.com/unjs/ofetch) use [unjs/destr](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. +We can also provide a custom function to parse the result using the `parseFunction` option. By default, [ofetch](https://github.com/unjs/ofetch) uses [unjs/destr](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. ```js [custom-parsing.mjs] import { ofetch } from 'ofetch' @@ -119,7 +120,7 @@ const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { console.log(data) // The data is not parsed. ``` -The function take the raw data as argument and must return something, ideally, data parsed. +The function takes the raw data as an argument and must return something, ideally, data parsed. ## Type safety @@ -177,7 +178,7 @@ async function main() { main().catch(console.error) ``` -Nothing more to have a full typed repo object! :ok_hand: +Nothing more to have a fully typed repo object! :ok_hand: ::alert{type="warning"} This is **not a validation**. It's just a way to tell TypeScript what the response should look like. If we want full validation, we can use a tool like [zod](https://zod.dev) or [valibot](https://valibot.dev). @@ -201,7 +202,7 @@ const response = await ofetch('https://api.github.com/gists', { console.log(response) ``` -Of course, we receive an `401 Unauthorized` error, because we didn't provide any authentication. But this error means we have successfully made a `POST` request. +Of course, we receive an `401 Unauthorized` error because we didn't provide any authentication. But this error means we have successfully made a `POST` request. Supported methods are: @@ -254,22 +255,18 @@ The body can take several formats: We can also provide some query string parameters to the request. They can be useful to add filters or to paginate the results. -Imagine we want to get the last 2 tags of the [`unjs/ofetch`](https://github.com/unjs/ofetch) repository. We can do it by creating a new file named `query-string.ts` with the following content: +Imagine we want to get the last 2 tags of the [`unjs/ofetch`](https://github.com/unjs/ofetch) repository. We can do it by creating a new file named `query-string.mjs` with the following content: -```ts [query-string.ts] +```js [query-string.mjs] import { ofetch } from 'ofetch' -async function main() { - const response = await ofetch('https://api.github.com/repos/unjs/ofetch/tags', { - query: { - per_page: 2, - }, - }) // Be careful, we use the GitHub API directly. - - console.log(response) -} +const response = await ofetch('https://api.github.com/repos/unjs/ofetch/tags', { + query: { + per_page: 2, + }, +}) // Be careful, we use the GitHub API directly. -main().catch(console.error) +console.log(response) ``` We receive this response (results could be different, because the tags are updated): @@ -307,7 +304,7 @@ We can find a key `params` in the options. It's an alias for `query`. ### Headers -In the [`methods` section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in an header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.mjs` with the following content: +In the [methods section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in a header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.mjs` with the following content: ```js [headers.mjs] import { ofetch } from 'ofetch' @@ -368,7 +365,7 @@ Run the script again: GH_TOKEN= node headers.mjs ``` -And as a result, we get the URL of the created gist ! :raised_hands: +And as a result, we get the URL of the created gist! :raised_hands: ::alert{type="danger"} Never put credential token directly in code. Use an environment variable instead. @@ -376,7 +373,7 @@ Never put credential token directly in code. Use an environment variable instead ### Error handling -This part is straight forward with [ofetch](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: +This part is straightforward with [ofetch](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: ```js [error-handling.mjs] import { ofetch } from "ofetch"; @@ -391,7 +388,7 @@ try { } ``` -With that, we will only see a `FetchError` in the console. We can do better. In fact, we can retrieve the returned error body using `error.data`. Let's update a little our script: +With that, we will only see a `FetchError` in the console. We can do better. In fact, we can retrieve the returned error body using `error.data`. Let's update our script a little: ```js [error-handling.mjs] // ... @@ -400,11 +397,11 @@ try { // ... } catch (error) { - console.log(error.data) // This allow us to get the error body. + console.log(error.data) // This allows us to get the error body. } ``` -Then, run the script and we get the following error we can easily use to display a message to the user: +Then, run the script and we get the following error that we can easily use to display a message to the user: ```sh { @@ -413,10 +410,10 @@ Then, run the script and we get the following error we can easily use to display } ``` -We can also completely disable error response using option `ignoreResponseError`. +We can also completely disable error response using the option `ignoreResponseError`. ## Conclusion We have seen how to use [ofetch](https://github.com/unjs/ofetch) to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. -With this 101, we've just scratch the surface of [ofetch](https://github.com/unjs/ofetch). In next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates and how to create a fetch with some default options! +With this 101, we've just scratched the surface of [ofetch](https://github.com/unjs/ofetch). In the next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates, and how to create a fetch with some default options! From aa854434cebc304f949d0463d1164b14c4e161ba Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 00:09:58 +0000 Subject: [PATCH 09/13] [autofix.ci] apply automated fixes --- .../1.learn/1.ofetch-101-first-hand.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md index 56f7ccb4..aaac591e 100644 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ b/content/4.resources/1.learn/1.ofetch-101-first-hand.md @@ -356,7 +356,7 @@ const response = await ofetch('https://api.github.com/gists', { } }) // Be careful, we use the GitHub API directly. - console.log(response.url) +console.log(response.url) ``` Run the script again: @@ -376,15 +376,16 @@ Never put credential token directly in code. Use an environment variable instead This part is straightforward with [ofetch](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: ```js [error-handling.mjs] -import { ofetch } from "ofetch"; +import { ofetch } from 'ofetch' try { - await ofetch("https://api.github.com", { - method: "POST", - }); -} catch (error) { + await ofetch('https://api.github.com', { + method: 'POST', + }) +} +catch (error) { // Error will be pretty printed - console.error(error); + console.error(error) } ``` From 11f0cd435b538a6d35340e1f2975a7b05751d24e Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 3 Nov 2023 16:35:59 +0100 Subject: [PATCH 10/13] chore: update folder name and add some examples --- app.config.ts | 48 +- content/3.resources.yml | 4 + content/3.resources/1.learn.yml | 5 + .../1.learn/1.ofetch-101-first-hand.md | 421 ++++++++++++++++++ content/4.packages/ofetch.yml | 1 + 5 files changed, 461 insertions(+), 18 deletions(-) create mode 100644 content/3.resources.yml create mode 100644 content/3.resources/1.learn.yml create mode 100644 content/3.resources/1.learn/1.ofetch-101-first-hand.md diff --git a/app.config.ts b/app.config.ts index ce301030..1ead43fd 100644 --- a/app.config.ts +++ b/app.config.ts @@ -126,24 +126,30 @@ export default defineAppConfig({ { title: 'Content', items: [ - // { - // title: 'Learn', - // url: '/learn', - // rel: null, - // target: null, - // }, - // { - // title: 'Build', - // url: '/build', - // rel: null, - // target: null, - // }, - // { - // title: 'Explore', - // url: '/explore', - // rel: null, - // target: null, - // }, + { + title: 'Resouces', + url: '/resources', + rel: null, + target: null, + }, + { + title: 'Learn', + url: '/resources/learn', + rel: null, + target: null, + }, + // { + // title: 'Build', + // url: '/build', + // rel: null, + // target: null, + // }, + // { + // title: 'Explore', + // url: '/explore', + // rel: null, + // target: null, + // }, { title: 'Search', url: '/search', @@ -161,6 +167,12 @@ export default defineAppConfig({ rel: 'noopener', target: '_blank', }, + { + title: 'X', + url: 'https://x.com/unjsio', + rel: 'noopener', + target: '_blank', + }, { title: 'GitHub', url: 'https://github.com/unjs', diff --git a/content/3.resources.yml b/content/3.resources.yml new file mode 100644 index 00000000..8ee81ad5 --- /dev/null +++ b/content/3.resources.yml @@ -0,0 +1,4 @@ +title: Resources +description: Get started with UnJS, craft a project from scratch and deep dive into into packages to learn how they are built. +navigation.icon: i-heroicons-beaker-solid +layout: resources diff --git a/content/3.resources/1.learn.yml b/content/3.resources/1.learn.yml new file mode 100644 index 00000000..420b01a1 --- /dev/null +++ b/content/3.resources/1.learn.yml @@ -0,0 +1,5 @@ +title: Learn the basics +description: Embark on a journey through this vast ecosystem, unraveling the mysteries and unlocking the true potential of more than twenty amazing packages. +navigation.icon: i-heroicons-rocket-launch-solid +navigation.description: Embark on a journey through this vast ecosystem +layout: learn diff --git a/content/3.resources/1.learn/1.ofetch-101-first-hand.md b/content/3.resources/1.learn/1.ofetch-101-first-hand.md new file mode 100644 index 00000000..e840fc64 --- /dev/null +++ b/content/3.resources/1.learn/1.ofetch-101-first-hand.md @@ -0,0 +1,421 @@ +--- +title: ofetch 101 - first hand +description: Discover ofetch, a better API for fetch that works on Node.js, browser, and workers. +authors: + - name: Barbapapazes + picture: https://esteban-soubiran.site/esteban.webp + twitter: soubiran_ +packages: + - ofetch +project: https://github.com/unjs/ofetch/tree/main/examples +publishedAt: 2023-11-04 +modifiedAt: 2023-11-04 +layout: article +--- + +[ofetch](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between Node.js, browser and workers and is built on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). + +In fact, Node.js [fully supports the fetch API since version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) via the [unidici project](https://github.com/nodejs/undici). If we want to support older versions, we will need to use a third-party package. + +At the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. + +[ofetch](https://github.com/unjs/ofetch) has been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. + +## Installation + +First, let's create a new project: + +```bash +mkdir ofetch-101 +cd ofetch-101 +npm init -y +``` + +Then, install the package: + +```bash +npm install ofetch +``` + +::alert{type="info"} +We can use the package manager of our choice like `npm`, `yarn`, `pnpm` or `bun`. +:: + +## First request + +In order to make our first request, let's create a file named `first-request.mjs` with the following content: + +```js [first-request.mjs] +import { ofetch } from 'ofetch' + +const data = await ofetch('https://ungh.cc/repos/unjs/ofetch') +``` + +Then, we can run the script with: + +```bash +node first-request.mjs +``` + +And _voilĂ _, we should see something like this: + +```sh +{ + repo: { + id: 319960543, + name: 'ofetch', + repo: 'unjs/ofetch', + description: 'đŸ˜± A better fetch API. Works on node, browser and workers.', + createdAt: '2020-12-09T13:11:38Z', + updatedAt: '2023-09-04T17:23:36Z', + pushedAt: '2023-09-04T21:05:19Z', + stars: 2474, + watchers: 14, + forks: 80, + defaultBranch: 'main' + } +} +``` + +We have made our first request with [ofetch](https://github.com/unjs/ofetch)! :tada: Easy, right? + +### Manual parsing + +In our first example, the returned result is automatically parsed to JSON thanks to [unjs/destr](https://github.com/unjs/destr). + + + +This behavior is very useful but sometimes, we want to manually parse the result. + +The first way to change how received data is parsed is using the `responseType` option. [ofetch](https://github.com/unjs/ofetch) provides several options: + +- `text` to have the raw text +- `json` to have a JSON object +- `blob` to have a `Blob` object +- `arrayBuffer` to have an `ArrayBuffer` object +- `stream` to have a `ReadableStream` object + +To try it, let's create a new file named `manual-parsing.mjs`. + +```js [manual-parsing.mjs] +import { ofetch } from 'ofetch' + +const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { + responseType: 'blob', +}) + +console.log(data) // A Blob object is returned. +``` + +We can also provide a custom function to parse the result using the `parseFunction` option. By default, [ofetch](https://github.com/unjs/ofetch) uses [unjs/destr](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. + +```js [custom-parsing.mjs] +import { ofetch } from 'ofetch' + +const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { + parseFunction: (data) => { + return data // We return the raw data but we could use JSON.parse to return a JSON object. + }, +}) + +console.log(data) // The data is not parsed. +``` + +The function takes the raw data as an argument and must return something, ideally, data parsed. + +## Type safety + +::alert{type="info"} +Usage of TypeScript is not mandatory to use [ofetch](https://github.com/unjs/ofetch). +:: + +Thanks to TypeScript and generics, [ofetch](https://github.com/unjs/ofetch) can be type safe. In order to easily use TypeScript to run our scripts, we will use [unjs/jiti](https://github.com/unjs/jiti). + + + +We can use it using `npx`: + +```bash +npx jiti .ts +``` + +Now that we are ready, let's create another file called `type-safety.ts` to type safe our [ofetch](https://github.com/unjs/ofetch) request: + +```bash +touch type-safety.ts +``` + +Then, let's create an interface for the response. We will use the same request as in our first request. + +```ts [type-safety.ts] +interface Repo { + id: number + name: string + repo: string + description: string + stars: number +} +``` + +Now, we can use the [ofetch](https://github.com/unjs/ofetch) function, like in our `first-request.ts` file, but this time, we will add a generic type to it. + +```ts [type-safety.ts] +import { ofetch } from 'ofetch' + +interface Repo { + id: number + name: string + repo: string + description: string + stars: number +} + +async function main() { + const { repo } = await ofetch<{ repo: Repo }>('https://ungh.cc/api/github/unjs/ofetch') + + console.log(`The repo ${repo.name} has ${repo.stars} stars.`) // The repo object is now strongly typed. +} + +main().catch(console.error) +``` + +Nothing more to have a fully typed repo object! :ok_hand: + +::alert{type="warning"} +This is **not a validation**. It's just a way to tell TypeScript what the response should look like. If we want full validation, we can use a tool like [zod](https://zod.dev) or [valibot](https://valibot.dev). +:: + +## Options + +With [ofetch](https://github.com/unjs/ofetch), we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. + +### Methods + +[ofetch](https://github.com/unjs/ofetch) supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.mjs` with the following content: + +```js [methods.mjs] +import { ofetch } from 'ofetch' + +const response = await ofetch('https://api.github.com/gists', { + method: 'POST', +}) // Be careful, we use the GitHub API. + +console.log(response) +``` + +Of course, we receive an `401 Unauthorized` error because we didn't provide any authentication. But this error means we have successfully made a `POST` request. + +Supported methods are: + +- `GET` +- `POST` +- `PUT` +- `PATCH` +- `DELETE` +- `HEAD` +- `OPTIONS` + +### Body + +Of course, when the method is not `GET` or `HEAD`, we can provide a body to the request. Let's see how to do it by creating a new file named `body.mjs` with the following content: + +```js [body.mjs] +import { ofetch } from 'ofetch' + +const response = await ofetch('https://api.github.com/markdown', { + method: 'POST', + // To provide a body, we need to use the `body` option and just use an object. + body: { + text: 'UnJS is **awesome**!\n\nCheck out their [website](https://unjs.io).', + }, +}) // Be careful, we use the GitHub API. + +console.log(response) +``` + +And we receive: + +```html +

UnJS is awesome!

+

Check out their website.

+``` + +Perfect and easy! + +The body can take several formats: + +- `string` +- `FormData` +- `URLSearchParams` +- `Blob` +- `BufferSource` +- `ReadableStream` +- `Record` + +### Query string + +We can also provide some query string parameters to the request. They can be useful to add filters or to paginate the results. + +Imagine we want to get the last 2 tags of the [`unjs/ofetch`](https://github.com/unjs/ofetch) repository. We can do it by creating a new file named `query-string.mjs` with the following content: + +```js [query-string.mjs] +import { ofetch } from 'ofetch' + +const response = await ofetch('https://api.github.com/repos/unjs/ofetch/tags', { + query: { + per_page: 2, + }, +}) // Be careful, we use the GitHub API directly. + +console.log(response) +``` + +We receive this response (results could be different, because the tags are updated): + +```js +[ + { + name: 'v1.3.3', + zipball_url: 'https://api.github.com/repos/unjs/ofetch/zipball/refs/tags/v1.3.3', + tarball_url: 'https://api.github.com/repos/unjs/ofetch/tarball/refs/tags/v1.3.3', + commit: { + sha: '051ef83ceba6cc496ac487969c9f6611ef6bd10d', + url: 'https://api.github.com/repos/unjs/ofetch/commits/051ef83ceba6cc496ac487969c9f6611ef6bd10d' + }, + node_id: 'MDM6UmVmMzE5OTYwNTQzOnJlZnMvdGFncy92MS4zLjM=' + }, + { + name: 'v1.3.2', + zipball_url: 'https://api.github.com/repos/unjs/ofetch/zipball/refs/tags/v1.3.2', + tarball_url: 'https://api.github.com/repos/unjs/ofetch/tarball/refs/tags/v1.3.2', + commit: { + sha: '1258d51494656c9507a8bb82646c644c64f23e69', + url: 'https://api.github.com/repos/unjs/ofetch/commits/1258d51494656c9507a8bb82646c644c64f23e69' + }, + node_id: 'MDM6UmVmMzE5OTYwNTQzOnJlZnMvdGFncy92MS4zLjI=' + } +] +``` + +A query string is an object of type `{ [key: string]: any }` so it's easy to use. + +::alert{type="info"} +We can find a key `params` in the options. It's an alias for `query`. +:: + +### Headers + +In the [methods section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in a header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.mjs` with the following content: + +```js [headers.mjs] +import { ofetch } from 'ofetch' + +const response = await ofetch('https://api.github.com/gists', { + method: 'POST', + headers: { + Authorization: `token ${process.env.GH_TOKEN}`, + } +}) // Be careful, we use the GitHub API directly. + +console.log(response) +``` + +::alert{type="info"} +We can generate a new GitHub Token with the [GitHub CLI](https://cli.github.com): `gh auth token`. +:: + +We can run the script with: + +```bash +GH_TOKEN= node headers.mjs +``` + +And we get the following error: + +```bash +FetchError: [POST] "https://api.github.com/gists": 422 Unprocessable Entity +``` + +And that's a good thing! It means we have successfully authenticated to GitHub. We just need to provide a valid body to the request but now, we know how to do it! We have to add the `body` option to the request with correct data: + +```js [headers.mjs] +import { ofetch } from 'ofetch' + +const response = await ofetch('https://api.github.com/gists', { + method: 'POST', + headers: { + Authorization: `token ${process.env.GH_TOKEN}`, + }, + body: { + description: 'This is a gist created by ofetch.', + public: true, + files: { + 'unjs.txt': { + content: 'UnJS is awesome!', + } + } + } +}) // Be careful, we use the GitHub API directly. + +console.log(response.url) +``` + +Run the script again: + +```bash +GH_TOKEN= node headers.mjs +``` + +And as a result, we get the URL of the created gist! :raised_hands: + +::alert{type="danger"} +Never put credential token directly in code. Use an environment variable instead. +:: + +### Error handling + +This part is straightforward with [ofetch](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: + +```js [error-handling.mjs] +import { ofetch } from 'ofetch' + +try { + await ofetch('https://api.github.com', { + method: 'POST', + }) +} +catch (error) { + // Error will be pretty printed + console.error(error) +} +``` + +With that, we will only see a `FetchError` in the console. We can do better. In fact, we can retrieve the returned error body using `error.data`. Let's update our script a little: + +```js [error-handling.mjs] +// ... + +try { + // ... +} +catch (error) { + console.log(error.data) // This allows us to get the error body. +} +``` + +Then, run the script and we get the following error that we can easily use to display a message to the user: + +```sh +{ + message: 'Not Found', + documentation_url: 'https://docs.github.com/rest/reference/repos#create-a-repository-for-the-authenticated-user' +} +``` + +We can also completely disable error response using the option `ignoreResponseError`. + +## Conclusion + +We have seen how to use [ofetch](https://github.com/unjs/ofetch) to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. + +With this 101, we've just scratched the surface of [ofetch](https://github.com/unjs/ofetch). In the next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates, and how to create a fetch with some default options! diff --git a/content/4.packages/ofetch.yml b/content/4.packages/ofetch.yml index 37d0e866..370d5da8 100644 --- a/content/4.packages/ofetch.yml +++ b/content/4.packages/ofetch.yml @@ -6,4 +6,5 @@ github: npm: name: ofetch documentation: https://github.com/unjs/ofetch#ofetch +examples: https://github.com/unjs/ofetch/tree/main/examples layout: package From 1d1d09e9a8f68b6585c477609e5205d5cd010a3e Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 3 Nov 2023 16:42:09 +0100 Subject: [PATCH 11/13] chore: remove old article --- .../1.learn/1.ofetch-101-first-hand.md | 420 ------------------ 1 file changed, 420 deletions(-) delete mode 100644 content/4.resources/1.learn/1.ofetch-101-first-hand.md diff --git a/content/4.resources/1.learn/1.ofetch-101-first-hand.md b/content/4.resources/1.learn/1.ofetch-101-first-hand.md deleted file mode 100644 index aaac591e..00000000 --- a/content/4.resources/1.learn/1.ofetch-101-first-hand.md +++ /dev/null @@ -1,420 +0,0 @@ ---- -title: ofetch 101 - first hand -description: Discover ofetch, a better API for fetch that works on Node.js, browser, and workers. -authors: - - name: Barbapapazes - picture: https://esteban-soubiran.site/esteban.webp - twitter: soubiran_ -packages: - - ofetch -publishedAt: 2023-11-04 -modifiedAt: 2023-11-04 -layout: article ---- - -[ofetch](https://github.com/unjs/ofetch) is a utility package to make HTTP requests. It exists to unify the API between Node.js, browser and workers and is built on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). - -In fact, Node.js [fully supports the fetch API since version 18](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) via the [unidici project](https://github.com/nodejs/undici). If we want to support older versions, we will need to use a third-party package. - -At the same time, we want to be able to fetch in every environment with the same code. Code could start running in a worker and then in a browser. - -[ofetch](https://github.com/unjs/ofetch) has been created to simplify these tasks and provide a wrapper around the native fetch API with more options and features. - -## Installation - -First, let's create a new project: - -```bash -mkdir ofetch-101 -cd ofetch-101 -npm init -y -``` - -Then, install the package: - -```bash -npm install ofetch -``` - -::alert{type="info"} -We can use the package manager of our choice like `npm`, `yarn`, `pnpm` or `bun`. -:: - -## First request - -In order to make our first request, let's create a file named `first-request.mjs` with the following content: - -```js [first-request.mjs] -import { ofetch } from 'ofetch' - -const data = await ofetch('https://ungh.cc/repos/unjs/ofetch') -``` - -Then, we can run the script with: - -```bash -node first-request.mjs -``` - -And _voilĂ _, we should see something like this: - -```sh -{ - repo: { - id: 319960543, - name: 'ofetch', - repo: 'unjs/ofetch', - description: 'đŸ˜± A better fetch API. Works on node, browser and workers.', - createdAt: '2020-12-09T13:11:38Z', - updatedAt: '2023-09-04T17:23:36Z', - pushedAt: '2023-09-04T21:05:19Z', - stars: 2474, - watchers: 14, - forks: 80, - defaultBranch: 'main' - } -} -``` - -We have made our first request with [ofetch](https://github.com/unjs/ofetch)! :tada: Easy, right? - -### Manual parsing - -In our first example, the returned result is automatically parsed to JSON thanks to [unjs/destr](https://github.com/unjs/destr). - - - -This behavior is very useful but sometimes, we want to manually parse the result. - -The first way to change how received data is parsed is using the `responseType` option. [ofetch](https://github.com/unjs/ofetch) provides several options: - -- `text` to have the raw text -- `json` to have a JSON object -- `blob` to have a `Blob` object -- `arrayBuffer` to have an `ArrayBuffer` object -- `stream` to have a `ReadableStream` object - -To try it, let's create a new file named `manual-parsing.mjs`. - -```js [manual-parsing.mjs] -import { ofetch } from 'ofetch' - -const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { - responseType: 'blob', -}) - -console.log(data) // A Blob object is returned. -``` - -We can also provide a custom function to parse the result using the `parseFunction` option. By default, [ofetch](https://github.com/unjs/ofetch) uses [unjs/destr](https://github.com/unjs/destr). Let's make our custom parser by creating a new file named `custom-parsing.mjs`. - -```js [custom-parsing.mjs] -import { ofetch } from 'ofetch' - -const data = await ofetch('https://ungh.cc/repos/unjs/ofetch', { - parseFunction: (data) => { - return data // We return the raw data but we could use JSON.parse to return a JSON object. - }, -}) - -console.log(data) // The data is not parsed. -``` - -The function takes the raw data as an argument and must return something, ideally, data parsed. - -## Type safety - -::alert{type="info"} -Usage of TypeScript is not mandatory to use [ofetch](https://github.com/unjs/ofetch). -:: - -Thanks to TypeScript and generics, [ofetch](https://github.com/unjs/ofetch) can be type safe. In order to easily use TypeScript to run our scripts, we will use [unjs/jiti](https://github.com/unjs/jiti). - - - -We can use it using `npx`: - -```bash -npx jiti .ts -``` - -Now that we are ready, let's create another file called `type-safety.ts` to type safe our [ofetch](https://github.com/unjs/ofetch) request: - -```bash -touch type-safety.ts -``` - -Then, let's create an interface for the response. We will use the same request as in our first request. - -```ts [type-safety.ts] -interface Repo { - id: number - name: string - repo: string - description: string - stars: number -} -``` - -Now, we can use the [ofetch](https://github.com/unjs/ofetch) function, like in our `first-request.ts` file, but this time, we will add a generic type to it. - -```ts [type-safety.ts] -import { ofetch } from 'ofetch' - -interface Repo { - id: number - name: string - repo: string - description: string - stars: number -} - -async function main() { - const { repo } = await ofetch<{ repo: Repo }>('https://ungh.cc/api/github/unjs/ofetch') - - console.log(`The repo ${repo.name} has ${repo.stars} stars.`) // The repo object is now strongly typed. -} - -main().catch(console.error) -``` - -Nothing more to have a fully typed repo object! :ok_hand: - -::alert{type="warning"} -This is **not a validation**. It's just a way to tell TypeScript what the response should look like. If we want full validation, we can use a tool like [zod](https://zod.dev) or [valibot](https://valibot.dev). -:: - -## Options - -With [ofetch](https://github.com/unjs/ofetch), we can do much more than simple `GET` requests. Let's see how to use and combine options to make more complex requests. - -### Methods - -[ofetch](https://github.com/unjs/ofetch) supports all the HTTP methods. Let's see how to use one of them. To test a `POST` request, we will use the GitHub API. Let's create a new file named `methods.mjs` with the following content: - -```js [methods.mjs] -import { ofetch } from 'ofetch' - -const response = await ofetch('https://api.github.com/gists', { - method: 'POST', -}) // Be careful, we use the GitHub API. - -console.log(response) -``` - -Of course, we receive an `401 Unauthorized` error because we didn't provide any authentication. But this error means we have successfully made a `POST` request. - -Supported methods are: - -- `GET` -- `POST` -- `PUT` -- `PATCH` -- `DELETE` -- `HEAD` -- `OPTIONS` - -### Body - -Of course, when the method is not `GET` or `HEAD`, we can provide a body to the request. Let's see how to do it by creating a new file named `body.mjs` with the following content: - -```js [body.mjs] -import { ofetch } from 'ofetch' - -const response = await ofetch('https://api.github.com/markdown', { - method: 'POST', - // To provide a body, we need to use the `body` option and just use an object. - body: { - text: 'UnJS is **awesome**!\n\nCheck out their [website](https://unjs.io).', - }, -}) // Be careful, we use the GitHub API. - -console.log(response) -``` - -And we receive: - -```html -

UnJS is awesome!

-

Check out their website.

-``` - -Perfect and easy! - -The body can take several formats: - -- `string` -- `FormData` -- `URLSearchParams` -- `Blob` -- `BufferSource` -- `ReadableStream` -- `Record` - -### Query string - -We can also provide some query string parameters to the request. They can be useful to add filters or to paginate the results. - -Imagine we want to get the last 2 tags of the [`unjs/ofetch`](https://github.com/unjs/ofetch) repository. We can do it by creating a new file named `query-string.mjs` with the following content: - -```js [query-string.mjs] -import { ofetch } from 'ofetch' - -const response = await ofetch('https://api.github.com/repos/unjs/ofetch/tags', { - query: { - per_page: 2, - }, -}) // Be careful, we use the GitHub API directly. - -console.log(response) -``` - -We receive this response (results could be different, because the tags are updated): - -```js -[ - { - name: 'v1.3.3', - zipball_url: 'https://api.github.com/repos/unjs/ofetch/zipball/refs/tags/v1.3.3', - tarball_url: 'https://api.github.com/repos/unjs/ofetch/tarball/refs/tags/v1.3.3', - commit: { - sha: '051ef83ceba6cc496ac487969c9f6611ef6bd10d', - url: 'https://api.github.com/repos/unjs/ofetch/commits/051ef83ceba6cc496ac487969c9f6611ef6bd10d' - }, - node_id: 'MDM6UmVmMzE5OTYwNTQzOnJlZnMvdGFncy92MS4zLjM=' - }, - { - name: 'v1.3.2', - zipball_url: 'https://api.github.com/repos/unjs/ofetch/zipball/refs/tags/v1.3.2', - tarball_url: 'https://api.github.com/repos/unjs/ofetch/tarball/refs/tags/v1.3.2', - commit: { - sha: '1258d51494656c9507a8bb82646c644c64f23e69', - url: 'https://api.github.com/repos/unjs/ofetch/commits/1258d51494656c9507a8bb82646c644c64f23e69' - }, - node_id: 'MDM6UmVmMzE5OTYwNTQzOnJlZnMvdGFncy92MS4zLjI=' - } -] -``` - -A query string is an object of type `{ [key: string]: any }` so it's easy to use. - -::alert{type="info"} -We can find a key `params` in the options. It's an alias for `query`. -:: - -### Headers - -In the [methods section](#methods), we encountered an error because we didn't provide any authentication. With headers, we can solve our problem. In fact, we need to provide to GitHub an authentication token in a header named `Authorization` with a value of `token `. Let's see how to do it by creating a new file named `headers.mjs` with the following content: - -```js [headers.mjs] -import { ofetch } from 'ofetch' - -const response = await ofetch('https://api.github.com/gists', { - method: 'POST', - headers: { - Authorization: `token ${process.env.GH_TOKEN}`, - } -}) // Be careful, we use the GitHub API directly. - -console.log(response) -``` - -::alert{type="info"} -We can generate a new GitHub Token with the [GitHub CLI](https://cli.github.com): `gh auth token`. -:: - -We can run the script with: - -```bash -GH_TOKEN= node headers.mjs -``` - -And we get the following error: - -```bash -FetchError: [POST] "https://api.github.com/gists": 422 Unprocessable Entity -``` - -And that's a good thing! It means we have successfully authenticated to GitHub. We just need to provide a valid body to the request but now, we know how to do it! We have to add the `body` option to the request with correct data: - -```js [headers.mjs] -import { ofetch } from 'ofetch' - -const response = await ofetch('https://api.github.com/gists', { - method: 'POST', - headers: { - Authorization: `token ${process.env.GH_TOKEN}`, - }, - body: { - description: 'This is a gist created by ofetch.', - public: true, - files: { - 'unjs.txt': { - content: 'UnJS is awesome!', - } - } - } -}) // Be careful, we use the GitHub API directly. - -console.log(response.url) -``` - -Run the script again: - -```bash -GH_TOKEN= node headers.mjs -``` - -And as a result, we get the URL of the created gist! :raised_hands: - -::alert{type="danger"} -Never put credential token directly in code. Use an environment variable instead. -:: - -### Error handling - -This part is straightforward with [ofetch](https://github.com/unjs/ofetch). Surround the HTTP call with a `try/catch` block and we're done. Let's see how to do it by creating a new file named `error-handling.ts` with the following content: - -```js [error-handling.mjs] -import { ofetch } from 'ofetch' - -try { - await ofetch('https://api.github.com', { - method: 'POST', - }) -} -catch (error) { - // Error will be pretty printed - console.error(error) -} -``` - -With that, we will only see a `FetchError` in the console. We can do better. In fact, we can retrieve the returned error body using `error.data`. Let's update our script a little: - -```js [error-handling.mjs] -// ... - -try { - // ... -} -catch (error) { - console.log(error.data) // This allows us to get the error body. -} -``` - -Then, run the script and we get the following error that we can easily use to display a message to the user: - -```sh -{ - message: 'Not Found', - documentation_url: 'https://docs.github.com/rest/reference/repos#create-a-repository-for-the-authenticated-user' -} -``` - -We can also completely disable error response using the option `ignoreResponseError`. - -## Conclusion - -We have seen how to use [ofetch](https://github.com/unjs/ofetch) to easily make HTTP requests in any environment. We have seen how to use the different options to make more complex requests and how to gracefully handle errors. - -With this 101, we've just scratched the surface of [ofetch](https://github.com/unjs/ofetch). In the next articles, we will use some advanced options like `retry` and `timeout` to make our requests more robust, interceptors to make optimistic updates, and how to create a fetch with some default options! From 777a9ad7db21249eb50bf855bb97f06190ccfd42 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 3 Nov 2023 16:48:22 +0100 Subject: [PATCH 12/13] content: add examples key into package template --- content/4.packages/.template.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/content/4.packages/.template.yml b/content/4.packages/.template.yml index 29e292cb..0fc62c87 100644 --- a/content/4.packages/.template.yml +++ b/content/4.packages/.template.yml @@ -7,4 +7,5 @@ npm: name: npm_name playgrounds: documentation: docs_link +examples: layout: package From 9a349a447b5ff8a066384196b355487e26a78e17 Mon Sep 17 00:00:00 2001 From: barbapapazes Date: Fri, 3 Nov 2023 16:49:09 +0100 Subject: [PATCH 13/13] chore: format --- tailwind.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailwind.config.ts b/tailwind.config.ts index 2527f420..6a955398 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -20,7 +20,7 @@ export default > { }, }, fontFamily: { - sans: ['Nunito', 'Nunito fallback' , ...fontFamily.sans], + sans: ['Nunito', 'Nunito fallback', ...fontFamily.sans], }, typography: theme => ({ DEFAULT: {