Skip to content

Commit 57562be

Browse files
committed
content: update h3 getting started
1 parent a1a5f32 commit 57562be

File tree

1 file changed

+86
-115
lines changed

1 file changed

+86
-115
lines changed

content/4.resources/1.learn/2.h3-101-first-hand.md renamed to content/learn/articles/2024-02-11-build-your-first-h3-app.md

Lines changed: 86 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,43 @@
11
---
22
title: Build your first H3 app
33
description: Get started with H3 by building a simple app.
4+
authors:
5+
- name: Estéban S
6+
picture: https://github.com/barbapapazes.png
7+
twitter: soubiran_
8+
category: getting-started
9+
packages:
10+
- h3
411
resources:
5-
- # add a link to the examples repo
6-
- # add a link to the documentation
12+
- label: Source Code
13+
to: https://github.com/unjs/examples/tree/main/h3/build-your-first-app
14+
icon: i-simple-icons-github
15+
- label: H3 Documentation
16+
to: https://h3.unjs.io
17+
icon: i-heroicons-book-open
18+
- label: H3 Examples
19+
to: https://github.com/unjs/h3/tree/main/examples
20+
icon: i-simple-icons-github
21+
publishedAt: 2024-02-11
22+
modifiedAt: 2024-02-11
723
---
824

9-
1025
H3 is a minimal http framework for high performance and portability.
1126

12-
During this tutorial, we will create a simple app to get a wide overview of H3 capabilities. This app will serve an HTML file populated with data. There will be some forms to add and remove data. At the end, we will see how to add an API endpoint to get the data in JSON format.
27+
During this tutorial, we will create a very simple app to get a wide overview of H3 capabilities. This app will serve an HTML file populated with data. There will be some forms to add and remove data. At the end, we will see how to add an API endpoint to get the data in JSON format.
1328

