trpc + AWS Lambda (through cdk)

Fferdy3/6/2023
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 😅
Nnlucas3/6/2023
Have you looked at the Lambda Adapter and its docs?
Fferdy3/6/2023
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(...)


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"),
...
UUUnknown User3/7/2023
2 Messages Not Public
Sign In & Join Server To View
Nnlucas3/7/2023
So exporting a handler is a standard lambda thing, that’s the boundary of your application
Nnlucas3/7/2023
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
Fferdy3/7/2023
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
Fferdy3/7/2023
@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?
Nnlucas3/7/2023
That's a tRPC thing, check the docs for transformers 🙂
Nnlucas3/7/2023
It's easy!
Fferdy3/7/2023
I will, though I am still curious why every example I've seen of the proxy client doesnt include having to specify a transformer
Nnlucas3/7/2023
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
Fferdy3/7/2023
const client = createTRPCProxyClient<AppRouter>({
    links: [
      httpBatchLink({
        url: "https://xxxxxxxx.execute-api.us-west-2.amazonaws.com/prod/"
      })
    ],
  });
Fferdy3/7/2023
^^^ this doesnt compile
Nnlucas3/7/2023
Can you share your appRouter too?
Fferdy3/7/2023
yeah
Fferdy3/7/2023
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;
Nnlucas3/7/2023
What's the compile error for your example?
Nnlucas3/7/2023
This does look fine so it's weird you're having an issue
Fferdy3/7/2023
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; }'.
Nnlucas3/7/2023
Hm, what's your @trpc/* version? Are they all identical?
Fferdy3/7/2023
"@trpc/server": "^10.14.0",
"@trpc/client": "^10.14.0",
Nnlucas3/7/2023
I've definitely seen someone else asking about this, and I think they resolved it, just can't find it now
Fferdy3/7/2023
ok ill search around as well
Nnlucas3/7/2023
Reload your editor / TS language server and restart your build if you haven't
Fferdy3/7/2023
no luck with that
Nnlucas3/7/2023
bah, this is a weird one
Nnlucas3/7/2023
This is an error that appears when you have set a transformer on the API but not on the frontend
Nnlucas3/7/2023
It's intentional and I can reproduce that in my own codebase
Nnlucas3/7/2023
But transformers are optional
Nnlucas3/7/2023
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?
Fferdy3/7/2023
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
Nnlucas3/7/2023
At runtime?
Fferdy3/7/2023
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
Fferdy3/7/2023
it's just the return value isn't getting processed correctly
Nnlucas3/7/2023
That might be something more related to API Gateway if you're using that
Nnlucas3/7/2023
honestly AWS is a weird balancing act in my experience
Nnlucas3/7/2023
There's talk to adding some more in-depth docs but we're not there yet
Fferdy3/7/2023
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
Nnlucas3/7/2023
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.
Nnlucas3/7/2023
It's a pain but that's AWS 😄
Nnlucas3/7/2023
Did also have some similar teething troubles like you
Fferdy3/7/2023
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>();
Fferdy3/7/2023
also fixed having to supply a transformer
Nnlucas3/7/2023
Huh, that’s very weird
Nnlucas3/7/2023
Typescript issue maybe? Is strict mode enabled?
Fferdy3/7/2023
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
Nnlucas3/7/2023
That’s good, strict mode fixes a lot of stuff in these well-typed frameworks
UUUnknown User3/7/2023
Message Not Public
Sign In & Join Server To View
FFalo4/24/2023
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 ?
FFalo4/24/2023
Because i have a tons of problem with tsc and running the js file
Fferdy5/1/2023
i do everything through cdk, specifically using NodejsFunction