Skip to content
tRPC

Fastify Adapter

On this page

Example app

The best way to start with the Fastify adapter is to take a look at the example application.

Description Links
  • Fastify server with WebSocket
  • Simple tRPC client in node

How to use tRPC with Fastify

Install dependencies

yarn add @trpc/server fastify zod

⚠️ Fastify version requirement

The tRPC v11 Fastify adapter requires Fastify v5+. Using Fastify v4 may cause requests to return empty responses without errors.

Zod isn't a required dependency, but it's used in the sample router below.

Create the router

First of all you need a router to handle your queries, mutations and subscriptions.

A sample router is given below, save it in a file named router.ts.

router.ts
router.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import { import zz } from 'zod';

type 
type User = {
    id: string;
    name: string;
    bio?: string;
}
User
= {
id: stringid: string; name: stringname: string; bio?: string | undefinedbio?: string; }; const const users: Record<string, User>users: type Record<K extends keyof any, T> = { [P in K]: T; }Record<string,
type User = {
    id: string;
    name: string;
    bio?: string;
}
User
> = {};
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<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        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
<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<...>
router
({
getUserById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
getUserById
:
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<User>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((opts: ProcedureResolverOptions<object, object, object, string>opts) => {
return const users: Record<string, User>users[opts: ProcedureResolverOptions<object, object, object, string>opts.ProcedureResolverOptions<object, object, object, string>.input: stringinput]; // input type is string }),
createUser: MutationProcedure<{
    input: {
        name: string;
        bio?: string | undefined;
    };
    output: User;
    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;
    bio: z.ZodOptional<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
    name: string;
    bio?: string | undefined;
}, {
    name: string;
    bio?: string | undefined;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(
import zz.
function object<{
    name: z.ZodString;
    bio: z.ZodOptional<z.ZodString>;
}>(shape?: {
    name: z.ZodString;
    bio: z.ZodOptional<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;
    bio: 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()._ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodStringmin(3), bio: z.ZodOptional<z.ZodString>bio: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.max(maxLength: number, params?: string | z.core.$ZodCheckMaxLengthParams): z.ZodStringmax(142).ZodType<any, any, $ZodStringInternals<string>>.optional(): z.ZodOptional<z.ZodString>optional(), }), ) .
ProcedureBuilder<object, object, object, { name: string; bio?: string | undefined; }, { name: string; bio?: string | undefined; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<User>(resolver: ProcedureResolver<object, object, object, {
    name: string;
    bio?: string | undefined;
}, UnsetMarker, User>): MutationProcedure<{
    input: {
        name: string;
        bio?: string | undefined;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
mutation
((
opts: ProcedureResolverOptions<object, object, object, {
    name: string;
    bio?: string | undefined;
}>
opts
) => {
const const id: stringid = var Date: DateConstructorDate.DateConstructor.now(): numbernow().Number.toString(radix?: number): stringtoString(); const const user: Useruser:
type User = {
    id: string;
    name: string;
    bio?: string;
}
User
= { id: stringid, ...
opts: ProcedureResolverOptions<object, object, object, {
    name: string;
    bio?: string | undefined;
}>
opts
.
ProcedureResolverOptions<object, object, object, { name: string; bio?: string | undefined; }>.input: {
    name: string;
    bio?: string | undefined;
}
input
};
const users: Record<string, User>users[const user: Useruser.id: stringid] = const user: Useruser; return const user: Useruser; }), }); // export type definition of API export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    getUserById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    createUser: MutationProcedure<{
        input: {
            name: string;
            bio?: string | undefined;
        };
        output: User;
        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.

Create the context

Then you need a context that will be created for each request.

A sample context is given below, save it in a file named context.ts:

context.ts
context.ts
import { 
type CreateFastifyContextOptions = {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<RouteGenericInterface, ... 6 more ..., unknown>;
    info: TRPCRequestInfo;
}
CreateFastifyContextOptions
} from '@trpc/server/adapters/fastify';
export function
function createContext({ req, res }: CreateFastifyContextOptions): {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<...>;
    user: {
        ...;
    };
}
createContext
({ req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>req, res: FastifyReply<RouteGenericInterface, RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, unknown, FastifySchema, FastifyTypeProviderDefault, unknown>res }:
type CreateFastifyContextOptions = {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<RouteGenericInterface, ... 6 more ..., unknown>;
    info: TRPCRequestInfo;
}
CreateFastifyContextOptions
) {
const
const user: {
    name: string | string[];
}
user
= { name: string | string[]name: req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>req.FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<...>>.headers: IncomingHttpHeadersheaders.string | string[] | undefinedusername ?? 'anonymous' };
return { req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>req, res: FastifyReply<RouteGenericInterface, RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, unknown, FastifySchema, FastifyTypeProviderDefault, unknown>res,
user: {
    name: string | string[];
}
user
};
} export type
type Context = {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<RouteGenericInterface, ... 6 more ..., unknown>;
    user: {
        ...;
    };
}
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
function createContext({ req, res }: CreateFastifyContextOptions): {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<...>;
    user: {
        ...;
    };
}
createContext
>>;

Create Fastify server

tRPC includes an adapter for Fastify out of the box. This adapter lets you convert your tRPC router into a Fastify plugin. In order to prevent errors during large batch requests, make sure to set the maxParamLength Fastify option to a suitable value, as shown.

server.ts
import {
  function fastifyTRPCPlugin<TRouter extends AnyRouter>(fastify: FastifyInstance, opts: FastifyTRPCPluginOptions<TRouter>, done: (err?: Error) => void): voidfastifyTRPCPlugin,
  interface FastifyTRPCPluginOptions<TRouter extends AnyRouter>FastifyTRPCPluginOptions,
} from '@trpc/server/adapters/fastify';
import const fastify: typeof fastifyfastify from 'fastify';
import { 
function createContext({ req, res }: CreateFastifyContextOptions): {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<...>;
    user: {
        ...;
    };
}
createContext
} from './context';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
, type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from './router';
const
const server: FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>> & {
    ...;
}
server
=
fastify<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>(opts?: fastify.FastifyHttpOptions<Server<typeof IncomingMessage, typeof ServerResponse>, FastifyBaseLogger> | undefined): FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<...> & {
    ...;
} (+3 overloads)
fastify
({
routerOptions?: fastify.FastifyRouterOptions<Server<typeof IncomingMessage, typeof ServerResponse>> | undefinedrouterOptions: { maxParamLength?: number | undefinedmaxParamLength: 5000, }, });
const server: FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>> & {
    ...;
}
server
.
FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>.register: FastifyRegister
<{
    prefix: string;
    trpcOptions: {
        router: BuiltRouter<{
            ctx: object;
            meta: object;
            errorShape: DefaultErrorShape;
            transformer: false;
        }, DecorateCreateRouterOptions<{}>>;
        createContext: ({ req, res }: CreateFastifyContextOptions) => {
            req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<...>>;
            res: FastifyReply<...>;
            user: {
                ...;
            };
        };
        onError({ path, error }: HTTPErrorHandlerOptions<...>): void;
    };
}, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>(plugin: FastifyPluginCallback<...>, opts: FastifyRegisterOptions<...>) => FastifyInstance<...> & ... 1 more ... & {
    ...;
} (+5 overloads)
register
(function fastifyTRPCPlugin<TRouter extends AnyRouter>(fastify: FastifyInstance, opts: FastifyTRPCPluginOptions<TRouter>, done: (err?: Error) => void): voidfastifyTRPCPlugin, {
prefix: stringprefix: '/trpc',
trpcOptions: {
    router: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>;
    createContext: ({ req, res }: CreateFastifyContextOptions) => {
        req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<...>>;
        res: FastifyReply<...>;
        user: {
            ...;
        };
    };
    onError({ path, error }: HTTPErrorHandlerOptions<...>): void;
}
trpcOptions
: {
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, FastifyRequest<...>>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext?: NodeHTTPCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>, FastifyReply<...>> | undefined
createContext
,
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, FastifyRequest<...>>.onError?: HTTPErrorHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>> | undefined
onError
({ path: string | undefinedpath, error: TRPCErrorerror }) {
// report to error monitoring var console: Consoleconsole.Console.error(...data: any[]): voiderror(`Error in tRPC handler on path '${path: string | undefinedpath}':`, error: TRPCErrorerror); }, } satisfies interface FastifyTRPCPluginOptions<TRouter extends AnyRouter>FastifyTRPCPluginOptions<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>['trpcOptions'],
}); (async () => { try { await
const server: FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>> & {
    ...;
}
server
.FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>.listen(opts?: FastifyListenOptions): Promise<string> (+2 overloads)listen({ FastifyListenOptions.port?: number | undefinedport: 3000 });
} catch (function (local var) err: unknownerr) {
const server: FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>> & {
    ...;
}
server
.FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>.log: FastifyBaseLoggerlog.
error: pino.LogFn
<unknown, string>(obj: unknown, msg?: string | undefined) => void (+2 overloads)
error
(function (local var) err: unknownerr);
var process: NodeJS.Processprocess.NodeJS.Process.exit(code?: number | string | null): neverexit(1); } })();

Your endpoints are now available via HTTP!

EndpointHTTP URI
getUserByIdGET http://localhost:3000/trpc/getUserById?input=INPUT

where INPUT is a URI-encoded JSON string.
createUserPOST http://localhost:3000/trpc/createUser

with req.body of type User

Enable WebSockets

The Fastify adapter supports WebSockets via the @fastify/websocket plugin. All you have to do in addition to the above steps is install the dependency, add some subscriptions to your router, and activate the useWSS option in the plugin. The minimum @fastify/websocket version required is 3.11.0.

Install dependencies

yarn add @fastify/websocket

Import and register @fastify/websocket

import const ws: anyws from '@fastify/websocket';

const server: anyserver.register(const ws: anyws);

Add some subscriptions

Edit the router.ts file created in the previous steps and add the following code:

router.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';

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<{
    randomNumber: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<{
            randomNumber: number;
        }, never, any>;
        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
<{
    randomNumber: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<{
            randomNumber: number;
        }, never, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    randomNumber: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<{
            randomNumber: number;
        }, never, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    randomNumber: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<{
            randomNumber: number;
        }, never, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
randomNumber: SubscriptionProcedure<{
    input: void;
    output: AsyncIterable<{
        randomNumber: number;
    }, never, any>;
    meta: object;
    errorShape: DefaultErrorShape;
}>
randomNumber
:
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>.subscription<AsyncGenerator<{
    randomNumber: number;
}, never, any>>(resolver: ProcedureResolver<object, object, object, UnsetMarker, UnsetMarker, AsyncGenerator<{
    randomNumber: number;
}, never, any>>): SubscriptionProcedure<{
    input: void;
    output: AsyncIterable<{
        randomNumber: number;
    }, never, any>;
    meta: object;
    errorShape: DefaultErrorShape;
}> (+1 overload)
subscription
(async function* () {
while (true) { yield { randomNumber: numberrandomNumber: var Math: MathMath.Math.random(): numberrandom() }; await new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
Promise
((resolve: (value: unknown) => voidresolve) => function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): numbersetTimeout(resolve: (value: unknown) => voidresolve, 1000));
} }), });

Activate the useWSS option

server.ts
import {
  function fastifyTRPCPlugin<TRouter extends AnyRouter>(fastify: FastifyInstance, opts: FastifyTRPCPluginOptions<TRouter>, done: (err?: Error) => void): voidfastifyTRPCPlugin,
  interface FastifyTRPCPluginOptions<TRouter extends AnyRouter>FastifyTRPCPluginOptions,
} from '@trpc/server/adapters/fastify';
import const fastify: typeof fastifyfastify from 'fastify';
import { 
function createContext({ req, res }: CreateFastifyContextOptions): {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<...>;
}
createContext
} from './context';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
, type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from './router';
const
const server: FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>> & {
    ...;
}
server
=
fastify<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>(opts?: fastify.FastifyHttpOptions<Server<typeof IncomingMessage, typeof ServerResponse>, FastifyBaseLogger> | undefined): FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<...> & {
    ...;
} (+3 overloads)
fastify
();
const server: FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault> & PromiseLike<FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>> & {
    ...;
}
server
.
FastifyInstance<Server<typeof IncomingMessage, typeof ServerResponse>, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>.register: FastifyRegister
<{
    useWSS: boolean;
    trpcOptions: {
        router: BuiltRouter<{
            ctx: object;
            meta: object;
            errorShape: DefaultErrorShape;
            transformer: false;
        }, DecorateCreateRouterOptions<{}>>;
        createContext: ({ req, res }: CreateFastifyContextOptions) => {
            req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<...>>;
            res: FastifyReply<...>;
        };
        keepAlive: {
            ...;
        };
    };
}, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>(plugin: FastifyPluginCallback<...>, opts: FastifyRegisterOptions<...>) => FastifyInstance<...> & ... 1 more ... & {
    ...;
} (+5 overloads)
register
(function fastifyTRPCPlugin<TRouter extends AnyRouter>(fastify: FastifyInstance, opts: FastifyTRPCPluginOptions<TRouter>, done: (err?: Error) => void): voidfastifyTRPCPlugin, {
useWSS: booleanuseWSS: true,
trpcOptions: {
    router: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>;
    createContext: ({ req, res }: CreateFastifyContextOptions) => {
        req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<...>>;
        res: FastifyReply<...>;
    };
    keepAlive: {
        ...;
    };
}
trpcOptions
: {
router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext: ({ req, res }: CreateFastifyContextOptions) => {
    req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage, FastifySchema, FastifyTypeProviderDefault, unknown, FastifyBaseLogger, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>>;
    res: FastifyReply<...>;
}
createContext
,
// Enable heartbeat messages to keep connection open (disabled by default)
keepAlive: {
    enabled: boolean;
    pingMs: number;
    pongWaitMs: number;
}
keepAlive
: {
enabled: booleanenabled: true, // server ping message interval in milliseconds pingMs: numberpingMs: 30000, // connection is terminated if pong message is not received in this many milliseconds pongWaitMs: numberpongWaitMs: 5000, }, }, });

You can now subscribe to the randomNumber topic and should receive a random number every second 🚀.

Fastify plugin options

nametypeoptionaldefaultdescription
prefixstringtrue"/trpc"URL prefix for tRPC routes
useWSSbooleantruefalseEnable WebSocket support via @fastify/websocket
trpcOptionsFastifyHandlerOptions<AppRouter, Request, Reply>falsen/atRPC handler options including router, createContext, etc.