Skip to content
You are reading the v2 docs, currently in beta.V1 docs
oRPC
Esc
navigateopen⌘Jpreview
On this page

Contract-First

Build your first contract-first API with oRPC: describe the API in a contract, implement it with full type checking, and call it from a client that only needs the contract.

In the Getting Started guide, each procedure is defined and implemented in one place, and the client’s types come from the server’s code. Contract-first splits this into two steps. First you write a contract: a description of every procedure, its input, and its output, with no logic inside. Then you implement the contract, and TypeScript checks that the implementation matches it exactly.

This is worth the extra step when:

  • You want to agree on the API design before writing code, so frontend and backend work can start at the same time.
  • The client lives in another repository or team, and it should depend on the API’s shape, not on server code.
  • You want one source of truth the implementation can never drift away from.

This guide follows the same path as Getting Started, in contract-first order:

  1. Define a contract that describes your API.
  2. Implement the contract on the server and serve it over HTTP.
  3. Call it from a fully typed client built from the contract alone.

Installation

Contract-first adds one package to the usual setup: @orpc/contract, which holds the contract builder. As before, you also need a schema library such as Zod, Valibot, ArkType, or any other Standard Schema library.

npm install @orpc/contract@beta @orpc/server@beta @orpc/client@beta zod
pnpm add @orpc/contract@beta @orpc/server@beta @orpc/client@beta zod
yarn add @orpc/contract@beta @orpc/server@beta @orpc/client@beta zod
bun add @orpc/contract@beta @orpc/server@beta @orpc/client@beta zod

Define a Contract

A contract describes a procedure without implementing it. Build one with the oc builder (short for oRPC contract): declare the input with .input, the output with .output, and stop there. A contract has no .handler. Group contracts into a plain object, just like procedures form a router.

