---
title: Fastify
description: Use rest-rpc with Fastify
---

## Install

```package-install
npm i @rest-rpc/fastify
```

## Usage

```ts
import Fastify from "fastify";
import { route, registerRoutes } from "@rest-rpc/fastify";
import { z } from "zod";

const app = Fastify({ logger: true });
const todos = new Map([["todo_1", { id: "todo_1", title: "Write docs" }]]);

const getTodo = route
	.get("/todos/:id")
	.params(z.object({ id: z.string() }))
	.handler(({ params }) => {
		const todo = todos.get(params.id);
		return todo
			? { status: 200, body: todo }
			: { status: 404, body: { code: "TODO_NOT_FOUND" as const } };
	});

export const routes = { todos: { get: getTodo } };

await app.register(
	async (api) => {
		registerRoutes(api, routes);
	},
	{ prefix: "/api" },
);

await app.listen({ port: 3000 });
```

`registerRoutes()` accepts a Fastify instance. Register it inside a plugin to
serve routes under a prefix.

## Framework Context

Handlers and middleware receive the Fastify `req` and `reply` objects in
addition to the [common handler arguments](/docs/route-builder#handler-arguments):

```ts
const getTodo = route
	.get("/todos/:id")
	.params(z.object({ id: z.string() }))
	.handler(({ params, req }) => {
		req.log.info({ todoId: params.id }, "Reading todo");
		return todos.get(params.id);
	});
```

To share an application [context](/docs/context) type across routes, augment
`DefaultContext` in `@rest-rpc/fastify`.

## Options

```ts
type RegisterRoutesOptions = {
	bodyCodecs?: readonly BodyCodec<FastifyRequest>[];
	disableRequestValidation?: boolean;
	disableResponseValidation?: boolean;
	requestValidationErrorHandler?: RequestValidationErrorHandler;
	responseValidationErrorHandler?: ResponseValidationErrorHandler;
	preHandler?: ExtendedFastifyPreHandler[];
};
```

- `bodyCodecs`: see [Request and Response Bodies](#request-and-response-bodies).
- `disableRequestValidation`, `disableResponseValidation`: see
  [Disabling Server Validation](/docs/http-behavior/schemas#disabling-server-validation).
- `requestValidationErrorHandler`, `responseValidationErrorHandler`: see [Error Handling](#error-handling).
- `preHandler`: see [preHandler](#prehandler).

## preHandler

Use the `preHandler` option to run Fastify `preHandler` hooks only for rest-rpc
routes. Each hook receives `req` and `reply` plus a `route` argument that
contains the matched route declaration, including its `metadata`.

The hooks are registered on each rest-rpc route, so they do not run for other
routes in the same Fastify instance. They run before rest-rpc decodes and
validates the request, and before [route middleware](/docs/middleware).

```ts
registerRoutes(app, routes, {
	preHandler: [
		async (req, reply, route) => {
			if (route.metadata?.auth !== "required") return;
			if (!req.headers.authorization) {
				return reply.status(401).send({ code: "UNAUTHORIZED" });
			}
		},
	],
});
```

## Request and Response Bodies

Fastify parses request bodies with its content-type parsers. Add parsers with
`addContentTypeParser()` for content types beyond Fastify's defaults; rest-rpc
validates the resulting `request.body`. A custom codec's `deserialize` receives
the `FastifyRequest` and replaces `request.body` for its content types.

Handlers can return these values for the [default codecs](/docs/http-behavior/serialization#default-codecs):

| Content type                                 | Value                                                         |
| -------------------------------------------- | ------------------------------------------------------------- |
| `application/json` or any `+json` media type | JSON value                                                    |
| `application/x-www-form-urlencoded`          | `URLSearchParams`                                             |
| `text/*`                                     | `string`                                                      |
| Any other media type                         | `Blob`, `Uint8Array` (including `Buffer`), or Node `Readable` |

`multipart/form-data` responses need a custom serializer. A custom serializer's
`body` is passed to `reply.send()`. Return encoded bytes when the codec must
control the exact payload, because Fastify serializes plain objects itself.

## Error Handling

Use `requestValidationErrorHandler` and `responseValidationErrorHandler` to
replace the [default validation error responses](/docs/http-behavior/errors).
Other errors use Fastify's error handler.

```ts
registerRoutes(app, routes, {
	requestValidationErrorHandler: (error, request, reply) => {
		return reply.status(422).send({
			code: "VALIDATION_ERROR",
			issues: error.issues,
		});
	},
	responseValidationErrorHandler: (_error, request, reply) => {
		return reply.status(500).send({ code: "INVALID_RESPONSE" });
	},
});

app.setErrorHandler((error, request, reply) => {
	return reply.status(500).send({ code: "INTERNAL_SERVER_ERROR" });
});
```
