-
Notifications
You must be signed in to change notification settings - Fork 27
content: add h3 101 #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Barbapapazes
wants to merge
9
commits into
main
Choose a base branch
from
content/add-h3-101
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
content: add h3 101 #137
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
7a8c8b1
content: add h3 101
Barbapapazes 12ac7a2
[autofix.ci] apply automated fixes
autofix-ci[bot] 6f8e765
chore: lint
Barbapapazes 39eae8b
content: add more about usage
Barbapapazes a61b21e
[autofix.ci] apply automated fixes
autofix-ci[bot] fbbbbaa
content: udpate
Barbapapazes a1a5f32
Merge branch 'main' into content/add-h3-101
Barbapapazes 57562be
content: update h3 getting started
Barbapapazes 4ddf8b8
chore: lint
Barbapapazes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| --- | ||
| title: h3 101 - first hand | ||
| description: Discover h3, a better API for fetch that works on Node.js, browser, and workers. | ||
| authors: | ||
| - name: EstΓ©ban Soubiran | ||
| picture: https://esteban-soubiran.site/esteban.webp | ||
| twitter: soubiran_ | ||
| packages: | ||
| - h3 | ||
| publishedAt: 2023-09-12 | ||
| modifiedAt: 2023-09-12 | ||
| layout: learn-post | ||
| --- | ||
|
|
||
| <!-- what is h3, the specificity with composable (and tree shaking), perf... --> | ||
|
|
||
| ## Installation | ||
|
|
||
| First, let's create a new project: | ||
|
|
||
| ```bash | ||
| mkdir h3-101 | ||
| cd h3-101 | ||
| npm init -y | ||
| ``` | ||
|
|
||
| Then, install h3: | ||
|
|
||
| ```bash | ||
| npm install h3 | ||
| ``` | ||
|
|
||
| ::alert{type="info"} | ||
| We can use the package manager of our choice like `npm`, `yarn`, `pnpm` or `bun`. | ||
| :: | ||
|
|
||
| ## First Server | ||
|
|
||
| Before deep diving into [`unjs/h3`](https://github.com/unjs/h3), let's create the simpler server possible to understand how it works and some of its specificities. | ||
|
|
||
| To do so, we can create our first file named `first-server.mjs`. | ||
|
|
||
| First of all, we will first create our app, where request are processed. | ||
|
|
||
| ```js [first-server.mjs] | ||
| import { createApp } from 'h3' | ||
|
|
||
| const app = createApp() | ||
| ``` | ||
|
|
||
| Then, we will plug a event handler. It's a function that will be invoked on requests. In our case, we will response to every request with `Hello World!`. | ||
|
|
||
| ```js [first-server.mjs] | ||
| import { createApp, defineEventHandler } from 'h3' | ||
|
|
||
| const app = createApp() | ||
| .use('/', defineEventHandler(() => { | ||
| return new Response('Hello World!') | ||
| })) | ||
| ``` | ||
|
|
||
| But our app does not listen requests, just handle them for the moment. To listen for requests to be handled, we will use [`node:http`](https://nodejs.org/api/http.html) to create a server and listen on port `3000`. | ||
|
|
||
| ```js [first-server.mjs] | ||
| import { createServer } from 'node:http' | ||
| import { createApp, defineEventHandler, toNodeListener } from 'h3' | ||
|
|
||
| const app = createApp() | ||
| .use('/', defineEventHandler(() => { | ||
| return new Response('Hello World!') | ||
| })) | ||
|
|
||
| createServer(toNodeListener(app)).listen(3000) | ||
| ``` | ||
|
|
||
| Finally, we can run it with [`Node.js`](https://nodejs.org/en/): | ||
|
|
||
| ```bash | ||
| node first-script.mjs | ||
| ``` | ||
|
|
||
| Then, we can use [`curl`](https://curl.se/) to test it: | ||
|
|
||
| ```bash | ||
| curl http://localhost:3000 | ||
| # Output: Hello World! | ||
| ``` | ||
|
|
||
| And it works! | ||
|
|
||
| ### Understanding the Code | ||
|
|
||
| Let's take a moment to understand the last line because it could be a bit confusing and unfamiliar but it's what's make [`unjs/h3`](https://github.com/unjs/h3) so powerful. | ||
|
|
||
| ```js | ||
| import { createServer } from 'node:http' | ||
| import { createApp, toNodeListener } from 'h3' | ||
|
|
||
| // ... | ||
|
|
||
| createServer(toNodeListener(app)).listen(3000) | ||
| ``` | ||
|
|
||
| The fist thing we can notice is that the app is completely runtime agnostic so ready for the future. In fact, we create an app and pass it to a function that wraps it to make it compatible with Node.js. It's called an adapter and the concept is important. | ||
|
|
||
| [`unjs/h3`](https://github.com/unjs/h3) provide another important wrapper for the [`Fetch API`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) called `toWebHandler` that get an [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and return a [`Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). It enable us to deploy [`unjs/h3](https://github.com/unjs/h3) on the edge to [`Cloudflare Workers`](https://workers.cloudflare.com/), [`Deno Deploy`](https://deno.com/deploy), [`Bun`](https://bun.sh/), [`Lagon`](https://lagon.app/), or more. | ||
|
|
||
| Secondly, we can notice that we use [`createServer`](https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener) from `node:http`. The listener is not build-in [`unjs/h3](https://github.com/unjs/h3) because edge runtime don't need it and it's a very platform specific thing. | ||
|
|
||
| In the same time, it's allow use to create our own listener to put him on steroids. In the ecosystem, it's called [`unjs/listhen`](https://github.com/unjs/listhen) π. | ||
|
|
||
| <!-- related article to listhen 101 --> | ||
|
|
||
| All of this makes [`unjs/h3](https://github.com/unjs/h3) fully agnostic from the infrastructure and versatile to be used everywhere anytime. | ||
|
|
||
| ## Add a Router | ||
|
|
||
| Now we have understand the basics, let's add a router to our app to make it more useful. In fact, the first example respond always with `Hello World!` but we want to respond with `Hello World!` only on the root path `/`. | ||
|
|
||
| We can try with `/foo`: | ||
|
|
||
| ```bash | ||
| curl http://localhost:3000/foo | ||
| # Output: Hello World! | ||
| ``` | ||
|
|
||
| We expect to have a `404` error but we have a `200` response. It's because we don't have a router and the handler is called on every request. | ||
|
|
||
| To fix it, create a new file named `routing.mjs`. Same as before, we will need to create an app and a server. | ||
|
|
||
| ```js [routing.mjs] | ||
| import { createServer } from 'node:http' | ||
| import { createApp, toNodeListener } from 'h3' | ||
|
|
||
| const app = createApp() | ||
|
|
||
| createServer(toNodeListener(app)).listen(3000) | ||
| ``` | ||
|
|
||
| Then, we will add a router to our app. It will be able to route requests to the right handler. | ||
|
|
||
| ```js [routing.mjs] | ||
| import { createServer } from 'node:http' | ||
| import { createApp, createRouter, defineEventHandler, toNodeListener } from 'h3' | ||
|
|
||
| const app = createApp() | ||
|
|
||
| const router = createRouter() | ||
| .get('/', defineEventHandler(() => { | ||
| return 'Hello World!' | ||
| })) | ||
|
|
||
| app.use(router) | ||
|
|
||
| createServer(toNodeListener(app)).listen(3000) | ||
| ``` | ||
|
|
||
| When a request is received, the router will try to match it with the right handler. In our case, we have only one handler for the root path `/`. | ||
|
|
||
| Finally, we can run it: | ||
|
|
||
| ```bash | ||
| node routing.mjs | ||
| ``` | ||
|
|
||
| And test it: | ||
|
|
||
| ```bash | ||
| curl http://localhost:3000 | ||
| # Output: Hello World! | ||
| curl http://localhost:3000/foo | ||
| # Output: { "statusCode": 404, "statusMessage": "Cannot find any path matching /foo.", "stack": [] } | ||
| ``` | ||
|
|
||
| Perfect! It works as expected. | ||
|
|
||
| ### HTTP Methods | ||
|
|
||
| Let's try `curl -X POST http://localhost:3000` and see what happens. | ||
|
|
||
| We receive a `404` error. It's because we setup our handler only for `GET` requests using the `get` method of the router. | ||
|
|
||
| To handle others HTTP methods, we can use the appropriate method: | ||
|
|
||
| - `router.get` for `GET` requests | ||
| - `router.post` for `POST` requests | ||
| - `router.put` for `PUT` requests | ||
| - `router.patch` for `PATCH` requests | ||
| - `router.delete` for `DELETE` requests | ||
| - `router.head` for `HEAD` requests | ||
| - `router.options` for `OPTIONS` requests | ||
| - `router.trace` for `TRACE` requests | ||
| - `router.connect` for `CONNECT` requests | ||
| - `router.use` for all requests | ||
|
|
||
| Perfect! To practice, we can add a new handler for `POST` requests: | ||
|
|
||
| ```js [routing.mjs] | ||
| import { createServer } from 'node:http' | ||
| import { createApp, createRouter, defineEventHandler, toNodeListener } from 'h3' | ||
|
|
||
| const app = createApp() | ||
|
|
||
| const router = createRouter() | ||
| .get('/', defineEventHandler(() => { | ||
| return 'GET: Hello World!' | ||
| })) | ||
| .post('/', defineEventHandler(() => { | ||
| return 'POST: Hello World!' | ||
| })) | ||
|
|
||
| app.use(router) | ||
|
|
||
| createServer(toNodeListener(app)).listen(3000) | ||
| ``` | ||
|
|
||
| We can chain handlers to avoid code duplication (`router.get(...).post(...)`). | ||
|
|
||
| ::alert{type="info"} | ||
| Do not forget to manually restart the server. | ||
| :: | ||
|
|
||
| Then, we can test it: | ||
|
|
||
| ```bash | ||
| curl -X POST http://localhost:3000 | ||
| # Output: POST: Hello World! | ||
| ``` | ||
|
|
||
| Easy! :tada: | ||
|
|
||
| ## Composables | ||
|
|
||
| ### URL Params | ||
|
|
||
| ### Query Params | ||
|
|
||
| ### Body | ||
|
|
||
| ### Headers | ||
|
|
||
| ### Status | ||
|
|
||
| ### Redirects | ||
|
|
||
| ### Cookies | ||
|
|
||
| ### Errors | ||
|
|
||
| ## Hooks and Middlewares | ||
|
|
||
| <!-- And more about the node (for node) and web (for runtime using request, response like workers) adapter --> | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest of the sections such as these be in the docs rather than getting started article. We might link to the h3 docs in a list here (and this article should wait for website )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will cut the article at this section and wait for website.