Skip to main content

Without a Backend (Gradual Migration)

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

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.response<{ 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 />

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

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!