Once you have a contract defined, use the implement() function from @orpc/server to create a typed implementer that enforces your implementation matches the contract.
implement(contract)
import { implement } from '@orpc/server'
import { contract } from './contract' // your @orpc/contract router
const impl = implement(contract)
The returned impl object mirrors the contract structure. For each procedure in the contract, you get a builder that only exposes the methods that don’t change the contract (no .input(), .output(), or .errors() on leaf procedures — those are fixed by the contract).
Implementing procedures
import { implement } from '@orpc/server'
import { contract } from './contract'
const impl = implement(contract)
export const router = impl.router({
planet: {
list: impl.planet.list.handler(async ({ input }) => {
// input type is inferred from the contract
return [{ id: 1, name: 'Earth' }]
}),
find: impl.planet.find.handler(async ({ input, errors }) => {
const planet = await db.findById(input.id)
if (!planet) throw errors.NOT_FOUND() // typed from contract
return planet
}),
create: impl.planet.create.handler(async ({ input }) => {
return { id: 2, name: input.name }
}),
},
})
TypeScript will error if:
- A procedure is missing from the implementation.
- The handler return type doesn’t match the contract output schema.
- An unknown error code is thrown.
Setting context
Use .$context<T>() on the implementer to declare what the HTTP adapter must provide:
const impl = implement(contract).$context<{
headers: Record<string, string | undefined>
}>()
Adding middleware
The implementer supports .use() just like the os builder:
const authedImpl = implement(contract)
.$context<{ headers: Record<string, string | undefined> }>()
.use(async ({ context, next }) => {
const user = await getUserFromHeaders(context.headers)
if (!user) throw new ORPCError('UNAUTHORIZED')
return next({ context: { user } })
})
export const router = authedImpl.router({
planet: {
create: authedImpl.planet.create.handler(async ({ input, context }) => {
return { id: 2, name: input.name, createdBy: context.user.id }
}),
},
})
Sharing the contract type with clients
The contract type can be used on the client side for full type safety, without importing any server code:
// client.ts
import type { RouterClient } from '@orpc/server'
import type { contract } from './contract' // only the type, not runtime code
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
const link = new RPCLink({ url: 'http://localhost:3000' })
export const orpc: RouterClient<typeof contract> = createORPCClient(link)
// orpc.planet.list({ limit: 10 }) -- fully typed
Publish @orpc/contract routes as a separate npm package so frontend teams can import the contract without taking a dependency on backend server code.