ferdy
ferdy16mo ago

trpc + AWS Lambda (through cdk)

Hi all, has anyone successfully integrated tRPC with AWS Lambda? My current stack is API Gateway + Lambda, all created through cdk. I am trying to figure out how to hook up the tRPC router and client to the lambda code. This is my first time using tRPC 😅
36 Replies
Nick
Nick16mo ago
Have you looked at the Lambda Adapter and its docs?
ferdy
ferdy16mo ago
yeah, I have been trying to figure out how to make that sample code work. But it doesn't show me how to fit that code into the surrounding cdk infrastructure. I see that they export a handler:
export const handler = awsLambdaRequestHandler(...)
export const handler = awsLambdaRequestHandler(...)
but when I try to reference that handler from cdk it's not quite working:
const testLambda = new aws_lambda.Function(this, "testLambda", {
handler:("/path/to/ApiEntryPoint.handler"),
...
const testLambda = new aws_lambda.Function(this, "testLambda", {
handler:("/path/to/ApiEntryPoint.handler"),
...
Lois
Lois16mo ago
remember to add AWS extension in your VSCode the prompt + autocomplete is good
Nick
Nick16mo ago
So exporting a handler is a standard lambda thing, that’s the boundary of your application At that point you’re looking at CDK docs to integrate, though honestly I haven’t used CDK. For my own local dev I just use a standalone trpc adapter, and the lambda adapter is just for AWS deployments
ferdy
ferdy16mo ago
ok I got it mostly figured out -- for any other wayward souls with this issue, check out this github repo that really helped me out: https://github.com/jacksonludwig/trpc-repro
GitHub
GitHub - jacksonludwig/trpc-repro
Contribute to jacksonludwig/trpc-repro development by creating an account on GitHub.
ferdy
ferdy16mo ago
@Nick Lucas thanks for the help. I have one more question: when creating my client typescript keeps complaining to me that I am not providing a transformer, and I don't really know what I am supposed to pass in for that. The sample code I linked above doesn't seem to have to do that, I guess because they are not using createTRPCProxyClient. Do you know why that is or what I can use as a transformer?
Nick
Nick16mo ago
That's a tRPC thing, check the docs for transformers 🙂 It's easy!
ferdy
ferdy16mo ago
I will, though I am still curious why every example I've seen of the proxy client doesnt include having to specify a transformer
Nick
Nick16mo ago
You don't have to, but you also can't go half in, which is what you might have inadvertently done if it's moaning at you
ferdy
ferdy16mo ago
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: "https://xxxxxxxx.execute-api.us-west-2.amazonaws.com/prod/"
})
],
});
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: "https://xxxxxxxx.execute-api.us-west-2.amazonaws.com/prod/"
})
],
});
^^^ this doesnt compile
Nick
Nick16mo ago
Can you share your appRouter too?
ferdy
ferdy16mo ago
yeah
export const createContext = (_: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => ({});
type Context = inferAsyncReturnType<typeof createContext>;

const t = initTRPC.context<Context>().create();

export const middleware = t.middleware;
export const router = t.router;
export const procedure = t.procedure;
export const mergeRouters = t.mergeRouters;

const appRouter = router({
testRoute: procedure.query(async () => {
// mock fast db lookup
await new Promise((res) => setTimeout(res, 100));

return {
value: 5,
};
}),
testRoute2: procedure.query(async () => {
await new Promise((res) => setTimeout(res, 100));

return {
obj: {
value: 10,
},
};
}),
});

export const handler = awsLambdaRequestHandler({
router: appRouter,
createContext,
});

export type AppRouter = typeof appRouter;
export const createContext = (_: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => ({});
type Context = inferAsyncReturnType<typeof createContext>;

const t = initTRPC.context<Context>().create();

export const middleware = t.middleware;
export const router = t.router;
export const procedure = t.procedure;
export const mergeRouters = t.mergeRouters;

const appRouter = router({
testRoute: procedure.query(async () => {
// mock fast db lookup
await new Promise((res) => setTimeout(res, 100));

return {
value: 5,
};
}),
testRoute2: procedure.query(async () => {
await new Promise((res) => setTimeout(res, 100));

return {
obj: {
value: 10,
},
};
}),
});

export const handler = awsLambdaRequestHandler({
router: appRouter,
createContext,
});

export type AppRouter = typeof appRouter;
Nick
Nick16mo ago
What's the compile error for your example? This does look fine so it's weird you're having an issue
ferdy
ferdy16mo ago
TS2345: Argument of type '{ links: TRPCLink<CreateRouterInner<RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>, { testRoute: BuildProcedure<"query", { _config: RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>; ... 5 more ...; _meta: object; }, { ...; ...' is not assignable to parameter of type 'CreateTRPCClientOptions<CreateRouterInner<RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>, { testRoute: BuildProcedure<"query", { _config: RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>; ... 5 more ...; _meta: object; }, {...'.   Property 'transformer' is missing in type '{ links: TRPCLink<CreateRouterInner<RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>, { testRoute: BuildProcedure<"query", { _config: RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>; ... 5 more ...; _meta: object; }, { ...; ...' but required in type '{ transformer: DataTransformerOptions; }'.
TS2345: Argument of type '{ links: TRPCLink<CreateRouterInner<RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>, { testRoute: BuildProcedure<"query", { _config: RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>; ... 5 more ...; _meta: object; }, { ...; ...' is not assignable to parameter of type 'CreateTRPCClientOptions<CreateRouterInner<RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>, { testRoute: BuildProcedure<"query", { _config: RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>; ... 5 more ...; _meta: object; }, {...'.   Property 'transformer' is missing in type '{ links: TRPCLink<CreateRouterInner<RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>, { testRoute: BuildProcedure<"query", { _config: RootConfig<{ ctx: {}; meta: object; errorShape: never; transformer: DataTransformerOptions; }>; ... 5 more ...; _meta: object; }, { ...; ...' but required in type '{ transformer: DataTransformerOptions; }'.
Nick
Nick16mo ago
Hm, what's your @trpc/* version? Are they all identical?
ferdy
ferdy16mo ago
"@trpc/server": "^10.14.0",
"@trpc/client": "^10.14.0",
"@trpc/server": "^10.14.0",
"@trpc/client": "^10.14.0",
Nick
Nick16mo ago
I've definitely seen someone else asking about this, and I think they resolved it, just can't find it now
ferdy
ferdy16mo ago
ok ill search around as well
Nick
Nick16mo ago
Reload your editor / TS language server and restart your build if you haven't
ferdy
ferdy16mo ago
no luck with that
Nick
Nick16mo ago
bah, this is a weird one This is an error that appears when you have set a transformer on the API but not on the frontend It's intentional and I can reproduce that in my own codebase But transformers are optional I suspect it might be something weird related to your repo setup. If you install superjson and get that set up, do any new errors appear?
ferdy
ferdy16mo ago
I was able to pass in superjson and get around the error, but then the response from my lambda was coming back undefined -- I was wondering if the default transformer would've just handled the response correctly
Nick
Nick16mo ago
At runtime?
ferdy
ferdy16mo ago
yep. and I tested out calling the api directly in my aws console, confirmed its working. Also confirmed that the lambda is getting invoked when I call it from the client by double checking logs it's just the return value isn't getting processed correctly
Nick
Nick16mo ago
That might be something more related to API Gateway if you're using that honestly AWS is a weird balancing act in my experience There's talk to adding some more in-depth docs but we're not there yet
ferdy
ferdy16mo ago
it definitely could be an apig oddity, though I can see it in the console returning the json response correctly. Maybe I need to find somewhere in between that I can inspect to see where that response is getting lost... Like a debug log from the trpc client or something
Nick
Nick16mo ago
Yep I put console logs at several stages, exported my own handler and called the lambda adapter's handler within it so I could log before and after. It's a pain but that's AWS 😄 Did also have some similar teething troubles like you
ferdy
ferdy16mo ago
ok I got it working -- truly dont understand this yet but adding Context to the type parameter of create did the trick:
export const createContext = ({event, context}: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => ({});
type Context = inferAsyncReturnType<typeof createContext>;

const t = initTRPC.context<Context>().create<Context>();
export const createContext = ({event, context}: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) => ({});
type Context = inferAsyncReturnType<typeof createContext>;

const t = initTRPC.context<Context>().create<Context>();
also fixed having to supply a transformer
Nick
Nick16mo ago
Huh, that’s very weird Typescript issue maybe? Is strict mode enabled?
ferdy
ferdy16mo ago
ohh no it wasnt. I didn't do the initial setup of this codebase, didn't even notice strict wasnt on. Enabling strict mode and removing the extra <Context> works, compile-wise -- not sure yet about runtime
Nick
Nick16mo ago
That’s good, strict mode fixes a lot of stuff in these well-typed frameworks
Unknown User
Unknown User16mo ago
Message Not Public
Sign In & Join Server To View
Falo
Falo15mo ago
Ferdy, i'm wondering how to push the code on aws, did you compile your code with tsc and send the dist folder to aws lambda ? Because i have a tons of problem with tsc and running the js file
ferdy
ferdy14mo ago
i do everything through cdk, specifically using NodejsFunction
ferdy
ferdy14mo ago
Snyk Advisor
Find the best open-source package for your project with Snyk Open Source Advisor. Explore over 1 million open source packages.