Skip to content
tRPC

Responses

Set headers and status from a resolver, or return a response of your own.

On this page

response sets the call's headers and status; Effect code uses the Response service over the same handle:

import { const Response: ResponseServiceResponse } from '@trpc12/trpc/server';
import { import EffectEffect } from 'effect';

const 
const login: BuildProcedure<RootDef<Context, {}>, "mutation", {
    output: void;
    errors: never;
    services: never;
    unmapped: never;
}>
login
= const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.
ProcedureBuilder<RootDef<Context, {}>>.mutation<void>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => void): BuildProcedure<RootDef<Context, {}>, "mutation", {
    output: void;
    errors: never;
    services: never;
    unmapped: never;
}>
mutation
((opts: ResolverOpts<RootDef<Context, {}>>opts) => {
opts: ResolverOpts<RootDef<Context, {}>>opts.ResolverOpts<RootDef<Context, {}>>.response: ResponseHandleresponse.ResponseHandle.headers: Headersheaders.Headers.append(name: string, value: string): voidappend('set-cookie', 'session=…; HttpOnly'); opts: ResolverOpts<RootDef<Context, {}>>opts.ResolverOpts<RootDef<Context, {}>>.response: ResponseHandleresponse.ResponseHandle.status: number | undefinedstatus = 201; }); const
const list: BuildProcedure<RootDef<Context, {}>, "query", {
    output: Post[];
    errors: never;
    services: Response;
    unmapped: never;
}>
list
= const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.
ProcedureBuilder<RootDef<Context, {}>>.query<Effect.Effect<Post[], never, Response>>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => Effect.Effect<Post[], never, Response>): BuildProcedure<RootDef<Context, {}>, "query", {
    output: Post[];
    errors: never;
    services: Response;
    unmapped: never;
}>
query
(() =>
import EffectEffect.const gen: <Effect.Effect<void, never, Response>, Post[]>(f: () => Generator<Effect.Effect<void, never, Response>, Post[], never>) => Effect.Effect<Post[], never, Response> (+1 overload)gen(function* () { yield* const Response: ResponseServiceResponse.ResponseService.setHeader(name: string, value: string): Effect.Effect<void, never, Response>setHeader('cache-control', 'max-age=60'); return
const db: {
    post: {
        find: (id: string) => Post | undefined;
        list: () => Post[];
        create: (input: {
            title: string;
            author?: string | null;
        }) => Post;
    };
}
db
.
post: {
    find: (id: string) => Post | undefined;
    list: () => Post[];
    create: (input: {
        title: string;
        author?: string | null;
    }) => Post;
}
post
.list: () => Post[]list();
}), );

In a batch, headers merge (set-cookie appends) and each call keeps its own status.

Returning a response

A procedure can also return its own response, an HttpServerResponse from effect/http or a web Response, for redirects, proxied upstreams or downloads. It's sent as is, with the handle's headers added, and the client's output is the web Response:

const 
const report: BuildProcedure<RootDef<Context, {}>, "query", {
    output: HttpServerResponse.HttpServerResponse;
    errors: never;
    services: never;
    unmapped: never;
}>
report
= const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.
ProcedureBuilder<RootDef<Context, {}>>.query<HttpServerResponse.HttpServerResponse>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => HttpServerResponse.HttpServerResponse): BuildProcedure<RootDef<Context, {}>, "query", {
    output: HttpServerResponse.HttpServerResponse;
    errors: never;
    services: never;
    unmapped: never;
}>
query
(() =>
import HttpServerResponseHttpServerResponse.const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponsetext(function toCsv(rows: string[][]): stringtoCsv(const rows: string[][]rows), { contentType?: string | undefinedcontentType: 'text/csv' }), ); const const res: Responseres = await
const client: TrpcClient<{
    report: typeof report;
}, {}, undefined, LinksTypes<readonly [TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]>>
client
.
report: {
    query(input?: undefined, opts?: CallOptions<{}> | undefined): TrpcPromise<Response, UnexpectedError>;
}
report
.function query(input?: undefined, opts?: CallOptions<{}> | undefined): TrpcPromise<Response, UnexpectedError>query(); // Response

Only an unbatched HTTP call can carry one: in a batch, over WebSockets or in-process, the call fails with BAD_REQUEST. With httpLink({ batch: true }), the client's types require { batch: false } on calls to procedures that may return a response, and the link sends them alone: client.report.query(undefined, { batch: false }).

Reading the response on the client

httpLink hands each call's response to an onResponse call option ({ status, headers }). A batched call gets its own line's status and the batch's headers (Custom links).