import { const oc: ContractBuilder<object>
The oRPC contract builder. Chain methods like `.input`, `.errors`, and `.output` to define procedure contracts, then compose them into router contracts.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
} from '@orpc/contract'
import * as import zz from 'zod' const
const PlanetSchema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
PlanetSchema
= import zz.
function object<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}>(shape?: {
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
object
({
id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), description: z.ZodOptional<z.ZodString>description: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.optional(): z.ZodOptional<z.ZodString>optional(), }) export const
const listPlanetsContract: ProcedureContractBuilderWithOutput<z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>, object>
listPlanetsContract
= const oc: ContractBuilder<object>
The oRPC contract builder. Chain methods like `.input`, `.errors`, and `.output` to define procedure contracts, then compose them into router contracts.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
.
ContractBuilder<object>.output<z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>>(schema: z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>): ProcedureContractBuilderWithOutput<z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>, object>
Defines the output schema used to validate and type the procedure output.
@see{@link https://orpc.dev/docs/contract/procedure#inputoutput-validation Procedure Contract - Input/Output Validation}
output
(import zz.
function array<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>(element: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, params?: string | z.core.$ZodArrayParams): z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>
array
(
const PlanetSchema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
PlanetSchema
))
export const
const findPlanetContract: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
findPlanetContract
= const oc: ContractBuilder<object>
The oRPC contract builder. Chain methods like `.input`, `.errors`, and `.output` to define procedure contracts, then compose them into router contracts.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
.
ContractBuilder<object>.input<z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>): ProcedureContractBuilderWithInput<z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>, object>
Defines the input schema used to validate and type the procedure input.
@see{@link https://orpc.dev/docs/contract/procedure#inputoutput-validation Procedure Contract - Input/Output Validation}
input
(import zz.
function object<{
    id: z.ZodNumber;
}>(shape?: {
    id: z.ZodNumber;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() }))
.
ProcedureContractBuilderWithInput<ZodObject<{ id: ZodNumber; }, $strip>, object>.output<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>): ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
Defines the output schema used to validate and type the procedure output.
@see{@link https://orpc.dev/docs/contract/procedure#inputoutput-validation Procedure Contract - Input/Output Validation}
output
(
const PlanetSchema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
PlanetSchema
)
export const
const createPlanetContract: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
createPlanetContract
= const oc: ContractBuilder<object>
The oRPC contract builder. Chain methods like `.input`, `.errors`, and `.output` to define procedure contracts, then compose them into router contracts.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
.
ContractBuilder<object>.input<z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>): ProcedureContractBuilderWithInput<z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
Defines the input schema used to validate and type the procedure input.
@see{@link https://orpc.dev/docs/contract/procedure#inputoutput-validation Procedure Contract - Input/Output Validation}
input
(import zz.
function object<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}>(shape?: {
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
object
({ name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), description: z.ZodOptional<z.ZodString>description: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.optional(): z.ZodOptional<z.ZodString>optional() }))
.
ProcedureContractBuilderWithInput<ZodObject<{ name: ZodString; description: ZodOptional<ZodString>; }, $strip>, object>.output<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>): ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
Defines the output schema used to validate and type the procedure output.
@see{@link https://orpc.dev/docs/contract/procedure#inputoutput-validation Procedure Contract - Input/Output Validation}
output
(
const PlanetSchema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>
PlanetSchema
)
export const
const contract: {
    planet: {
        list: ProcedureContractBuilderWithOutput<z.ZodArray<z.ZodObject<{
            id: z.ZodNumber;
            name: z.ZodString;
            description: z.ZodOptional<z.ZodString>;
        }, z.core.$strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
            id: z.ZodNumber;
        }, z.core.$strip>, z.ZodObject<{
            id: z.ZodNumber;
            name: z.ZodString;
            description: z.ZodOptional<z.ZodString>;
        }, z.core.$strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
            name: z.ZodString;
            description: z.ZodOptional<...>;
        }, z.core.$strip>, z.ZodObject<...>, object>;
    };
}
contract
= {
planet: {
    list: ProcedureContractBuilderWithOutput<z.ZodArray<z.ZodObject<{
        id: z.ZodNumber;
        name: z.ZodString;
        description: z.ZodOptional<z.ZodString>;
    }, z.core.$strip>>, object>;
    find: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
        id: z.ZodNumber;
    }, z.core.$strip>, z.ZodObject<{
        id: z.ZodNumber;
        name: z.ZodString;
        description: z.ZodOptional<z.ZodString>;
    }, z.core.$strip>, object>;
    create: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
        name: z.ZodString;
        description: z.ZodOptional<...>;
    }, z.core.$strip>, z.ZodObject<...>, object>;
}
planet
: {
list: ProcedureContractBuilderWithOutput<z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>, object>
list
:
const listPlanetsContract: ProcedureContractBuilderWithOutput<z.ZodArray<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>, object>
listPlanetsContract
,
find: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
find
:
const findPlanetContract: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    id: z.ZodNumber;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
findPlanetContract
,
create: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
create
:
const createPlanetContract: ProcedureContractBuilderWithInputOutput<z.ZodObject<{
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
    description: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, object>
createPlanetContract
,
}, }

A few things to notice:

  • .output matters here. In Getting Started, the client’s result type flows from the handler’s return value. A contract has no handler, so .output is where that type comes from. Skip it and the result type becomes unknown.
  • .output is not just a type: once implemented, the server validates every response against it at runtime.
  • Contracts can also declare typed errors and metadata. Learn more in the Procedure Contract documentation.

Keep this file free of server code. That is what lets the client import it safely later.

Implement the Contract

The implement function turns your contract into a builder that already knows every procedure’s shape. We name it os because it works just like the os builder from Getting Started, except it is locked to your contract.

