Skip to content
tRPC

Response Caching

On this page

Since all tRPC queries are normal HTTP GET requests, you can use standard HTTP cache headers to cache responses. This can make responses snappy, give your database a rest, and help scale your API.

Using responseMeta to cache responses

Most tRPC adapters support a responseMeta callback that lets you set HTTP headers (including cache headers) based on the procedures being called.

This works with any hosting provider that supports standard HTTP cache headers (e.g. Vercel, Cloudflare, AWS CloudFront).

server.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import { function createHTTPServer<TRouter extends AnyRouter>(opts: CreateHTTPHandlerOptions<TRouter>): http.Server<any, any>createHTTPServer } from '@trpc/server/adapters/standalone';
import type { 
type CreateHTTPContextOptions = {
    req: http.IncomingMessage;
    res: http.ServerResponse;
    info: TRPCRequestInfo;
}
CreateHTTPContextOptions
} from '@trpc/server/adapters/standalone';
export const
const createContext: (opts: CreateHTTPContextOptions) => Promise<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}>
createContext
= async (opts: CreateHTTPContextOptionsopts:
type CreateHTTPContextOptions = {
    req: http.IncomingMessage;
    res: http.ServerResponse;
    info: TRPCRequestInfo;
}
CreateHTTPContextOptions
) => {
return { req: http.IncomingMessagereq: opts: CreateHTTPContextOptionsopts.req: http.IncomingMessagereq, res: http.ServerResponseres: opts: CreateHTTPContextOptionsopts.res: http.ServerResponseres, }; }; type
type Context = {
    req: http.IncomingMessage;
    res: http.ServerResponse;
}
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: (opts: CreateHTTPContextOptions) => Promise<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}>
createContext
>>;
export const
const t: TRPCRootObject<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>, {
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.context<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}>(): TRPCBuilder<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>
context
<
type Context = {
    req: http.IncomingMessage;
    res: http.ServerResponse;
}
Context
>().
TRPCBuilder<{ req: http.IncomingMessage; res: http.ServerResponse; }, object>.create<RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>>(opts?: RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object> | undefined): TRPCRootObject<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>, {
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
const const waitFor: (ms: number) => Promise<unknown>waitFor = async (ms: numberms: number) => 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, ms: numberms));
export const
const appRouter: BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
=
const t: TRPCRootObject<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>, {
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: http.IncomingMessage; res: http.ServerResponse; }, object, RuntimeConfigOptions<{ req: http.IncomingMessage; res: http.ServerResponse; }, object>, { ctx: { req: http.IncomingMessage; res: http.ServerResponse; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>(_: {
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}) => BuiltRouter<...>
router
({
public: BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
public
:
const t: TRPCRootObject<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>, {
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: http.IncomingMessage; res: http.ServerResponse; }, object, RuntimeConfigOptions<{ req: http.IncomingMessage; res: http.ServerResponse; }, object>, { ctx: { req: http.IncomingMessage; res: http.ServerResponse; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
slowQueryCached: QueryProcedure<{
    input: void;
    output: {
        lastUpdated: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
slowQueryCached
:
const t: TRPCRootObject<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, RuntimeConfigOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object>, {
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: http.IncomingMessage; res: http.ServerResponse; }, object, RuntimeConfigOptions<{ req: http.IncomingMessage; res: http.ServerResponse; }, object>, { ctx: { req: http.IncomingMessage; res: http.ServerResponse; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.
ProcedureBuilder<{ req: http.IncomingMessage; res: http.ServerResponse; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    lastUpdated: string;
}>(resolver: ProcedureResolver<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, object, UnsetMarker, UnsetMarker, {
    lastUpdated: string;
}>): QueryProcedure<{
    input: void;
    output: {
        lastUpdated: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (
opts: ProcedureResolverOptions<{
    req: http.IncomingMessage;
    res: http.ServerResponse;
}, object, object, UnsetMarker>
opts
) => {
await const waitFor: (ms: number) => Promise<unknown>waitFor(5000); // wait for 5s return { lastUpdated: stringlastUpdated: new
var Date: DateConstructor
new () => Date (+3 overloads)
Date
().Date.toJSON(key?: any): stringtoJSON(),
}; }), }), }); // Exporting `type AppRouter` only exposes types that can be used for inference // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export export type
type AppRouter = Router<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
;
// export API handler const const server: http.Server<any, any>server =
createHTTPServer<BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>(opts: CreateHTTPHandlerOptions<...>): http.Server<...>
createHTTPServer
({
BaseHandlerOptions<BuiltRouter<{ ctx: { req: http.IncomingMessage; res: http.ServerResponse; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<...>>, http.IncomingMessage>.router: BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
,
createContext: NodeHTTPCreateContextFn<BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, http.IncomingMessage, http.ServerResponse>
createContext
,
HTTPBaseHandlerOptions<BuiltRouter<{ ctx: { req: http.IncomingMessage; res: http.ServerResponse; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<...>>, http.IncomingMessage>.responseMeta?: ResponseMetaFn<BuiltRouter<{
    ctx: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: http.IncomingMessage;
            res: http.ServerResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>> | undefined
responseMeta
(
opts: {
    data: TRPCResponse<unknown, DefaultErrorShape>[];
    ctx?: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    } | undefined;
    paths: readonly string[] | undefined;
    info: TRPCRequestInfo | undefined;
    type: ProcedureType | "unknown";
    errors: TRPCError[];
    eagerGeneration: boolean;
}
opts
) {
const { const paths: readonly string[] | undefinedpaths, const errors: TRPCError[]errors, const type: "query" | "mutation" | "subscription" | "unknown"type } =
opts: {
    data: TRPCResponse<unknown, DefaultErrorShape>[];
    ctx?: {
        req: http.IncomingMessage;
        res: http.ServerResponse;
    } | undefined;
    paths: readonly string[] | undefined;
    info: TRPCRequestInfo | undefined;
    type: ProcedureType | "unknown";
    errors: TRPCError[];
    eagerGeneration: boolean;
}
opts
;
// assuming you have all your public routes with the keyword `public` in them const const allPublic: boolean | undefinedallPublic = const paths: readonly string[] | undefinedpaths && const paths: readonly string[]paths.ReadonlyArray<string>.every(predicate: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): boolean (+1 overload)every((path: stringpath) => path: stringpath.String.includes(searchString: string, position?: number): booleanincludes('public')); // checking that no procedures errored const const allOk: booleanallOk = const errors: TRPCError[]errors.Array<T>.length: numberlength === 0; // checking we're doing a query request const const isQuery: booleanisQuery = const type: "query" | "mutation" | "subscription" | "unknown"type === 'query'; if (const allPublic: boolean | undefinedallPublic && const allOk: booleanallOk && const isQuery: booleanisQuery) { // cache request for 1 day + revalidate once every second const const ONE_DAY_IN_SECONDS: numberONE_DAY_IN_SECONDS = 60 * 60 * 24; return { ResponseMeta.headers?: Headers | HTTPHeaders | undefinedheaders: new var Headers: new (init?: HeadersInit) => HeadersHeaders([ [ 'cache-control', `s-maxage=1, stale-while-revalidate=${const ONE_DAY_IN_SECONDS: numberONE_DAY_IN_SECONDS}`, ], ]), }; } return {}; }, }); const server: http.Server<any, any>server.listen(3000);