---
title: Next.js
description: Use rest-rpc with Next.js
---

## Install

```sh
pnpm add @rest-rpc/next
```

## Usage

### Single Route Handler

```ts
// app/api/todos/[id]/route.ts
import { createRouteHandler, route } from "@rest-rpc/next";
import { api } from "@/contract";

const getTodoRoute = route(api.todos.get)
	.middleware(({ request }) => ({
		authorization: request.headers.get("authorization"),
	}))
	.handler(({ id, context }) => {
		return getTodo(id, { authorization: context.authorization });
	});

export const { GET } = createRouteHandler(getTodoRoute);
```

### Catch-all Route Handler

```ts
// app/api/[...rest-rpc].ts
import { createRouteHandler, router } from "@rest-rpc/next";
import { api } from "@/contract";

const routes = router(api)
	.middleware(({ request }) => ({
		authorization: request.headers.get("authorization"),
	}))
	.handlers({
		todos: {
			list() {
				return listTodos();
			},
			get({ id, context }) {
				return getTodo(id, { authorization: context.authorization });
			},
			create({ title }) {
				return createTodo({ title });
			},
		},
	});

export const { GET, POST } = createRouteHandler(routes);
```

## Framework Context

```ts
type MiddlewareInput = {
	request: NextRequest;
	route: HttpRouteDeclaration;
	runtime: Record<never, never>;
};
```

## Options

```ts
type CreateRouteHandlerOptions = {
	errorHandlers?: CreateWebHandlerOptions["errorHandlers"];
	parseBody?: (input: WebRouteParseBodyInput) => unknown | Promise<unknown>;
};
```

### Error Handlers

Request validation errors use `onRequestValidationError`. Response contract
validation errors use `onResponseValidationError` and default to a generic 500
response. Other unhandled route errors use `onUnhandledError`, or are re-thrown
when that hook is omitted or returns `undefined`.

```ts
export const { GET, POST } = createRouteHandler(routes, {
	errorHandlers: {
		onRequestValidationError: ({ issues }) => ({
			status: 422,
			body: { code: "VALIDATION_ERROR", issues },
		}),
		onResponseValidationError: () => ({
			status: 500,
			body: { code: "INVALID_RESPONSE" },
		}),
		onUnhandledError: () => ({
			status: 500,
			body: { code: "INTERNAL_SERVER_ERROR" },
		}),
	},
});
```

### Body Parsing

```ts
const uploadRoute = route(api.uploads.create).handler(createUpload);

export const { POST } = createRouteHandler(uploadRoute, {
	parseBody: ({ request }) => request.formData(),
});
```

## Custom Runtime Context

`@rest-rpc/next` is a small wrapper around the [Web adapter](./web). It creates
Next route handler exports and passes only the `NextRequest` to the middleware.

Use `@rest-rpc/web` directly when middleware needs framework data that Next passes
as the route handler context, or when you want to shape the runtime context
yourself.

```ts
// app/api/[...rest-rpc]/route.ts
import { initWeb } from "@rest-rpc/web";
import type { NextRequest } from "next/server";
import { api } from "@/contract";

type Runtime = {
	params: Promise<{ rest: string[] }>;
};

const web = initWeb<Runtime, NextRequest>();

const routes = web
	.router(api)
	.middleware(async ({ request, runtime }) => {
		const params = await runtime.params;

		return {
			pathname: request.nextUrl.pathname,
			segments: params.rest,
		};
	})
	.handlers({
		todos: {
			list({ context }) {
				return listTodos({
					pathname: context.pathname,
					segments: context.segments,
				});
			},
		},
	});

const handleRequest = web.createRouteHandler(routes);

export const GET = (request: NextRequest, context: Runtime) =>
	handleRequest(request, context);
```
