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, and this page is available as Markdown at https://dotpress.dev/examples/fastify.md.

Fastify

A small notes API, with its Dotpress routes mounted on an existing Fastify 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.

Run it

npx degit dotpressjs/dotpress/examples/fastify my-api
cd my-api
npm install
npm run dev
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:

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.

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:

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:

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 and Deployment › AWS Lambda.

Learn more