Skip to main content

Next.js Server

Installation

pnpm add @ts-rest/next

Usage

For maximum type safety, by default @ts-rest/next uses a single endpoint for all routes.

info

It is possible to split out to multiple endpoints by having multiple [...ts-rest].tsx files in different folders, so long as the routers you use all begin with the same path e.g. all /posts endpoints in a posts folder.

// pages/api/[...ts-rest].tsx
import { createNextRoute, createNextRouter } from '@ts-rest/next';

const postsRouter = createNextRoute(api.posts, {
createPost: async (args) => {
const newPost = await posts.createPost(args.body);

return {
status: 201,
body: newPost,
};
},
});

const router = createNextRoute(api, {
posts: postsRouter,
});

// Actually initiate the collective endpoints
export default createNextRouter(api, router);

createNextRouter is a function that takes a router and a complete router, and creates endpoints, with the correct methods, paths and callbacks.

JSON Query Parameters

To handle JSON query parameters, you can use the jsonQuery option.

export default createNextRouter(api, router, { jsonQuery: true });

Response Validation

To enable response parsing and validation, you can use the validateResponses option. If there is a corresponding response Zod schema defined in the contract for the returned status code, the response will be parsed and validated. If validation fails a ResponseValidationError will be thrown causing a 500 response to be returned.

export default createNextRouter(api, router, { validateResponses: true });

Error Handling

You can create a global error handler to handle any thrown errors by using the errorHandler option. This includes response validation errors.

export default createNextRouter(api, router, {
responseValidation: true,
errorHandler: (error: unknown, req: NextApiRequest, res: NextApiResponse) => {
if (error instanceof ResponseValidationError) {
console.log(error.cause);
return res.status(500).json({ message: 'Internal Server Error' });
}
},
});

Request Validation Error Handling

The default behavior when validating request schema is to return a 400 status with the corresponding ZodError. To customize handling of the request validation errors enable the throwRequestValidation option. This option allows you to handle the request validation error in the global errorHandler function.

export default createNextRouter(api, router, {
throwRequestValidation: true,
errorHandler: (error: unknown, req: NextApiRequest, res: NextApiResponse) => {
if (error instanceof RequestValidationError) {
if (error.body !== null) {
return res
.status(400)
.json({ message: 'Malformed Body', errors: error.body.flatten() });
}

return res.status(400).json({ message: 'Bad Request' });
}
},
});
export class RequestValidationError extends Error {
constructor(
public pathParams: z.ZodError | null,
public headers: z.ZodError | null,
public query: z.ZodError | null,
public body: z.ZodError | null,
) {
super('[ts-rest] request validation failed');
}
}

Future Work

As this pattern doesn't support a lambda per endpoint, it is planned to provide a helper utility to allow individual endpoints to be created.

The downside of this is that it will not be possible to give a Typescript warning if the path is incorrect.