From 53703dc860f1ff6fe7ce71d543deff1cfa810b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9ban?= Date: Thu, 7 Dec 2023 13:59:18 +0100 Subject: [PATCH] docs: add examples (#539) Co-authored-by: Pooya Parsa --- README.md | 6 +++-- examples/body.ts | 23 ++++++++++++++++ examples/cookies.ts | 31 ++++++++++++++++++++++ examples/errors.ts | 37 ++++++++++++++++++++++++++ examples/first-server.ts | 48 ++++++++++++++++++++++++++++++++++ examples/handler-middleware.ts | 30 +++++++++++++++++++++ examples/headers.ts | 35 +++++++++++++++++++++++++ examples/index.mjs | 19 ++++++++++++++ examples/nested-router.ts | 27 +++++++++++++++++++ examples/package.json | 12 +++++++++ examples/query-params.ts | 18 +++++++++++++ examples/redirect.ts | 20 ++++++++++++++ examples/router.ts | 43 ++++++++++++++++++++++++++++++ examples/status.ts | 43 ++++++++++++++++++++++++++++++ examples/url-params.ts | 28 ++++++++++++++++++++ package.json | 4 +-- playground/package.json | 4 +-- pnpm-lock.yaml | 20 ++++++++++++-- pnpm-workspace.yaml | 1 + 19 files changed, 441 insertions(+), 8 deletions(-) create mode 100644 examples/body.ts create mode 100644 examples/cookies.ts create mode 100644 examples/errors.ts create mode 100644 examples/first-server.ts create mode 100644 examples/handler-middleware.ts create mode 100644 examples/headers.ts create mode 100644 examples/index.mjs create mode 100644 examples/nested-router.ts create mode 100644 examples/package.json create mode 100644 examples/query-params.ts create mode 100644 examples/redirect.ts create mode 100644 examples/router.ts create mode 100644 examples/status.ts create mode 100644 examples/url-params.ts diff --git a/README.md b/README.md index 3e8e60ae..822d9095 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ H3 (pronounced as /eษชtสƒฮธriห/, like h-3) is a minimal h(ttp) framework built for high performance and portability. -๐Ÿ‘‰ [Online Playground](https://stackblitz.com/github/unjs/h3/tree/main/playground?startScript=dev) +๐Ÿ‘‰ [Online Playground](https://stackblitz.com/github/unjs/h3/tree/main/playground) + +๐Ÿ‘‰ [Online Examples Playground](https://stackblitz.com/github/unjs/h3/tree/main/examples) ## Features @@ -135,7 +137,7 @@ app.use(router); Routes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3). -For using nested routers, see [this example](https://stackblitz.com/edit/github-2bmusk?file=app.ts&startScript=dev) +For using nested routers, see [this example](./examples/nested-router.ts) ## More app usage examples diff --git a/examples/body.ts b/examples/body.ts new file mode 100644 index 00000000..9d585a6d --- /dev/null +++ b/examples/body.ts @@ -0,0 +1,23 @@ +import { createApp, createRouter, defineEventHandler, readBody } from "h3"; + +export const app = createApp(); + +const router = createRouter() + .get( + "/", + defineEventHandler(() => { + return "use POST method to try!"; + }), + ) + .post( + "/", + defineEventHandler(async (event) => { + const body = await readBody(event); + // Use can also use `readFormData` to get a FormData object, `readMultiPartFormData` to get an array of MultiPartData or `readRawBody` to get a Buffer. + return { + body, + }; + }), + ); + +app.use(router); diff --git a/examples/cookies.ts b/examples/cookies.ts new file mode 100644 index 00000000..3bd96ee8 --- /dev/null +++ b/examples/cookies.ts @@ -0,0 +1,31 @@ +import { + createApp, + createRouter, + defineEventHandler, + getCookie, + setCookie, +} from "h3"; + +export const app = createApp(); + +const router = createRouter() + .get( + "/", + defineEventHandler((event) => { + const testCookie = getCookie(event, "testCookie"); + return `testCookie is ${JSON.stringify( + testCookie, + )} (go to /set to set it)`; + }), + ) + .get( + "/set", + defineEventHandler((event) => { + // By default, path is set to `/`. You can use any of the options supported by the Set-Cookie header. + // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + setCookie(event, "testCookie", "bar", { httpOnly: true }); + return "testCookie is set"; + }), + ); + +app.use(router); diff --git a/examples/errors.ts b/examples/errors.ts new file mode 100644 index 00000000..a330bc39 --- /dev/null +++ b/examples/errors.ts @@ -0,0 +1,37 @@ +import { createApp, createError, createRouter, defineEventHandler } from "h3"; + +export const app = createApp({ debug: true }); + +const router = createRouter() + .get( + "/", + defineEventHandler(() => { + // Always "throw" errors to propgate them to the error handler + throw createError({ statusMessage: "Simple error!", statusCode: 301 }); + }), + ) + .get( + "/complexe-error", + defineEventHandler(() => { + // You can fully customize errors by adding data, cause and if it's a fatal error or not + throw createError({ + status: 400, + message: "Bad request", + statusMessage: "Bad request message", + }); + }), + ) + .get( + "/fatal-error", + defineEventHandler(() => { + // Fatal errors will stop the execution of the current request and will be logged + throw createError({ + status: 500, + message: "Fatal error", + fatal: true, + data: { foo: "bar" }, + }); + }), + ); + +app.use(router); diff --git a/examples/first-server.ts b/examples/first-server.ts new file mode 100644 index 00000000..3c108f79 --- /dev/null +++ b/examples/first-server.ts @@ -0,0 +1,48 @@ +import { createApp, defineEventHandler, toNodeListener } from "h3"; + +export const app = createApp(); + +app + // `/` is the root path and will response to every request. + .use( + "/first-request", + defineEventHandler(() => { + return "hello world"; + }), + ) + .use( + "/hello", + defineEventHandler(() => { + return "world"; + }), + ) + .use( + "/json", + defineEventHandler(() => { + // Automatically set the `Content-Type` header to `application/json`. + return { + hello: "world", + }; + }), + ) + .use( + "/html", + defineEventHandler(() => { + // By default, the `Content-Type` header is set to `text/html`. + return "

hello world

"; + }), + ) + .use( + "/buffer", + defineEventHandler(() => { + // No `Content-Type` header is set by default. You can set it manually using `setHeader`. + return Buffer.from("hello world"); + }), + ) + .use( + "/blob", + defineEventHandler(() => { + // No `Content-Type` header is set by default. You can set it manually using `setHeader`. + return new Blob(["hello world"]); + }), + ); diff --git a/examples/handler-middleware.ts b/examples/handler-middleware.ts new file mode 100644 index 00000000..375e80a2 --- /dev/null +++ b/examples/handler-middleware.ts @@ -0,0 +1,30 @@ +import { + createApp, + createRouter, + defineEventHandler, + defineRequestMiddleware, + defineResponseMiddleware, +} from "h3"; + +export const app = createApp(); + +const router = createRouter().get( + "/", + defineEventHandler({ + onRequest: defineRequestMiddleware(() => { + // Do anything you want here like authentication, rate limiting, etc. + console.log("onRequest"); + // Never return anything from onRequest to avoid to close the connection + }), + onBeforeResponse: defineResponseMiddleware(() => { + // Do anything you want here like logging, collecting metrics, or output compression, etc. + console.log("onResponse"); + // Never return anything from onResponse to avoid to close the connection + }), + handler: defineEventHandler(() => { + return "GET: hello world"; + }), + }), +); + +app.use(router); diff --git a/examples/headers.ts b/examples/headers.ts new file mode 100644 index 00000000..17ff427f --- /dev/null +++ b/examples/headers.ts @@ -0,0 +1,35 @@ +import { + createApp, + createRouter, + defineEventHandler, + getRequestHeader, + getResponseHeaders, + setResponseHeader, +} from "h3"; + +export const app = createApp(); + +const router = createRouter().get( + "/user-agent", + defineEventHandler((event) => { + const userAgent = getRequestHeader(event, "user-agent"); + // You can also use `getRequestHeaders` to get all headers at once. + // const headers = getRequestHeaders(event) + + setResponseHeader(event, "content-type", "text/plain"); + setResponseHeader(event, "x-server", "nitro"); + // You can also use `setResponseHeaders` to set multiple headers at once. + // setResponseHeaders(event, { 'x-server': 'nitro', 'content-type': 'text/plain' }) + + const responseHeaders = getResponseHeaders(event); + // You can also use `getResponseHeader` to get a single header. + // const contentType = getResponseHeader(event, 'content-type') + + return { + userAgent, + responseHeaders, + }; + }), +); + +app.use(router); diff --git a/examples/index.mjs b/examples/index.mjs new file mode 100644 index 00000000..53c450d2 --- /dev/null +++ b/examples/index.mjs @@ -0,0 +1,19 @@ +import { readdir } from "node:fs/promises"; +import { listenAndWatch } from "listhen"; + +async function promptExample() { + const { consola } = await import("consola"); + const exampleFiles = await readdir(new URL(".", import.meta.url)).then((r) => + r.filter((f) => f.endsWith(".ts")), + ); + return await consola.prompt("Select an example to run:", { + type: "select", + options: exampleFiles, + }); +} + +const exampleFile = process.argv[2] || (await promptExample()); + +listenAndWatch(new URL(exampleFile, import.meta.url), { + name: `H3 example: ${exampleFile}`, +}); diff --git a/examples/nested-router.ts b/examples/nested-router.ts new file mode 100644 index 00000000..5dd59347 --- /dev/null +++ b/examples/nested-router.ts @@ -0,0 +1,27 @@ +import { + createApp, + defineEventHandler, + toNodeListener, + createRouter, + useBase, + sendRedirect, +} from "h3"; + +// Init App +export const app = createApp({ debug: true }); + +// Main Router +const router = createRouter(); +router.use( + "/", + defineEventHandler((event) => sendRedirect(event, "/api/test")), +); +app.use(router); + +// Nested API Ruter +const apiRouter = createRouter(); +router.use("/api/**", useBase("/api", apiRouter.handler)); +apiRouter.use( + "/test", + defineEventHandler(() => "API /test"), +); diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 00000000..01165df4 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,12 @@ +{ + "name": "h3-examples", + "private": true, + "scripts": { + "dev": "node ./index.mjs" + }, + "dependencies": { + "h3": "latest", + "listhen": "latest", + "consola": "latest" + } +} diff --git a/examples/query-params.ts b/examples/query-params.ts new file mode 100644 index 00000000..6eaa772a --- /dev/null +++ b/examples/query-params.ts @@ -0,0 +1,18 @@ +import { createApp, createRouter, defineEventHandler, getQuery } from "h3"; + +export const app = createApp(); + +const router = createRouter().get( + "/", + defineEventHandler((event) => { + const query = getQuery(event); + + if (!query.name) { + return "Set ?name=yourname in URL to get a greeting!"; + } + + return `Hello ${query.name}`; + }), +); + +app.use(router); diff --git a/examples/redirect.ts b/examples/redirect.ts new file mode 100644 index 00000000..1e8dbaa5 --- /dev/null +++ b/examples/redirect.ts @@ -0,0 +1,20 @@ +import { createApp, createRouter, defineEventHandler, sendRedirect } from "h3"; + +export const app = createApp(); + +const router = createRouter() + .get( + "/unjs", + defineEventHandler((event) => { + return sendRedirect(event, "https://unjs.io/packages/h3"); // 302 Found by default + }), + ) + .get( + "/permanent", + defineEventHandler((event) => { + // You can use any 3xx status code you want + return sendRedirect(event, "https://unjs.io/packages/h3", 301); + }), + ); + +app.use(router); diff --git a/examples/router.ts b/examples/router.ts new file mode 100644 index 00000000..c049fe72 --- /dev/null +++ b/examples/router.ts @@ -0,0 +1,43 @@ +import { createApp, createRouter, defineEventHandler } from "h3"; + +export const app = createApp(); + +const router = createRouter() + .get( + "/", + defineEventHandler(() => { + return "GET: hello world"; + }), + ) + .post( + "/", + defineEventHandler(() => { + return "POST: hello world"; + }), + ) + .put( + "/", + defineEventHandler(() => { + return "PUT: hello world"; + }), + ) + .delete( + "/", + defineEventHandler(() => { + return "DELETE: hello world"; + }), + ) + .patch( + "/", + defineEventHandler(() => { + return "PATCH: hello world"; + }), + ) + .head( + "/", + defineEventHandler(() => { + return "HEAD: hello world"; + }), + ); + +app.use(router); diff --git a/examples/status.ts b/examples/status.ts new file mode 100644 index 00000000..b918bbec --- /dev/null +++ b/examples/status.ts @@ -0,0 +1,43 @@ +import { + createApp, + createRouter, + defineEventHandler, + getResponseStatus, + getResponseStatusText, + sendNoContent, + setResponseStatus, +} from "h3"; + +export const app = createApp(); + +const router = createRouter() + .get( + "/not-found", + defineEventHandler((event) => { + setResponseStatus(event, 404); + + return "Not found"; // You need to explicitly return something to avoid a 404 'Cannot find any path matching "/not-found"' response. + }), + ) + .get( + "/bad-request", + defineEventHandler((event) => { + setResponseStatus(event, 400, "Bad request message"); // You can customize the status message. + + const status = getResponseStatus(event); // You can get the status message. + const text = getResponseStatusText(event); // You can get the status message. + + return { + status, + text, + }; + }), + ) + .get( + "/no-content", + defineEventHandler((event) => { + sendNoContent(event); // Do not need to explicitly return because `sendNoContent` will cut the connection. + }), + ); + +app.use(router); diff --git a/examples/url-params.ts b/examples/url-params.ts new file mode 100644 index 00000000..382581ac --- /dev/null +++ b/examples/url-params.ts @@ -0,0 +1,28 @@ +import { + createApp, + createRouter, + defineEventHandler, + getRouterParam, + getRouterParams, +} from "h3"; + +export const app = createApp(); + +const router = createRouter() + .get( + "/:name", + defineEventHandler((event) => { + const name = getRouterParam(event, "name"); + return `Hello ${name}`; + }), + ) + .get( + "/:name/:age", + defineEventHandler((event) => { + const params = getRouterParams(event); + + return `Hello ${params.name}, you are ${params.age} years old`; + }), + ); + +app.use(router); diff --git a/package.json b/package.json index 6ed6114f..b1c35e75 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "scripts": { "build": "unbuild", "dev": "vitest", - "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground", - "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w", + "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground examples", + "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground examples -w", "play": "listhen -w ./playground/app.ts", "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs", "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags", diff --git a/playground/package.json b/playground/package.json index f7582346..c37d0d9a 100644 --- a/playground/package.json +++ b/playground/package.json @@ -6,7 +6,7 @@ "dev": "listhen -w ./app.ts" }, "dependencies": { - "h3": "^1.9.0", - "listhen": "^1.5.5" + "h3": "latest", + "listhen": "latest" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdc131be..237f0aa5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,13 +103,25 @@ importers: specifier: ^3.22.4 version: 3.22.4 + examples: + dependencies: + consola: + specifier: latest + version: 3.2.3 + h3: + specifier: latest + version: link:.. + listhen: + specifier: latest + version: 1.5.5 + playground: dependencies: h3: - specifier: ^1.9.0 + specifier: latest version: link:.. listhen: - specifier: ^1.5.5 + specifier: latest version: 1.5.5 packages: @@ -772,6 +784,7 @@ packages: dependencies: is-glob: 4.0.3 micromatch: 4.0.5 + napi-wasm: 1.1.0 bundledDependencies: - napi-wasm @@ -4891,6 +4904,9 @@ packages: hasBin: true dev: true + /napi-wasm@1.1.0: + resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f1ccb742..001631ac 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - "playground" + - "examples"