ts-rest is no longer maintained. Forks are welcome. Read the notice.
ts-rest

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.

contract.ts
    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(), 
          }), 
        },
      },
    });
contract.ts
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(), 
      }), 
    },
  },
});
contract.ts
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', 
      }), 
    },
  },
});
contract.ts
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.

server.ts
import {  } from '@ts-rest/express';

const  = ();

const  = .(, {
  : async ({ params: {  } }) => {
params: {
    id: string;
}
const = await .(); return { : 200, : , }; }, });

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.

client.ts
import {  } from '@ts-rest/core';

const  = (, {
  : 'http://localhost:3000',
});

const  = await .({
  params: { : '1' },
params: {
    id: string;
}
}); if (. === 200) { .body;
body: {
    name: string;
}
}

Credits

Thank you to all the contributors who made ts-rest possible, including the original core team:

The project and its community are no longer maintained. Please do not expect support through GitHub or Discord.