ts-rest is no longer maintained
There are no planned releases, bug fixes, security updates, or support. V4 will not be released by this project; the unfinished work is available for forks to build on.
Forks are welcome
You are welcome to continue ts-rest independently, including individual adapters, under the existing MIT licence. You do not need approval from the original maintainers.
Read the fork guidance and maintenance FAQ
Thank you to everyone who used or contributed to ts-rest, and to everyone who sponsored my work.
Existing documentation
Simple cross-stack type-safety for your API, with just a sprinkle of TypeScript magic ✨
- 🛟 Contract-First API
- 🌈 It's just HTTP/REST
- 🔒 Supports all Standard Schema validation libraries
- 📦 OpenAPI generation
Define your API contract
Define an API contact, allowing you to share your API contract between client and server, or even publish it for a third party to consume.
We support any Typescript validation library that supports Standard Schema, or if you want you can use plain TS types and get no runtime validation.
import { initContract } from '@ts-rest/core';
import { z } from 'zod';
const c = initContract();
export const contract = c.router({
getPokemon: {
method: 'GET',
path: '/pokemon/:id',
responses: {
200: z.object({
name: z.string(),
}),
},
},
});import { initContract } from '@ts-rest/core';
import * as v from 'valibot';
const c = initContract();
export const contract = c.router({
getPokemon: {
method: 'GET',
path: '/pokemon/:id',
responses: {
200: v.object({
name: v.string(),
}),
},
},
});import { initContract } from '@ts-rest/core';
import { type } from 'arktype';
const c = initContract();
export const contract = c.router({
getPokemon: {
method: 'GET',
path: '/pokemon/:id',
responses: {
200: type({
name: 'string',
}),
},
},
});import { initContract } from '@ts-rest/core';
const c = initContract();
export const contract = c.router({
getPokemon: {
method: 'GET',
path: '/pokemon/:id',
responses: {
200: c.type<{ name: string }>(),
},
},
});Fulfill the contract on your server
We support Nest, Next.js, Express, Fastify and more - Our server implementation allows you to guarentee your backend stays in sync with your API contract.
import { initServer } from '@ts-rest/express';
const s = initServer();
const router = s.router(contract, {
getPokemon: async ({ params: { id } }) => { const pokemon = await pokemonService.getPokemon(id);
return {
status: 200,
body: pokemon,
};
},
});Use the API on the client
Our @ts-rest/core package provides a super light-weight fetch based client - We remain close to HTTP fundamentals, so we always return a status code, and body.
import { initClient } from '@ts-rest/core';
const client = initClient(contract, {
baseUrl: 'http://localhost:3000',
});
const result = await client.getPokemon({
params: { id: '1' },});
if (result.status === 200) {
result.body;}Credits
Thank you to all the contributors who made ts-rest possible, including the original core team:
- @oliverbutler - Founder of ts-rest, Lead Engineer at Onsi
- @gabrola - Typescript Expert and first to join the team
- @michaelangeloio - SWE at GitLab, second to join the team
The project and its community are no longer maintained. Please do not expect support through GitHub or Discord.