Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

Hidden page. It waits for review: the sidebar, search and search engines leave it out, and listed pages don't link to it.

Contract-first

Describe procedures in a contract, implement them later, and type clients from the contract alone.

On this page

A contract describes procedures without implementing them. It lives in its own package, and clients and the OpenAPI generator accept it like a router.

import { 
const contract: {
    create<TConfig extends ContractConfig = {}>(): ContractBuilder<ContractRootDef<TConfig["meta"] extends object ? TConfig["meta"] : {}>>;
}
contract
} from '@trpc12/trpc/contract';
const
const c: ContractBuilder<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>>
c
=
const contract: {
    create<TConfig extends ContractConfig = {}>(): ContractBuilder<ContractRootDef<TConfig["meta"] extends object ? TConfig["meta"] : {}>>;
}
contract
.
create<{
    meta: Meta;
}>(): ContractBuilder<ContractRootDef<Meta>>
create
<{ meta: Metameta:
type Meta = {
    auth?: boolean;
}
Meta
}>().
ContractBuilder<ContractRootDef<Meta>>.errors<{
    readonly UNAUTHORIZED: {};
}>(map: {
    readonly UNAUTHORIZED: {};
}): ContractBuilder<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>>
errors
({ type UNAUTHORIZED: {}UNAUTHORIZED: {} });
export const
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
            declared: {
                UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined>;
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
        create: ContractProcedure<...>;
    };
}
appContract
= {
post: {
    byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
        declared: {
            UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
        };
        errors: TrpcError<"UNAUTHORIZED", undefined>;
    }>, {
        inputIn: {
            id: string;
        };
        inputOut: {
            id: string;
        };
        errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>;
        requires: never;
    }>, {
        outputIn: {
            id: string;
            title: string;
            author: string | null;
            createdAt: Date;
        };
        outputOut: {
            ...;
        };
        requires: never;
    }>, {
        ...;
    }>>;
    create: ContractProcedure<...>;
}
post
: {
byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        ...;
    };
    requires: never;
}>, {
    ...;
}>>
byId
:
const c: ContractBuilder<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>>
c
.
ContractBuilder<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>>.input<z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>): ContractBuilder<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>>
input
(
const ById: z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>
ById
).
ContractBuilder<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>, { ...; }>>.output<z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>): ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        ...;
    };
    errors: TrpcError<...> | SchemaValidationError<...>;
    requires: never;
}>, {
    ...;
}>>
output
(
const Post: z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>
Post
).
ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>, { ...; }>, { ...; }>>.errors<{
    readonly NOT_FOUND: {};
}>(map: {
    readonly NOT_FOUND: {};
}): ContractBuilder<ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    ...;
}>, {
    ...;
}>>
errors
({ type NOT_FOUND: {}NOT_FOUND: {} }).
ContractBuilder<ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>, { ...; }>, { ...; }>, { ...; }>>.query(): ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        ...;
    };
    requires: never;
}>, {
    ...;
}>>
query
(),
create: ContractProcedure<"mutation", ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        title: string;
    };
    inputOut: {
        title: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        ...;
    };
    requires: never;
}>>
create
:
const c: ContractBuilder<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>>
c
.
ContractBuilder<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>>.meta<{
    readonly auth: true;
}>(meta: {
    readonly auth: true;
} & UndeclaredMeta<Meta, {
    readonly auth: true;
}>): ContractBuilder<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>>
meta
({ auth: trueauth: true }).
ContractBuilder<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>>.input<z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>): ContractBuilder<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        title: string;
    };
    inputOut: {
        title: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>>
input
(
const NewPost: z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>
NewPost
).
ContractBuilder<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>, { ...; }>>.output<z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>): ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        title: string;
    };
    inputOut: {
        ...;
    };
    errors: TrpcError<...> | SchemaValidationError<...>;
    requires: never;
}>, {
    ...;
}>>
output
(
const Post: z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>
Post
).
ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<"UNAUTHORIZED", undefined>; }>, { ...; }>, { ...; }>>.mutation(): ContractProcedure<"mutation", ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
    declared: {
        UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined>;
}>, {
    inputIn: {
        title: string;
    };
    inputOut: {
        title: string;
    };
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        ...;
    };
    requires: never;
}>>
mutation
(),
}, };

Implementing a contract

t.implement(contract) mirrors the contract. Each leaf already has its input, output, errors and route, and offers only .use(), .provide() and the terminal the contract declares. impl.router() checks at the type level and at runtime that every procedure is implemented and nothing else is added.

const impl = t
  .implement(appContract)
  .use(authed)
  .provide(Db, () => db);

export const appRouter = impl.router({
  post: {
    byId: impl.post.byId.query(
      (opts) => findPost(opts.input.id) ?? opts.errors.NOT_FOUND(),
    ),
    create: impl.post.create.mutation((opts) => createPost(opts.input)),
  },
});
  • An implementation may only return errors its contract declares. Middleware passed to impl.use() or impl.provide() runs for every procedure, so all of them must declare its errors.
  • The contract's meta keys must exist in t's meta with types t accepts, because middleware reads them as t's meta.
  • Top-level contract keys named router, use or provide would shadow the implementer's methods, so t.implement() throws on them.

Clients from a contract

A client takes the contract as its router, so the browser imports no server code and can validate calls against the contract's schemas:

const 
const client: TrpcClient<{
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
            declared: {
                UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined>;
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
        create: ContractProcedure<...>;
    };
}, {}, undefined, LinksTypes<...>>
client
=
createTrpcClient<{
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
            declared: {
                UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined>;
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
        create: ContractProcedure<...>;
    };
}, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<{ post: { byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<...>; }>, { ...; }>, { ...; }>, { ...; }>>; create: ContractProcedure<...>; }; }, readonly [...]>.router: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
            declared: {
                UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined>;
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
        create: ContractProcedure<...>;
    };
}
router
:
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
            declared: {
                UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined>;
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
        create: ContractProcedure<...>;
    };
}
appContract
,
TypedClientOptions<{ post: { byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, { declared: { UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>; }; errors: TrpcError<...>; }>, { ...; }>, { ...; }>, { ...; }>>; create: ContractProcedure<...>; }; }, readonly [...]>.links: readonly [TrpcLink<{}, any>, TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]
links
: [
function experimental_validationLink(opts: ValidationLinkOptions): TrpcLink (+1 overload)experimental_validationLink({ ValidationLinkOptions.contract: objectcontract:
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<Meta>, {
            declared: {
                UNAUTHORIZED: TrpcError<"UNAUTHORIZED", undefined>;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined>;
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
        create: ContractProcedure<...>;
    };
}
appContract
}),
httpLink<{
    url: string;
}>(opts: {
    url: string;
}): TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>
httpLink
({ url: stringurl }),
], });

From a router or a document

toContract(router) derives the contract of an existing router, for the OpenAPI handler and build steps (Lazy subtrees). To start from an OpenAPI document instead, see A contract from OpenAPI.

Credits

Zodios, by ecyrbe, pioneered schema-first APIs in TypeScript, with one definition of every endpoint for the client and the server, and ts-rest, by Oliver Butler, made the contract a router that the server implements. v12's contracts and t.implement() follow oRPC's take on them.