Roger Heathcote
Roger Heathcote

Reputation: 3515

How get path params in CDK + APIGateway + Lambda

So, turns out I had it all along but I was logging it out incorrectly. I had been doing a Object.keys(event).forEach and console logging each key and value. I guess this didn't display the value as it's a nested object. Using JSON.stringify, as per @robC3's answer shows all the nested objects and values properly and is easier too! TL;DR just use curly braces in your gateway paths and they will be present in event.pathParameters.whateverYouCalledThem

I'm used to express land where you just write /stuff/:things in your route and then req.params.things becomes available in your handler for 'stuff'.

I'm struggling to get the same basic functionality in CDK. I have a RestAPI called 'api' and resources like so...

const api = new apigateway.RestApi(this, "image-cache-api", { //options })
const stuff = api.root.addResource("stuff")
const stuffWithId = get.addResource("{id}")
stuffWithId.addMethod("GET", new apigateway.LambdaIntegration(stuffLambda, options))

Then I deploy the function and call it at https://<api path>/stuff/1234

Then in my lambda I check event.pathParameters and it is this: {id: undefined}

I've had a look through the event object and the only place I can see 1234 is in the path /stuff/1234 and while I could just parse it out of that I'm sure that's not how it's supposed to work.

:/

Most of the things I have turned up while googling mention "mapping templates". That seems overly complicated for such a common use case so I had been working to the assumption there would be some sort of default mapping. Now I'm starting to think there isn't. Do I really have to specify a mapping template just to get access to path params and, if so, where should it go in my CDK stack code?

I tried the following...

stuffWithId.addMethod("GET", new apigateway.LambdaIntegration(stuffLambda, {
    requestTemplate: {
        "id": "$input.params('id')",
    }        
}))

But got the error...

error TS2559: Type '{ requestTemplate: { id: string; }; }' has no properties in common with type 'LambdaIntegrationOptions'.

I'm pretty confused as to whether I need requestTemplate, requestParametes, or something else entirely as all the examples I have found so far are for the console rather than CDK.

Upvotes: 9

Views: 10517

Answers (2)

rob3c
rob3c

Reputation: 2086

This works fine, and you can see where the full path, path params, query params, etc., are in the event structure when you test it in a browser.

// lambdas/handler.ts

// This code uses @types/aws-lambda for typescript convenience.
// Build first, and then deploy the .js handler.

import { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda';

export const main: APIGatewayProxyHandler = async (event, context, callback) => {
    return <APIGatewayProxyResult> {
        body: JSON.stringify([ event, context ], null, 4),
        statusCode: 200,
    };
}

// apig-lambda-proxy-demo-stack.ts

import * as path from 'path';
import { aws_apigateway, aws_lambda, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class ApigLambdaProxyDemoStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const stuffLambda = new aws_lambda.Function(this, 'stuff-lambda', {
        code: aws_lambda.Code.fromAsset(path.join('dist', 'lambdas')),
        handler: 'handler.main',
        runtime: aws_lambda.Runtime.NODEJS_14_X,
    });

    const api = new aws_apigateway.RestApi(this, 'image-cache-api');

    const stuff = api.root.addResource('stuff');
    const stuffWithId = stuff.addResource('{id}');
    stuffWithId.addMethod('GET', new aws_apigateway.LambdaIntegration(stuffLambda));
  }
}

This sample query:

https://[id].execute-api.[region].amazonaws.com/prod/stuff/1234?q1=foo&q2=bar

gives this response (excerpt):

Sample API Gateway test lambda response JSON

If you want to handle arbitrary paths at a certain point in your API, you'll want to explore the IResource.addProxy() CDK method. For example,

api.root.addProxy({
    defaultIntegration: new aws_apigateway.LambdaIntegration(stuffLambda),
});

That creates a {proxy+} resource at the API root in the example and would forward all requests to the lambda. Rather than configuring every single endpoint in API Gateway, you could handle them all in your same handler.

Upvotes: 13

lynkfox
lynkfox

Reputation: 2400

First thing to note is that all cdk using LambdaIntegration module actually have to be Post - Get methods with LambdaIntegration don't function as you are sending data to the Lambda. If you want to do a get specifically you have to write custom methods in the api for it.

Now, I have only done this in Python, but hopefully you can get the idea:

my_rest_api = apigateway.RestApi(
        self, "MyAPI",
        retain_deployments=True,
        deploy_options=apigateway.StageOptions(
            logging_level=apigateway.MethodLoggingLevel.INFO,
            stage_name="Dev
        )
    )


a_resource = apigateway.Resource(
        self, "MyResource",
        parent=my_rest_api.root,
        path_part="Stuff"
    )

my_method = apigateway.Method(
        self, "MyMethod",
        http_method="POST",
        resource=quoting_resource,
        integration=apigateway.AwsIntegration(
            service="lambda",
            integration_http_method="POST",
            path="my:function:arn"
        )
    )

your Resource construct defines your path - you can chain multiple resources together if you want to have methods off each level, or just put them all together in path_part - so you could have resourceA defined, and use it as the parent in resourceB - which would get you resourceAPathPart/resourceBPathPart/ to access your lambda.

or you can put it all together in resourceA with path_part = stuff/path/ect

I used the AwsIntegration method here instead of LambdaIntegration because, in the full code, I'm using stage variables to dynamically pick different lambdas depending on what stage im in, but the effect is rather similar

Upvotes: 0

Related Questions