Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions content/4.resources/1.learn/2.h3-101-first-hand.md
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
Copy link
Copy Markdown
Member

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 )

Copy link
Copy Markdown
Contributor Author

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.


<!-- And more about the node (for node) and web (for runtime using request, response like workers) adapter -->