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()orimpl.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 typestaccepts, because middleware reads them ast's meta. - Top-level contract keys named
router,useorprovidewould shadow the implementer's methods, sot.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.