Skip to main content

Common Options

All of the serverless handlers support a common set of options to configure the behavior of the handlers.

  • jsonQuery - If set to true, the handler will automatically parse all query parameters as JSON values. Default is false.
  • responseValidation - If set to true, the handler will validate the response body against the contract. Default is false.
  • basePath - The base path for the handler. Default is /. See Base Path for more information.
  • cors - Default false - See CORS for more information.
  • errorHandler - See Custom Error Handler for more information.
  • requestMiddleware and responseHandlers - See Middleware for more information.

Base Path

Set the basePath option if your handler's URL path does not start at the root of the domain.

For example, for a Vercel Function that lives inside api/posts.ts, you would set the basePath to /api/posts.

CORS

The cors option allows you to configure CORS for your handler. The cors option can be a boolean (setting it to false disables CORS) or an options object. We use itty-router under the hood for routing and CORS handling, so the options are the same as the itty-router CORS options. You can find the full list of options here.

Error Handling

To throw HTTP errors from anywhere within your code. You can use the TsRestHttpError class. This class takes a status code, a body and optionally a content-type header.

import { TsRestHttpError } from '@ts-rest/serverless';

// anywhere in your code
throw new TsRestHttpError(404, { message: 'Not Found' });

// or with a content-type header
throw new TsRestHttpError(404, 'Not Found', 'text/plain');

You can also use the TsRestRouteError to return a response that is defined in your contract.

import { TsRestRouteError } from '@ts-rest/serverless';
import { contract } from './contract';

// anywhere in your code
throw new TsRestRouteError(contract.getPost, {
status: 404,
body: { message: 'Not Found' },
});

Custom Error Handler

The errorHandler option allows you to define a custom error handler to handle any uncaught exceptions. The error handler is a function that takes an error and request and optionally returns a response object. If you do not return a response object, ts-rest will return a default 500 server error.

Note that this will catch

import { TsRestRequest, TsRestResponse } from '@ts-rest/serverless';

const errorHandler = (error: unknown, request: TsRestRequest) => {
console.error('Server Error', error);

return TsRestResponse.fromJson(
{ message: 'Custom Server Error Message' },
{ status: 500 }
);
};

Middleware

Global Request Middleware

You can set global request middleware by using the requestMiddleware option. These are functions that will be called sequentially before any router handler. This is useful for doing authentication, logging, etc.

You can optionally return a response object from the middleware to short-circuit the request.

You can also extend the request object with additional properties that can be accessed in the router handlers in a type-safe manner.

import { TsRestRequest, TsRestResponse } from '@ts-rest/serverless';
import { fetchRequestHandler } from '@ts-rest/serverless/fetch';
import { contract } from './contract';

export default async (request: Request) => {
return fetchRequestHandler({
request,
contract,
router: {
getPost: async ({ params: { id } }, { request }) => {
const post = prisma.post.findUniqueOrThrow({ where: { id, ownerId: request.userId } });

return {
status: 200,
body: post,
};
},
},
options: {
requestMiddleware: [
(request: TsRestRequest & { userId: string }) => {
if (request.headers.get('Authorization')) {
const userId = authenticate(request.headers.get('Authorization'));
if (!userId) {
return TsRestResponse.fromJson({ message: 'Unauthorized' }, { status: 401 });
}
request.userId = userId;
}
},
],
},
});
}

Global Response Handlers

You can set global response handlers by using the responseHandlers option. This can be useful to intercept all responses, including error responses, before they are sent. This can be useful for logging, adding headers, etc.

import { TsRestRequest } from '@ts-rest/serverless';
import { fetchRequestHandler } from '@ts-rest/serverless/fetch';
import { contract } from './contract';
import { router } from './router';

export default async (request: Request) => {
return fetchRequestHandler({
request,
contract,
router,
options: {
requestMiddleware: [
(request: TsRestRequest & { time: Date }) => {
request.time = new Date();
},
],
responseHandlers: [
(response, request) => {
console.log('Request took', new Date().getTime() - request.time.getTime(), 'ms');
},
],
},
});
}

Per Route Middleware

You can also set middleware for individual routes by passing an object in the form of { middleware: RequestHandler[], handler: (...) => ... } to the route definition.

import { fetchRequestHandler } from '@ts-rest/serverless/fetch';
import { contract } from './contract';

export default async (request: Request) => {
return fetchRequestHandler({
request,
contract,
router: {
getPost: {
middleware: [authenticationMiddleware],
handler: async ({ params: { id } }) => {
const post = prisma.post.findUniqueOrThrow({ where: { id, ownerId: request.userId } });

return {
status: 200,
body: post,
};
},
}
},
});
}