import { function implement<TContract extends RouterContract, TInitialContext extends Context = DefaultInitialContext>(contract: TContract, config?: ProcedureConfig): Implementer<TContract, TInitialContext & object>
Turns a contract into an implementer, used to implement the contract's procedures, routers, and middleware with full type safety.
@see{@link https://orpc.dev/docs/contract/implementation#implementer Contract Implementation - Implementer}
implement
} from '@orpc/server'
const
const os: Implementer<{
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, DefaultInitialContext & object>
os
=
implement<{
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, DefaultInitialContext>(contract: {
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, config?: ProcedureConfig): Implementer<...>
Turns a contract into an implementer, used to implement the contract's procedures, routers, and middleware with full type safety.
@see{@link https://orpc.dev/docs/contract/implementation#implementer Contract Implementation - Implementer}
implement
(
const contract: {
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
)
export const
const listPlanets: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>>, object>
listPlanets
=
const os: Implementer<{
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, DefaultInitialContext & object>
os
.
planet: Public<SharedRouterImplementer<{
    list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ProcedureContractBuilderWithInputOutput<ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ProcedureContractBuilderWithInputOutput<...>;
}, DefaultInitialContext & object>> & {
    ...;
}
planet
.
list: ProcedureImplementer<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>>, object>
list
.
ProcedureImplementer<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{ id: ZodNumber; name: ZodString; description: ZodOptional<...>; }, $strip>>, object>['handler'](handler: ProcedureHandler<DefaultInitialContext & object, unknown, {
    id: number;
    name: string;
    description?: string | undefined;
}[] | AnyORPCError, object>): ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>>, object>
handler
(async () => {
// replace with your database query return [ { id: numberid: 1, name: stringname: 'Earth' }, { id: numberid: 2, name: stringname: 'Mars' }, ] }) export const
const findPlanet: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    id: ZodNumber;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
findPlanet
=
const os: Implementer<{
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, DefaultInitialContext & object>
os
.
planet: Public<SharedRouterImplementer<{
    list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ProcedureContractBuilderWithInputOutput<ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ProcedureContractBuilderWithInputOutput<...>;
}, DefaultInitialContext & object>> & {
    ...;
}
planet
.
find: ProcedureImplementer<DefaultInitialContext & object, object, ZodObject<{
    id: ZodNumber;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
find
.
ProcedureImplementer<DefaultInitialContext & object, object, ZodObject<{ id: ZodNumber; }, $strip>, ZodObject<{ id: ZodNumber; name: ZodString; description: ZodOptional<...>; }, $strip>, object>['handler'](handler: ProcedureHandler<DefaultInitialContext & object, {
    id: number;
}, {
    id: number;
    name: string;
    description?: string | undefined;
} | AnyORPCError, object>): ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    id: ZodNumber;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
handler
(async ({
input: {
    id: number;
}
input
}) => {
// replace with your database query return { id: numberid:
input: {
    id: number;
}
input
.id: numberid, name: "Earth"name: 'Earth' }
}) export const
const createPlanet: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
createPlanet
=
const os: Implementer<{
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, DefaultInitialContext & object>
os
.
planet: Public<SharedRouterImplementer<{
    list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ProcedureContractBuilderWithInputOutput<ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ProcedureContractBuilderWithInputOutput<...>;
}, DefaultInitialContext & object>> & {
    ...;
}
planet
.
create: ProcedureImplementer<DefaultInitialContext & object, object, ZodObject<{
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
create
.
ProcedureImplementer<DefaultInitialContext & object, object, ZodObject<{ name: ZodString; description: ZodOptional<ZodString>; }, $strip>, ZodObject<...>, object>['handler'](handler: ProcedureHandler<DefaultInitialContext & object, {
    name: string;
    description?: string | undefined;
}, {
    id: number;
    name: string;
    description?: string | undefined;
} | AnyORPCError, object>): ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
handler
(async ({
input: {
    name: string;
    description?: string | undefined;
}
input
}) => {
// replace with your database insert return { id: numberid: 3, ...
input: {
    name: string;
    description?: string | undefined;
}
input
}
}) export const
const router: {
    planet: {
        list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
=
const os: Implementer<{
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}, DefaultInitialContext & object>
os
.
router<{
    planet: {
        list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ImplementedProcedure<...>;
    };
}>(router: {
    planet: {
        list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ImplementedProcedure<...>;
    };
}): {
    planet: {
        list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
({
planet: {
    list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ImplementedProcedure<...>;
}
planet
: {
list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>>, object>
list
:
const listPlanets: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>>, object>
listPlanets
,
find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    id: ZodNumber;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
find
:
const findPlanet: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    id: ZodNumber;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
findPlanet
,
create: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
create
:
const createPlanet: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
createPlanet
,
}, })

The contract does the heavy lifting:

  • os.planet.find already knows its input and output types, so you only write the logic. Input is still validated before the handler runs.
  • Return the wrong shape from a handler and TypeScript reports an error immediately.
  • os.router checks completeness: forget to implement a procedure, or put it under the wrong key, and the code does not compile.

Implementations can use middleware and context as usual. Learn more in the Contract Implementation documentation.

Create a Server

Serving the router works exactly as in Getting Started: RPCHandler matches each request to a procedure, validates the input, runs your handler, and sends the result back.

import { function createServer<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse = typeof ServerResponse>(requestListener?: RequestListener<Request, Response>): Server<Request, Response> (+1 overload)
Returns a new instance of {@link Server } . The `requestListener` is a function which is automatically added to the `'request'` event. ```js import http from 'node:http'; // Create a local server to receive data from const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ data: 'Hello World!', })); }); server.listen(8000); ``` ```js import http from 'node:http'; // Create a local server to receive data from const server = http.createServer(); // Listen to the request event server.on('request', (request, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ data: 'Hello World!', })); }); server.listen(8000); ```
@sincev0.1.13
createServer
} from 'node:http'
import { class RPCHandler<T extends Context>
Serves an oRPC router over the RPC protocol using Node.js built-in HTTP request/response objects.
@see{@link https://orpc.dev/docs/adapters/node-http Node HTTP Adapter}
RPCHandler
} from '@orpc/server/node'
const const handler: RPCHandler<DefaultInitialContext & object>handler = new new RPCHandler<DefaultInitialContext & object>(router: Router<DefaultInitialContext & object>, options?: NoInfer<RPCHandlerOptions<DefaultInitialContext & object>>): RPCHandler<DefaultInitialContext & object>
Serves an oRPC router over the RPC protocol using Node.js built-in HTTP request/response objects.
@see{@link https://orpc.dev/docs/adapters/node-http Node HTTP Adapter}
RPCHandler
(
const router: {
    planet: {
        list: ImplementedProcedure<DefaultInitialContext & object, object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
)
const const server: Server<typeof IncomingMessage, typeof ServerResponse>server = createServer<typeof IncomingMessage, typeof ServerResponse>(requestListener?: RequestListener<typeof IncomingMessage, typeof ServerResponse> | undefined): Server<typeof IncomingMessage, typeof ServerResponse> (+1 overload)
Returns a new instance of {@link Server } . The `requestListener` is a function which is automatically added to the `'request'` event. ```js import http from 'node:http'; // Create a local server to receive data from const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ data: 'Hello World!', })); }); server.listen(8000); ``` ```js import http from 'node:http'; // Create a local server to receive data from const server = http.createServer(); // Listen to the request event server.on('request', (request, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ data: 'Hello World!', })); }); server.listen(8000); ```
@sincev0.1.13
createServer
(async (req: IncomingMessagereq,
res: ServerResponse<IncomingMessage> & {
    req: IncomingMessage;
}
res
) => {
const { const matched: booleanmatched } = await const handler: RPCHandler<DefaultInitialContext & object>handler.NodeHttpHandler<DefaultInitialContext & object>.handle(request: NodeHttpRequest, response: NodeHttpResponse, options?: FriendlyStandardHandlerHandleOptions<DefaultInitialContext & object> | undefined): Promise<NodeHttpHandlerHandleResult>handle(req: IncomingMessagereq,
res: ServerResponse<IncomingMessage> & {
    req: IncomingMessage;
}
res
, { prefix?: `/${string}` | undefinedprefix: '/rpc' })
if (const matched: booleanmatched) { return }
res: ServerResponse<IncomingMessage> & {
    req: IncomingMessage;
}
res
.ServerResponse<Request extends IncomingMessage = IncomingMessage>.statusCode: number
When using implicit headers (not calling `response.writeHead()` explicitly), this property controls the status code that will be sent to the client when the headers get flushed. ```js response.statusCode = 404; ``` After response header was sent to the client, this property indicates the status code which was sent out.
@sincev0.4.0
statusCode
= 404
res: ServerResponse<IncomingMessage> & {
    req: IncomingMessage;
}
res
.
Stream.Writable.end(chunk: any, cb?: () => void): ServerResponse<IncomingMessage> & {
    req: IncomingMessage;
} (+2 overloads)
Signals that no more data will be written, with one final chunk of data.
@see{@link Writable.end} for full details.@sincev0.9.4@paramchunk Optional data to write. For streams not operating in object mode, `chunk` must be a {string}, {Buffer}, {TypedArray} or {DataView}. For object mode streams, `chunk` may be any JavaScript value other than `null`.@paramcb Callback for when the stream is finished.
end
('Not found')
}) const server: Server<typeof IncomingMessage, typeof ServerResponse>server.Server.listen(port?: number, hostname?: string, listeningListener?: (() => void) | undefined): Server<typeof IncomingMessage, typeof ServerResponse> (+8 overloads)
Start a server listening for connections. A `net.Server` can be a TCP or an `IPC` server depending on what it listens to. Possible signatures: * `server.listen(handle[, backlog][, callback])` * `server.listen(options[, callback])` * `server.listen(path[, backlog][, callback])` for `IPC` servers * `server.listen([port[, host[, backlog]]][, callback])` for TCP servers This function is asynchronous. When the server starts listening, the `'listening'` event will be emitted. The last parameter `callback`will be added as a listener for the `'listening'` event. All `listen()` methods can take a `backlog` parameter to specify the maximum length of the queue of pending connections. The actual length will be determined by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux. The default value of this parameter is 511 (not 512). All {@link Socket } are set to `SO_REUSEADDR` (see [`socket(7)`](https://man7.org/linux/man-pages/man7/socket.7.html) for details). The `server.listen()` method can be called again if and only if there was an error during the first `server.listen()` call or `server.close()` has been called. Otherwise, an `ERR_SERVER_ALREADY_LISTEN` error will be thrown. One of the most common errors raised when listening is `EADDRINUSE`. This happens when another server is already listening on the requested`port`/`path`/`handle`. One way to handle this would be to retry after a certain amount of time: ```js server.on('error', (e) => { if (e.code === 'EADDRINUSE') { console.error('Address in use, retrying...'); setTimeout(() => { server.close(); server.listen(PORT, HOST); }, 1000); } }); ```
listen
(3000, '127.0.0.1', () => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
('Listening on 127.0.0.1:3000'))

Create a Client

Here is the payoff. Type the client with RouterContractClient<typeof contract>: it needs only the contract, which contains no business logic, so no server code can leak into the client bundle.

import type { type RouterContractClient<TRouter extends RouterContract, TClientContext extends ClientContext = object> = TRouter extends ProcedureContract<infer UInputSchema extends AnySchema, infer UOutputSchema extends AnySchema, infer UErrorMap extends ErrorMap> ? ProcedureContractClient<TClientContext, UInputSchema, UOutputSchema, UErrorMap> : { [K in keyof TRouter]: TRouter[K] extends RouterContract ? RouterContractClient<TRouter[K], TClientContext> : never; }
Client type inferred from a router contract, preserving its shape. Useful for typing a client without importing the server router.
@see{@link https://orpc.dev/docs/client/client-side Client-Side Clients}
RouterContractClient
} from '@orpc/contract'
import { function createORPCClient<T extends AnyNestedClient>(link: ClientLink<InferClientContext<T>>, { path, ...options }?: NoInfer<ORPCClientOptions<T>>): T
Creates a fully typed oRPC client from a link. The returned client mirrors the shape of your router or contract, so calling a procedure is as simple as calling a function.
@see{@link https://orpc.dev/docs/client/client-side Client-Side Clients}
createORPCClient
} from '@orpc/client'
import { class RPCLink<T extends ClientContext>
Client link that communicates with an RPC Handler over the Fetch API (HTTP).
@see{@link https://orpc.dev/docs/adapters/fetch-api Fetch API Adapter}
RPCLink
} from '@orpc/client/fetch'
const const link: RPCLink<ClientContext>link = new new RPCLink<ClientContext>(options: RPCLinkOptions<ClientContext>): RPCLink<ClientContext>
Client link that communicates with an RPC Handler over the Fetch API (HTTP).
@see{@link https://orpc.dev/docs/adapters/fetch-api Fetch API Adapter}
RPCLink
({
FetchLinkTransportOptions<ClientContext>.origin?: Value<Promisable<`https://${string}` | `http://${string}` | ({} & string) | undefined>, [options: ClientOptions<ClientContext>, path: string[]]>
The origin to prepend to all request URLs, useful for CORS requests.
@example'https://api.example.com'@example'http://localhost:3000'
origin
: 'http://127.0.0.1:3000',
RPCLinkCodecOptions<ClientContext>.url?: Value<Promisable<StandardUrl>, [options: ClientOptions<ClientContext>, path: string[], input: unknown]> | undefined
Base url for all requests (without origin). Should match with handler's prefix.
@example'/rpc?base=1'@default'/'
url
: '/rpc', // <- must match the server's prefix
}) export const
const orpc: {
    planet: {
        list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractClient<object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractClient<...>;
    };
}
orpc
: type RouterContractClient<TRouter extends RouterContract, TClientContext extends ClientContext = object> = TRouter extends ProcedureContract<infer UInputSchema extends AnySchema, infer UOutputSchema extends AnySchema, infer UErrorMap extends ErrorMap> ? ProcedureContractClient<TClientContext, UInputSchema, UOutputSchema, UErrorMap> : { [K in keyof TRouter]: TRouter[K] extends RouterContract ? RouterContractClient<TRouter[K], TClientContext> : never; }
Client type inferred from a router contract, preserving its shape. Useful for typing a client without importing the server router.
@see{@link https://orpc.dev/docs/client/client-side Client-Side Clients}
RouterContractClient
<typeof
const contract: {
    planet: {
        list: ProcedureContractBuilderWithOutput<ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
> =
createORPCClient<{
    planet: {
        list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractClient<object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractClient<...>;
    };
}>(link: ClientLink<...>, { path, ...options }?: NoInfer<ORPCClientOptions<...>>): {
    planet: {
        list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractClient<object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractClient<...>;
    };
}
Creates a fully typed oRPC client from a link. The returned client mirrors the shape of your router or contract, so calling a procedure is as simple as calling a function.
@see{@link https://orpc.dev/docs/client/client-side Client-Side Clients}
createORPCClient
(const link: RPCLink<ClientContext>link)

Call a Procedure

Calling procedures feels exactly the same as in Getting Started:

const 
const planets: {
    id: number;
    name: string;
    description?: string | undefined;
}[]
planets
= await
const orpc: {
    planet: {
        list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractClient<object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractClient<...>;
    };
}
orpc
.
planet: {
    list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ProcedureContractClient<object, ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ProcedureContractClient<...>;
}
planet
.
list: Client
(input?: void | undefined, options?: FriendlyClientOptions<object> | undefined) => PromiseWithError<{
    id: number;
    name: string;
    description?: string | undefined;
}[], Error>
list
()
const
const planet: {
    id: number;
    name: string;
    description?: string | undefined;
}
planet
= await
const orpc: {
    planet: {
        list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractClient<object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractClient<...>;
    };
}
orpc
.
planet: {
    list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ProcedureContractClient<object, ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ProcedureContractClient<...>;
}
planet
.
find: Client
(input: {
    id: number;
}, options?: FriendlyClientOptions<object> | undefined) => PromiseWithError<{
    id: number;
    name: string;
    description?: string | undefined;
}, Error>
find
({ id: numberid: 1 })
const orpc: {
    planet: {
        list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractClient<object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractClient<...>;
    };
}
orpc
.
planet: {
    list: ProcedureContractClient<object, InitialInputSchema, ZodArray<ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>>, object>;
    find: ProcedureContractClient<object, ZodObject<{
        id: ZodNumber;
    }, $strip>, ZodObject<{
        id: ZodNumber;
        name: ZodString;
        description: ZodOptional<ZodString>;
    }, $strip>, object>;
    create: ProcedureContractClient<...>;
}
planet
.
  • create
  • find
  • list
create: ProcedureContractClient<object, ZodObject<{
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, ZodObject<{
    id: ZodNumber;
    name: ZodString;
    description: ZodOptional<ZodString>;
}, $strip>, object>
create
// //

Both sides now answer to the contract. The server cannot ship a response the contract does not allow, the client cannot send input the contract rejects, and changing the contract surfaces every affected handler and call site as a compile error.

Next Steps

Last updated on August 24, 2026

Was this page helpful?