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;}
Meet the team
We've got many, many contributors, but the core team consists of:
- @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
We all work full time, so we tend to have non overlapping schedules, which normally means at some point one of us will be available to help with issues - We respond more easily on GitHub than on Discord, although reach out to us on Discord if you'd like to chat.