Skip to content
tRPC

AWS Lambda + API Gateway Adapter

On this page

AWS Lambda adapter

The AWS Lambda adapter is supported for API Gateway REST API(v1) and HTTP API(v2), and Lambda Function URL use cases.

httpBatchLink requires the router to work on a single API Gateway Resource (as shown in the example). If you'd like to have a Resource per procedure, you can use the httpLink instead (more info).

Example apps

Description Links
API Gateway with NodeJS client.
API Gateway REST API with response streaming.

How to add tRPC

1. Install deps

yarn add @trpc/server

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
();
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;
    }>;
}>>
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;
    }>;
}>(_: {
    getUser: QueryProcedure<{
        input: string;
        output: {
            id: string;
            name: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    getUser: QueryProcedure<{
        input: string;
        output: {
            id: string;
            name: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
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' }; }), }); // 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;
    }>;
}>> & DecorateCreateRouterOptions<{
    getUser: QueryProcedure<{
        input: string;
        output: {
            id: string;
            name: 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;
    }>;
}>>
appRouter
;

3. Use the Amazon API Gateway adapter

tRPC includes an adapter for API Gateway out of the box. This adapter lets you run your routes through the API Gateway handler.

server.ts
import type { type APIGatewayProxyEventV2 = APIGatewayProxyEventV2WithRequestContext<APIGatewayEventRequestContextV2>APIGatewayProxyEventV2 } from 'aws-lambda';
import type { 
type CreateAWSLambdaContextOptions<TEvent extends LambdaEvent> = {
    event: TEvent;
    context: Context;
    info: TRPCRequestInfo;
}
CreateAWSLambdaContextOptions
} from '@trpc/server/adapters/aws-lambda';
import { function awsLambdaRequestHandler<TRouter extends AnyRouter, TEvent extends LambdaEvent>(opts: AWSLambdaOptions<TRouter, TEvent>): (event: TEvent, context: Context) => Promise<inferAPIGWReturn<TEvent>>awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda'; import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from './router';
// created for each request const const createContext: ({ event, context, }: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => {}createContext = ({ event: APIGatewayProxyEventV2event, context: Contextcontext, }:
type CreateAWSLambdaContextOptions<TEvent extends LambdaEvent> = {
    event: TEvent;
    context: Context;
    info: TRPCRequestInfo;
}
CreateAWSLambdaContextOptions
<type APIGatewayProxyEventV2 = APIGatewayProxyEventV2WithRequestContext<APIGatewayEventRequestContextV2>APIGatewayProxyEventV2>) => ({}); // 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: ({ event, context, }: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => {}createContext>>;
export const const handler: (event: APIGatewayProxyEventV2, context: Context) => Promise<APIGatewayProxyStructuredResultV2>handler =
awsLambdaRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, APIGatewayProxyEventV2>(opts: AWSLambdaOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, APIGatewayProxyEventV2>): (event: APIGatewayProxyEventV2, context: Context) => Promise<...>
awsLambdaRequestHandler
({
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, APIGatewayProxyEventV2>.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?: AWSLambdaCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, APIGatewayProxyEventV2> | undefined
createContext
,
})

Build & deploy your code, now use your API Gateway URL to call your function.

EndpointHTTP URI
getUserGET https://<execution-api-link>/getUser?input=INPUT

where INPUT is a URI-encoded JSON string.

A word about payload format version

API Gateway has two different event data formats when it invokes a Lambda. For REST APIs they should be version "1.0"(APIGatewayProxyEvent), but you can choose which for HTTP APIs by stating either version "1.0" or "2.0".

  • Version 1.0: APIGatewayProxyEvent
  • Version 2.0: APIGatewayProxyEventV2

To infer what version you might have, supply the context as following:

function function createContext({ event, context, }: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>): voidcreateContext({
  event: APIGatewayProxyEventevent,
  context: Contextcontext,
}: 
type CreateAWSLambdaContextOptions<TEvent extends LambdaEvent> = {
    event: TEvent;
    context: Context;
    info: TRPCRequestInfo;
}
CreateAWSLambdaContextOptions
<type APIGatewayProxyEvent = APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext>APIGatewayProxyEvent>) {
// ... } // CreateAWSLambdaContextOptions<APIGatewayProxyEvent> or CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>

Read more here about payload format version

AWS Lambda Response Streaming Adapter

AWS Lambda supports streaming responses to clients with both Lambda Function URLs and API Gateway REST APIs.

Response streaming is supported for Lambda Function URLs and API Gateway REST APIs. For API Gateway REST APIs, you need to configure the integration with responseTransferMode: STREAM. Read more about Lambda response streaming and API Gateway response streaming.

Response Streaming

The signature of a streaming handler is different from the default handler. The streaming handler additionally receives a writable stream parameter, responseStream, besides the default node handler parameters, event and context. To indicate that Lambda should stream your responses, you must wrap your function handler with the awslambda.streamifyResponse() decorator.

Note that the awslambda namespace is automatically provided by the Lambda execution environment. You can import the types from @types/aws-lambda to augment the global namespace with the awslambda namespace.

server.ts
/// <reference types="aws-lambda" />
import type { type APIGatewayProxyEventV2 = APIGatewayProxyEventV2WithRequestContext<APIGatewayEventRequestContextV2>APIGatewayProxyEventV2 } from 'aws-lambda';
import type { 
type CreateAWSLambdaContextOptions<TEvent extends LambdaEvent> = {
    event: TEvent;
    context: Context;
    info: TRPCRequestInfo;
}
CreateAWSLambdaContextOptions
} from '@trpc/server/adapters/aws-lambda';
import { function awsLambdaStreamingRequestHandler<TRouter extends AnyRouter, TEvent extends LambdaEvent>(opts: AWSLambdaOptions<TRouter, TEvent>): StreamifyHandler<TEvent>awsLambdaStreamingRequestHandler } from '@trpc/server/adapters/aws-lambda'; import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    iterable: QueryProcedure<{
        input: void;
        output: AsyncGenerator<number, void, unknown>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
} from './router';
// created for each request const const createContext: ({ event, context, }: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => {}createContext = ({ event: APIGatewayProxyEventV2event, context: Contextcontext, }:
type CreateAWSLambdaContextOptions<TEvent extends LambdaEvent> = {
    event: TEvent;
    context: Context;
    info: TRPCRequestInfo;
}
CreateAWSLambdaContextOptions
<type APIGatewayProxyEventV2 = APIGatewayProxyEventV2WithRequestContext<APIGatewayEventRequestContextV2>APIGatewayProxyEventV2>) => ({
// your 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: ({ event, context, }: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => {}createContext>>;
export const const handler: StreamifyHandler<APIGatewayProxyEventV2, any>handler = awslambda.function awslambda.streamifyResponse<APIGatewayProxyEventV2, any>(handler: StreamifyHandler<APIGatewayProxyEventV2, any>): StreamifyHandler<APIGatewayProxyEventV2, any>streamifyResponse(
awsLambdaStreamingRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    iterable: QueryProcedure<{
        input: void;
        output: AsyncGenerator<number, void, unknown>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, APIGatewayProxyEventV2>(opts: AWSLambdaOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    iterable: QueryProcedure<{
        input: void;
        output: AsyncGenerator<number, void, unknown>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, APIGatewayProxyEventV2>): StreamifyHandler<...>
awsLambdaStreamingRequestHandler
({
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ iterable: QueryProcedure<{ input: void; output: AsyncGenerator<number, void, unknown>; meta: object; errorShape: DefaultErrorShape; }>; }>>, APIGatewayProxyEventV2>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    iterable: QueryProcedure<{
        input: void;
        output: AsyncGenerator<number, void, unknown>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    iterable: QueryProcedure<{
        input: void;
        output: AsyncGenerator<number, void, unknown>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
,
createContext?: AWSLambdaCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    iterable: QueryProcedure<{
        input: void;
        output: AsyncGenerator<number, void, unknown>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, APIGatewayProxyEventV2> | undefined
createContext
,
}), );