Zod 4 (and all other Standard Schema support) is available as a release candidate `3.53.0-rc.1`
ts-rest
Recipies

Frontend Only

Want to gradually introduce ts-rest to your project?

The ideal way to use ts-rest is to control both the client and server, however, we don't live in a perfect world where we can afford to rewrite everything at once! This guide will show you how to use ts-rest without a backend, commonly utilised to help migrate an existing application to ts-rest.

Adding @ts-rest to your project

pnpm add @ts-rest/core @ts-rest/react-query
bun add @ts-rest/core @ts-rest/react-query
npm install @ts-rest/core @ts-rest/react-query

This command installs the core, and the react-query adapter. We strongly recommend using react-query as it is a fantastic way to handle many common problems with data fetching in react, and ts-rest provides first class support for it.

Initialise a Contract

The first step is to create a contract, normally in the ts-rest docs we advise you to create this in a shared library, either in a monorepo or published to a package registry. However, for this guide we're going to create it in the same project as the client.

const c = initContract();

export const contract = c.router({
  getPosts: {
    method: 'GET',
    path: '/posts',
    responses: {
      200: c.type<{ posts: Post[]; total: number }>(),
    },
    query: z.object({
      take: z.string().transform(Number).optional(),
      skip: z.string().transform(Number).optional(),
      search: z.string().optional(),
    }),
    summary: 'Get all posts',
  },
  // ...
});

Read our core guide to learn more about contracts.

Setup react-query in your project

It's worth following the excellent @tanstack/react-query installation guide to get started with react-query. We're going to assume you've already done this (snippet below)

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// Create a client
const queryClient = new QueryClient();

const App = () => {
  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  );
};

Consume the Contract

Now that we have a contract, we can use it to create a client. We're going to use react-query to handle the data fetching, but you can use any data fetching library you want.

import { contract } from './contract';

export const client = initQueryClient(contract, {
  baseUrl: 'http://localhost:3333',
  baseHeaders: {},
});

const YourComponent = () => {
  const queryResult = client.getPosts.useQuery(
    ['posts'], // <- queryKey
    { query: { take: 10 } }, // <- Query params, Params, Body etc (all typed)
    { staleTime: 1000 }, // <- react-query options (optional)
  );

  // ... use the query result
};

Where should I put the client?

You should probably put the client somewhere accessible to all components, possibly at the top level of your application.

Next Steps

Now that you're using ts-rest in a non-breaking way you're in an excellent position to experiment with your team, if the experiment is successful you can start to migrate your backend to use ts-rest, all incrementally and without breaking your application!