Generating Calls from ts-rest
Use Callsheet with ts-rest contracts, with or without code generation.
If you already have a ts-rest contract, you can generate Callsheet directly from it. Callsheet reads the contract and builds a shared call module your app can use anywhere.
Start from a contract
This guide assumes you already have a ts-rest contract. If you don't, ts-rest's own docs are the best place to get set up.
Here is a small contract with one query and one mutation:
import { initContract } from '@ts-rest/core';
const c = initContract();
export const contract = c.router({
films: {
byId: c.query({
method: 'GET',
path: '/films/:id',
pathParams: c.type<{ id: string }>(),
responses: {
200: c.type<{ film: { id: string; title: string } }>(),
},
}),
update: c.mutation({
method: 'PATCH',
path: '/films/:id',
pathParams: c.type<{ id: string }>(),
body: c.type<{ title: string }>(),
responses: {
200: c.type<{ updated: true }>(),
},
}),
},
});Point Callsheet at the contract
If you haven't already, install the packages you need for the generated ts-rest workflow:
pnpm add @callsheet/react-query @ts-rest/core
pnpm add -D @callsheet/codegennpm install @callsheet/react-query @ts-rest/core
npm install -D @callsheet/codegenyarn add @callsheet/react-query @ts-rest/core
yarn add @callsheet/codegen -Dpnpm add @callsheet/swr @ts-rest/core
pnpm add -D @callsheet/codegennpm install @callsheet/swr @ts-rest/core
npm install -D @callsheet/codegenyarn add @callsheet/swr @ts-rest/core
yarn add @callsheet/codegen -DThen create a callsheet.config.ts at your project root:
import { defineConfig } from '@callsheet/codegen';
export default defineConfig({
sources: {
tsRest: [
{
importFrom: './src/rest/contract',
exportName: 'contract',
pathPrefix: ['rest'],
},
],
},
output: {
adapter: 'react-query',
file: './src/generated/calls.ts',
},
});import { defineConfig } from '@callsheet/codegen';
export default defineConfig({
sources: {
tsRest: [
{
importFrom: './src/rest/contract',
exportName: 'contract',
pathPrefix: ['rest'],
},
],
},
output: {
adapter: 'swr',
file: './src/generated/calls.ts',
},
});The tsRest source block tells Callsheet where to find your contract and how to read it:
importFromis the path to the module that exports the contract.exportNameis the name of the contract export in that module.pathPrefixis optional. It namespaces the generated calls under a prefix which is useful when your Callsheet also includes calls from other sources like GraphQL. In this example, the generated paths will start withrest.films.*instead of justfilms.*.
Generate your calls
Run codegen to build the call module:
pnpm callsheet-codegen --config callsheet.config.tsnpx callsheet-codegen --config callsheet.config.tsyarn callsheet-codegen --config callsheet.config.tsCallsheet reads the contract, finds each route, and infers whether it should be a query or mutation based on the route definition in your contract. The generated module looks like this:
import { defineCalls, mutation, query } from '@callsheet/react-query';
import { contract } from '../rest/contract';
export const calls = defineCalls({
rest: {
films: {
byId: query(contract.films.byId),
update: mutation(contract.films.update),
},
},
});import { defineCalls, mutation, query } from '@callsheet/swr';
import { contract } from '../rest/contract';
export const calls = defineCalls({
rest: {
films: {
byId: query(contract.films.byId),
update: mutation(contract.films.update),
},
},
});Each route in the contract becomes a call in the generated module. The contract is still the typed source, and the generated calls module is the shared surface your app imports from.
Use generated calls
Once generated, use the calls through your adapter:
import { queryOptions, useMutation, useQuery } from '@callsheet/react-query';
import { calls } from './generated/calls';
const film = useQuery(
queryOptions(calls.rest.films.byId, {
input: { params: { id: 'wall-e' } },
select: (data) => data.film,
}),
);
const updateFilm = useMutation(calls.rest.films.update);To add shared defaults or invalidation rules, read Using Callsheet with React Query.
import { useQuery, useMutation } from '@callsheet/swr';
import { calls } from './generated/calls';
const { data: film } = useQuery(calls.rest.films.byId, {
input: { params: { id: 'wall-e' } },
});
const { trigger: updateFilm } = useMutation(calls.rest.films.update);To add shared defaults or invalidation rules, read Using Callsheet with SWR.
This is the same shared call surface used throughout the rest of the Callsheet docs. The only difference here is the typed source the calls were built from.
Without code generation
If you don't want to use codegen, you can define ts-rest calls manually using @callsheet/ts-rest. In that setup, add the adapter package your app uses, plus @callsheet/ts-rest, and wrap the routes yourself:
pnpm add @callsheet/react-query @callsheet/ts-restnpm install @callsheet/react-query @callsheet/ts-restyarn add @callsheet/react-query @callsheet/ts-restpnpm add @callsheet/swr @callsheet/ts-restnpm install @callsheet/swr @callsheet/ts-restyarn add @callsheet/swr @callsheet/ts-restimport { defineCalls } from '@callsheet/react-query';
import { query, mutation } from '@callsheet/ts-rest';
import { contract } from './rest/contract';
export const calls = defineCalls({
films: {
byId: query(contract.films.byId),
update: mutation(contract.films.update),
},
});import { defineCalls } from '@callsheet/swr';
import { query, mutation } from '@callsheet/ts-rest';
import { contract } from './rest/contract';
export const calls = defineCalls({
films: {
byId: query(contract.films.byId),
update: mutation(contract.films.update),
},
});The result is the same shared call surface.
Related Docs
- Codegen Config for the full config reference
- Manual Setup if you want to compare the broader manual path
- Using Callsheet with React Query for shared defaults, overrides, and invalidation
- Using Callsheet with SWR for the SWR adapter guide