Next.js Server
Installation​
- pnpm
- npm
- yarn
pnpm add @ts-rest/next
npm install @ts-rest/next
yarn add @ts-rest/next
Usage​
For maximum type safety, by default @ts-rest/next
uses a single endpoint for all routes.
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';
// `contract` is the AppRouter returned by `c.router`
const postsRouter = createNextRoute(contract.posts, {
createPost: async (args) => {
const newPost = await posts.createPost(args.body);
return {
status: 201,
body: newPost,
};
},
});
const router = createNextRoute(contract, {
posts: postsRouter,
});
// Actually initiate the collective endpoints
export default createNextRouter(contract, router);
createNextRouter
is a function that takes a contract and a complete router, and creates endpoints, with the correct methods, paths and callbacks.
Single Route Handler​
It's also possible to create a handler for a single contract route
// pages/api/posts/index.tsx
import { createSingleRouteHandler } from '@ts-rest/next';
export default createSingleRouteHandler(api.posts.createPost, async (args) => {
const newPost = await posts.createPost(args.body);
return {
status: 201,
body: newPost,
};
});
createSingleRouteHandler
is a function that takes a single contract route and a handler, and creates a nextApiHandler
JSON Query Parameters​
To handle JSON query parameters, you can use the jsonQuery
option.
export default createNextRouter(contract, 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(contract, 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(contract, 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(contract, 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.