1429
> [!NOTE]
1530
> Deep dive into H3 through [the dedicated documentation](https://h3.unjs.io).
1631
32+
> [!TIP]
33+
> For more complexe apps, take a look at [Nitro](https://nitro.unjs.io).
34+
1735
## Prerequisites
1836

1937
To follow this tutorial, we need to have [Node.js](https://nodejs.org/en/) installed on our machine with [npm](https://www.npmjs.com/). We also need to have a basic knowledge of JavaScript.
2038

2139
> [!NOTE]
22-
> Despite H3 is written in TypeScript, you don't need to know TypeScript to use it.
40+
> Despite H3 is written in TypeScript, we don't need to know TypeScript to use it.
2341
2442
## Create a New Project
2543

@@ -41,12 +59,12 @@ And that's it! We are ready to start coding.
4159

4260
## Create the App
4361

44-
To create our first H3 app, we need to create an `app.ts` file at the root of our project. Inside, we will create a new app by importing the `createApp` function from H3 and calling it:
62+
To create our first H3 app, we need to create an `app.mjs` file at the root of our project. Inside, we will create a new app by importing the `createApp` function from H3 and calling it:
4563

46-
```ts [app.ts]
47-
import { createApp } from 'h3';
64+
```js [app.mjs]
65+
import { createApp } from 'h3'
4866

49-
export const app = createApp();
67+
export const app = createApp()
5068
```
5169

5270
:read-more{to="https://h3.unjs.io/concepts/app" title="App"}
@@ -64,7 +82,7 @@ In the `package.json` file, add a script named `start`:
6482
```json [package.json]
6583
{
6684
"scripts": {
67-
"start": "npx --yes listhen -w ./app.ts"
85+
"start": "npx --yes listhen -w ./app.mjs"
6886
}
6987
}
7088
```
@@ -79,17 +97,17 @@ Now that our app is ready to accept HTTP requests, we need to create a router to
7997

8098
With H3, we've just to use the function `createRouter` and add it to our app:
8199

82-
```ts [app.ts]
83-
import { createApp, createRouter } from 'h3';
100+
```js [app.mjs]
101+
import { createApp, createRouter } from 'h3'
84102

85-
export const app = createApp();
103+
export const app = createApp()
86104

87-
const router = createRouter();
105+
const router = createRouter()
88106

89-
app.use(router);
107+
app.use(router)
90108
```
91109

92-
The `app.use(router)`{lang="ts"} is necessary to add the router to our app.
110+
The `app.use(router)`{lang="js"} is necessary to add the router to our app.
93111

94112
:read-more{to="https://h3.unjs.io/concepts/router" title="Router"}
95113

@@ -102,114 +120,51 @@ We have an app and a router. The only thing missing is the handlers. A handler i
102120
103121
To add a handler, we can use any of the HTTP methods available on the router. For our tutorial, we will use the `get` method to handle the `GET` requests.
104122

105-
```ts [app.ts]
123+
```js [app.mjs]
106124
// ...
107125

108-
const router = createRouter();
126+
const router = createRouter()
109127

110128
router.get('/', () => {
111-
return 'Hello World!';
112-
});
113-
```
114-
115-
In the code above, we added a handler for the `/` route. This handler will send the string `Hello World!` to the client with a simple `return`{lang="ts"}.
116-
117-
:read-more{to="https://h3.unjs.io/concepts/event-handlers" title="Event Handlers"}
118-
119-
## Create a Fake Database
120-
121-
For our app, we will return an HTML page populated with some data. This part will not be explained in details since it's not the purpose of this tutorial.
122-
123-
To create our fake database (a JavaScript array) with some getters and setters, we need a file named `database.ts`:
124-
125-
```ts [database.ts]
126-
import { Book } from "./types";
127-
128-
/**
129-
* This is a fake database since it's just an array of objects.
130-
*
131-
* For this example, it's sufficient but do not use this in production.
132-
*/
133-
const database: Book[] = [{
134-
title: "Anna Karenina",
135-
price: 42,
136-
}, {
137-
title: "Madame Bovary",
138-
price: 15,
139-
}, {
140-
title: "War and Peace",
141-
price: 36,
142-
}, {
143-
title: "The Great Gatsby",
144-
price: 87,
145-
}, {
146-
title: "Lolita",
147-
price: 23,
148-
}
149-
];
129+
return 'Hello World!'
130+
})
150131

151-
export function getBooks(): Book[] {
152-
return database;
153-
}
154-
155-
export function addBook(book: Book) {
156-
database.push(book);
157-
}
158-
159-
export function removeBook(title: string) {
160-
const item = database.find((item) => item.title === title);
161-
162-
if (!item) {
163-
return
164-
}
165-
166-
const index = database.indexOf(item);
167-
168-
if (index > -1) {
169-
database.splice(index, 1);
170-
}
171-
}
132+
// ...
172133
```
173134

174-
Add some types on a file named `types.ts`:
175-
176-
```ts [types.ts]
177-
export interface Book {
178-
title: string
179-
price: number
180-
}
181-
```
135+
In the code above, we added a handler for the `/` route. This handler will send the string `Hello World!` to the client with a simple `return`{lang="js"}.
182136

183-
> [!IMPORTANT]
184-
> This is a fake database since it's just an array of objects. For this example, it's sufficient but **do not use this in production**.
137+
:read-more{to="https://h3.unjs.io/concepts/event-handlers" title="Event Handlers"}
185138

186139
## Our First HTML Page
187140

188-
For this first route, we will get the books from the database and render them in an HTML page. For each book, we will add a for to remove it from the database. Under the list, we will add a form to add a new book.
141+
For this first route, we will get the books from a static array and render them in an HTML page. For each book, we will add a for to remove it from the database. Under the list, we will add a form to add a new book.
189142

190143
For the style, we will use [Pico CSS](https://picocss.com/).
191144

192-
193-
```ts [app.ts]
145+
```js [app.mjs]
194146
// ...
195147

196148
const router = createRouter()
197149

198-
router.get('/', defineEventHandler(() => {
199-
const books = getBooks()
150+
const books = [
151+
{ title: 'The Hobbit', price: 10 },
152+
{ title: 'The Lord of the Rings', price: 20 },
153+
]
200154

155+
router.get('/', defineEventHandler(() => {
201156
return /* html */`
202157
<html>
203158
<head>
204159
<title>Books</title>
205-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
160+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
206161
</head>
207162
<body>
208163
<main class="container">
209164
<section>
210165
<h1>Books</h1>
211166
<ul>
212-
${books.map((book) => /* html */`
167+
${books.map(book => /* html */`
213168
<li>
214169
${book.title} - ${book.price}
215170
<form action="/remove" method="post">
@@ -244,9 +199,9 @@ Open a browser and go to `http://localhost:3000` to see the result.
244199

245200
:read-more{to="https://h3.unjs.io/concepts/event-handlers#responses-types" title="Responses Types"}
246201

247-
## Interact with the Database
202+
## Use POST Requests
248203

249-
In our HTML page, we have two forms. One to add a book and one to remove a book. We need to add two new routes to handle them.
204+
In our HTML page, we have two forms. One to add a book and one to remove a book. We need to add two new routes to handle them. This is interesting because we will need to handle the body of the request.
250205

251206
### Add a Book
252207

@@ -257,22 +212,26 @@ npm install zod
257212
```
258213

259214
> [!NOTE]
260-
> Zod is a TypeScript-first schema declaration and validation library. It's not mandatory to use it with H3 but it's a good practice to validate the data since it's runtime agnostic.
215+
> Zod is a schema validation with TypeScript type inference. It's not mandatory to use it with H3 but it's a recommended practice to validate the user data.
261216
262217
Then, we can add the route:
263218

264-
```ts [app.ts]
265-
import { z } from 'zod'
219+
```js [app.mjs]
220+
import zod from 'zod'
266221
// ...
267222
const router = createRouter()
268223

224+
const books = [
225+
// ...
226+
]
227+
269228
router.post('/add', defineEventHandler(async (event) => {
270-
const body = await readValidatedBody(event, z.object({
271-
title: z.string(),
272-
price: z.number({ coerce: true }).int().nonnegative(),
229+
const body = await readValidatedBody(event, zod.object({
230+
title: zod.string(),
231+
price: zod.number({ coerce: true }).int().nonnegative(),
273232
}).parse)
274233

275-
addBook(body)
234+
books.push(body)
276235

277236
const referer = getHeader(event, 'referer') ?? '/'
278237
return sendRedirect(event, referer)
@@ -283,27 +242,38 @@ There is two important things to notice in this code.
283242
284243
First, we use the `readValidatedBody` function to read the body of the request and validate it. It's important to validate the data sent by the client to avoid any security issue.
285244
245+
> [!NOTE]
246+
> We can use the `readBody` function to read the body of the request without validation.
247+
286248
Second, we use the `sendRedirect` function to redirect the client to the previous page. We use the `referer` header to get the previous page. If the header is not present, we redirect to the root page.
287249
288250
> [!NOTE]
289251
> We should think to redirect to `/` but using the referer is a better practice if we change the root page.
290252
291253
:read-more{to="https://h3.unjs.io/guides/validate-data" title="Validate Data"}
292254
255+
> [!IMPORTANT]
256+
> For more advanced apps, we should use a database to store the data. Take a look at [Nitro](https://nitro.unjs.io) to achieve this with ease.
257+
293258
### Remove a Book
294259
295-
Nothing new here, we will handle a `POST` request on the `/remove` route:
260+
Nothing new here, we will handle a `POST` request on the `/remove` route, validate the data and remove the book from the array:
296261
297-
```ts [app.ts]
262+
```js [app.mjs]
263+
import zod from 'zod'
298264
// ...
299265
const router = createRouter()
300266

267+
const books = [
268+
// ...
269+
]
270+
301271
router.post('/remove', defineEventHandler(async (event) => {
302-
const body = await readValidatedBody(event, z.object({
303-
title: z.string(),
272+
const body = await readValidatedBody(event, zod.object({
273+
title: zod.string(),
304274
}).parse)
305275

306-
removeBook(body.title)
276+
books.splice(books.findIndex(book => book.title === body.title), 1)
307277

308278
const referer = getHeader(event, 'referer') ?? '/'
309279
return sendRedirect(event, referer)
@@ -314,29 +284,30 @@ Same as before, we use the `readValidatedBody` function to read the body of the
314284
315285
:read-more{to="https://h3.unjs.io/guides/validate-data" title="Validate Data"}
316286
287+
> [!IMPORTANT]
288+
> For more advanced apps, we should use a database to store the data. Take a look at [Nitro](https://nitro.unjs.io) to achieve this with ease.
289+
317290
## Add an API Endpoint
318291
319292
We would need to add an API endpoint for external services. For this example, we will create another router dedicated to the API.
320293
321-
```ts [app.ts]
294+
```js [app.mjs]
322295
// ...
323296
const apiRouter = createRouter()
324297
```
325298
326299
Like any router, we will add an handler for the `/books` route:
327300
328-
```ts [app.ts]
301+
```js [app.mjs]
329302
// ...
330303
apiRouter.get('/books', defineEventHandler(() => {
331-
const books = getBooks()
332-
333304
return books
334305
}))
335306
```
336307
337308
Then, we will bind this second router to the first one using a base path:
338309
339-
```ts [app.ts]
310+
```js [app.mjs]
340311
// ...
341312
router.use('/api/**', useBase('/api', apiRouter.handler))
342313
```
@@ -351,4 +322,4 @@ And voilà! We now have our first H3 app!
351322
352323
During this course, we saw how to create a H3 app, use a listener with it, create a router, add handlers, validate data. But there is a lot more to discover about H3 on [the dedicated documentation](https://h3.unjs.io).
353324
354-
Then, do not hesitate to take a look at [Nitro](https://nitro.unjs.io) to create more advanced web servers that run everywhere.
325+
However, H3 is very low level and has very specific use cases. In general, we recommend [Nitro](https://nitro.unjs.io) to create more advanced web servers with database that run everywhere.

0 commit comments

Comments
 (0)