> For AI agents: the complete documentation index is available at https://dotpress.dev/llms.txt, the full documentation bundle is available at https://dotpress.dev/llms-full.txt.

# Fastify

A small notes API, with its Dotpress routes mounted on an existing [Fastify](https://fastify.dev) instance, and served by Node.js or by AWS Lambda:

- Dotpress routes next to a native Fastify route (`/health`), with a Fastify hook applied to both;
- one app, two entries: `server.ts` for Node.js, `lambda.ts` for AWS Lambda;
- tests with `inject()` of Fastify, over HTTP, and on simulated API Gateway events, without an AWS account.

Source: [`examples/fastify`](https://github.com/dotpressjs/dotpress/tree/main/examples/fastify).

## Run it

```sh
npx degit dotpressjs/dotpress/examples/fastify my-api
cd my-api
npm install
npm run dev
```

```sh
curl -X POST localhost:3000/notes -H 'content-type: application/json' -d '{"text":"Hello"}'
curl localhost:3000/notes
curl localhost:3000/health
```

## The routes

The routes receive their store as an argument, and know nothing about the server:

```ts file="<root>/../examples/fastify/src/routes.ts"
import { createRouter, notFoundError } from '@dotpress/core'
import { z } from 'zod'
import type { NoteStore } from './notes.ts'

const Note = z.object({
  id: z.uuid(),
  text: z.string(),
  createdAt: z.iso.datetime(),
})

/** Dotpress routes: they know nothing about Fastify or AWS Lambda. */
export const notesRouter = (store: NoteStore) => {
  const router = createRouter()

  router.defineRoute({
    path: '/notes',
    schema: (z) => ({ response: z.array(Note) }),
    handler: () => store.list(),
  })

  router.defineRoute({
    method: 'post',
    path: '/notes',
    status: 201,
    schema: (z) => ({
      body: z.object({ text: z.string().trim().min(1).max(1000) }),
      response: Note,
    }),
    handler: ({ req }) => store.create(req.body.text),
  })

  router.defineRoute({
    path: '/notes/:id',
    schema: () => ({ response: Note }),
    handler: async ({ req }) =>
      (await store.get(req.params.id)) ?? notFoundError('Note not found'),
  })

  return router
}

```

## The app

The Fastify instance keeps its own routes and hooks: `beforeRoutes` registers what must also apply to the Dotpress routes.

```ts file="<root>/../examples/fastify/src/app.ts"
import { fastifyAdapter } from '@dotpress/adapter-fastify'
import { createApp, type Logger } from '@dotpress/core'
import Fastify from 'fastify'
import { createNoteStore } from './notes.ts'
import { notesRouter } from './routes.ts'

/**
 * Mounts the Dotpress routes on a Fastify instance, next to its native
 * routes. Used by the server (`server.ts`) and the Lambda handler
 * (`lambda.ts`).
 */
export const createNotesApp = (options: { logger?: Logger } = {}) => {
  // An existing instance: its plugins, hooks and native routes stay.
  const fastify = Fastify()
  fastify.get('/health', async () => ({ status: 'ok' }))

  return createApp({
    adapter: fastifyAdapter({
      app: fastify,
      // Hooks and plugins registered here apply to the Dotpress routes too.
      beforeRoutes: (app) => {
        app.addHook('onRequest', async (request, reply) => {
          reply.header('x-request-id', request.id)
        })
      },
    }),
    ...(options.logger && { logger: options.logger }),
    routers: [notesRouter(createNoteStore())],
  })
}

```

## Two entries

On Node.js, the server closes gracefully on `SIGTERM`:

```ts file="<root>/../examples/fastify/src/server.ts"
import { createNotesApp } from './app.ts'

const port = Number(process.env.PORT ?? 3000)

const app = await createNotesApp()
await app.listen(port)
console.log(`Notes API: http://localhost:${port}/notes`)

// Closes the Fastify instance once the pending requests are served.
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
  process.once(signal, () => void app.close())
}

```

On AWS Lambda, the same app, created at the first invocation:

```ts file="<root>/../examples/fastify/src/lambda.ts"
import { createLambdaHandler } from '@dotpress/adapter-fastify/lambda'
import { createNotesApp } from './app.ts'

// API Gateway (REST and HTTP APIs), Function URLs and Application Load
// Balancers. The app is created at the first invocation (cold start), then
// reused by the next invocations of the execution environment.
export const handler = createLambdaHandler(() => createNotesApp())

```

`npm run build:lambda` bundles the handler with esbuild into `dist/lambda.mjs`, about 300 kB gzipped with Fastify. Fastify is a CommonJS package: the script defines `require` for it. See [Bundle the handler](https://dotpress.dev/adapters/aws-lambda.md#bundle-the-handler) and [Deployment › AWS Lambda](https://dotpress.dev/deployment/aws-lambda.md).

## Learn more

- [The Fastify adapter](https://dotpress.dev/adapters/fastify.md)
- [The AWS Lambda adapter](https://dotpress.dev/adapters/aws-lambda.md)
- [Testing](https://dotpress.dev/routing/testing.md)
