Skip to main content

Open API

Installation​

Much like the other packages, @ts-rest/open-api is easy to download. If you also want to serve a Swagger UI, you'll want to download swagger-ui-express for express or @nestjs/swagger for Nest.

pnpm add @ts-rest/open-api

Generating an OpenAPI Document​

import { myContract } from './my-api';
import { generateOpenApi } from '@ts-rest/open-api';

const openApiDocument = generateOpenApi(myContract, {
info: {
title: 'Posts API',
version: '1.0.0',
},
});

No kidding, that's it 🥳 You can now serve this document however you want, use it for CodeGen for your non-TS clients, or use it to generate a Swagger UI!

Extending Schemas with @anatine/zod-openapi​

Since our OpenAPI generator uses @anatine/zod-openapi, you can extend your Zod schemas with additional OpenAPI information like title, description, and example to improve the quality and clarity of the generated documentation.

To do so, extend Zod with extendZodWithOpenApi(z) when defining the schemas for your contract:

import { initContract } from '@ts-rest/core';
import { z } from 'zod';
import { extendZodWithOpenApi } from '@anatine/zod-openapi';

extendZodWithOpenApi(z);

const c = initContract();

export const contract = c.router({
getUser: {
method: 'GET',
path: '/users/:id',
pathParams: z.object({
id: z.string().openapi({
description: "The user's ID",
}),
}),
responses: {
200: z
.object({
id: z.string().uuid().openapi({
title: 'Unique ID',
description: 'A UUID generated by the server',
}),
name: z.string(),
phoneNumber: z.string().min(10).openapi({
description: 'US phone numbers only',
example: '555-555-5555',
}),
})
.openapi({
title: 'User',
description: 'A user schema',
mediaExamples: {
myExample: {
value: {
id: '123e4567-e89b-12d3-a456-426614174000',
name: 'John Doe',
phoneNumber: '555-555-5555',
},
summary: 'Example of a user',
},
},
}),
},
},
});

See the official @anatine/zod-openapi docs for more information.

info

All Zod schemas defined in your contract can benefit from the additional OpenAPI schema, including pathParams, queryParams, and responses. These keys are all used when generating the OpenAPI JSON file. This could improve the quality of your generated documentation.

Adding Examples to the Media Type​

In order to add examples to the media type rather than to the schema itself (this is useful if you want to show multiple examples), we have added a mediaExamples property to the .openapi() method options. This will only work for the schemas of the body, responses and individual query parameters if you are using jsonQuery option.

You can see an example of its usage in the code snippet above.

Serving a Swagger UI​

In Express use swagger-ui-express:

import { myContract } from './my-api';
import { generateOpenApi } from '@ts-rest/open-api';
import * as swaggerUi from 'swagger-ui-express';

const openApiDocument = generateOpenApi(myContract, {
info: {
title: 'Posts API',
version: '1.0.0',
},
});

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiDocument));

In NestJS use @nestjs/swagger:

import { myContract } from './my-api';
import { generateOpenApi } from '@ts-rest/open-api';
import { NestFactory } from '@nestjs/core';
import { SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

const document = generateOpenApi(myContract, {
info: {
title: 'Posts API',
version: '1.0.0',
},
});

SwaggerModule.setup('api', app, document);
// ^ Path for swagger

await app.listen(3000);
}
bootstrap();

Don't worry if you don't use express or Nest, whatever library you want to use is OK our OpenAPI returns a plain JSON object which is fully compliant with the OpenAPI spec.

If your contract has unique names for all of your endpoints, you can enable operationId's by setting setOperationId to true in the options:

const openApiSchema = generateOpenApi(
postsApi,
{
info: {
title: 'Posts API',
version: '1.0.0',
},
},
{
setOperationId: true,
},
);

Below is an example of what the OpenAPI document would look like with operationId's enabled:

{
"openapi": "3.0.2",
"paths": {
"/posts": {
"get": {
"description": "Get all posts",
"tags": [],
"parameters": [
{
"name": "userId",
"in": "query",
"schema": {
"type": "number"
}
}
],
"operationId": "getPosts", // <--- This is the operationId
"responses": {

JSON Query Params​

If you've enabled JSON Query params for your server and client, you can enable jsonQuery to mark the query params as application/json in the OpenAPI document:

const openApiSchema = generateOpenApi(
postsApi,
{
info: {
title: 'Posts API',
version: '1.0.0',
},
},
{
jsonQuery: true,
},
);

You'll want to do this to let your non ts-rest clients know that they should send the query params as JSON.