Express Adapter
On this page
Example app
| Description | Links |
|---|---|
| Express server & procedure calls with Node.js. |
How to add tRPC to existing Express project
1. Install deps
yarn add @trpc/server zodZod isn't a required dependency, but it's used in the sample router below.
2. Create a tRPC router
Implement your tRPC router. A sample router is given below:
server.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import { import zz } from 'zod';
export const const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<...>
router({
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
getUser: const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodString>(schema: z.ZodString): ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()).ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
id: string;
name: string;
}>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, {
id: string;
name: string;
}>): QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<object, object, object, string>opts) => {
opts: ProcedureResolverOptions<object, object, object, string>opts.ProcedureResolverOptions<object, object, object, string>.input: stringinput; // string
return { id: stringid: opts: ProcedureResolverOptions<object, object, object, string>opts.ProcedureResolverOptions<object, object, object, string>.input: stringinput, name: stringname: 'Bilbo' };
}),
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
createUser: const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
name: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
name: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
name: string;
}, {
name: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(import zz.function object<{
name: z.ZodString;
}>(shape?: {
name: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
name: z.ZodString;
}, z.core.$strip>
object({ name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodStringmin(5) }))
.ProcedureBuilder<object, object, object, { name: string; }, { name: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<{
name: string;
id: string;
}>(resolver: ProcedureResolver<object, object, object, {
name: string;
}, UnsetMarker, {
name: string;
id: string;
}>): MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
mutation(async (opts: ProcedureResolverOptions<object, object, object, {
name: string;
}>
opts) => {
// use your ORM of choice
return { id: stringid: '1', ...opts: ProcedureResolverOptions<object, object, object, {
name: string;
}>
opts.ProcedureResolverOptions<object, object, object, { name: string; }>.input: {
name: string;
}
input };
}),
});
// export type definition of API
export type type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>> & DecorateCreateRouterOptions<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>
AppRouter = typeof const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter;If your router file starts getting too big, split your router into several subrouters each implemented in its own file. Then merge them into a single root appRouter.
3. Use the Express adapter
tRPC includes an adapter for Express out of the box. This adapter lets you convert your tRPC router into an Express middleware.
server.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import * as import trpcExpresstrpcExpress from '@trpc/server/adapters/express';
import function express(): Expressexpress from 'express';
// created for each request
const const createContext: ({ req, res, }: trpcExpress.CreateExpressContextOptions) => {}createContext = ({
req: express.Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>req,
res: express.Response<any, Record<string, any>>res,
}: import trpcExpresstrpcExpress.type CreateExpressContextOptions = {
req: express.Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>;
res: express.Response<any, Record<string, any>>;
info: TRPCRequestInfo;
}
CreateExpressContextOptions) => ({}); // no context
type type Context = {}Context = type Awaited<T> = T extends null | undefined ? T : T extends object & {
then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Awaited<type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyReturnType<typeof const createContext: ({ req, res, }: trpcExpress.CreateExpressContextOptions) => {}createContext>>;
const const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<{}>(): TRPCBuilder<{}, object>context<type Context = {}Context>().TRPCBuilder<{}, object>.create<RuntimeConfigOptions<{}, object>>(opts?: RuntimeConfigOptions<{}, object> | undefined): TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
const const appRouter: BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter = const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, { ctx: {}; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{}>(_: {}) => BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>
router({
// [...]
});
const const app: Expressapp = function express(): Expressexpress();
const app: Expressapp.Application<Record<string, any>>.use: <"/trpc", ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>(path: "/trpc", ...handlers: RequestHandler<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>[]) => Express (+8 overloads)use(
'/trpc',
import trpcExpresstrpcExpress.function createExpressMiddleware<BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: NodeHTTPHandlerOptions<BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>, express.Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>, express.Response<any, Record<string, any>>>): express.Handler
createExpressMiddleware({
BaseHandlerOptions<BuiltRouter<{ ctx: {}; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, e.Request<...>>.router: BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>
router: const appRouter: BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter,
createContext?: NodeHTTPCreateContextFn<BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>, express.Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>, express.Response<any, Record<string, any>>> | undefined
createContext,
}),
);
const app: Expressapp.Application<Record<string, any>>.listen(port: number, callback?: (error?: Error) => void): Server (+5 overloads)listen(4000);Your endpoints are now available via HTTP!
| Endpoint | HTTP URI |
|---|---|
getUser | GET http://localhost:4000/trpc/getUser?input=INPUT where INPUT is a URI-encoded JSON string. |
createUser | POST http://localhost:4000/trpc/createUser with req.body of type {name: string} |