Setting path parameters in AWS API Gateway JavaScript SDK - amazon-web-services

I'm trying to set path parameters when making calls to API Gateway endpoints via the JavaScript SDK and not having any luck. It looks like I either have something misconfigured or there is a bug in the SDK generation.
I am able to successfully call endpoints that do not take path parameters but when I try to pass in a parameter to be used as a path parameter the SDK just replaces the path parameter with a blank and my call fails.
Example, assume client is a properly initialized API Gateway client. I have an endpoint called /measurement with a child of /measurement/{id}. I am able to call both directly.
client.measurementGet({},{}); - successfully calls my /measurement endpoint
client.measurementIdGet({"id": "1234"}, {}); - Browser makes a call to /measurement/ instead of /measurement/1234
Looking at the source of my apigClient.js, it appears that the SDK generator is not putting path parameters into the list of parameters that it's looking for. For example, the code of my generated measurementIdGet method looks like this:
apigClient.measurementIdGet = function (params, body, additionalParams) {
if(additionalParams === undefined) { additionalParams = {}; }
apiGateway.core.utils.assertParametersDefined(params, [], ['body']);
var measurementIdGetRequest = {
verb: 'get'.toUpperCase(),
path: pathComponent + uritemplate('/measurement/{id}').expand(apiGateway.core.utils.parseParametersToObject(params, [])),
headers: apiGateway.core.utils.parseParametersToObject(params, []),
queryParams: apiGateway.core.utils.parseParametersToObject(params, []),
body: body
};
return apiGatewayClient.makeRequest(measurementIdGetRequest, authType, additionalParams, config.apiKey);
};
I dug into the assertParametersDefined and parseParametersToObject and it looks like those methods are expecting a list of parameters to look for. In both cases the SDK has generated empty lists instead of putting my path parameter in there.
If I manually update the generated file to change the two lines to
apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']);
and
apiGateway.core.utils.parseParametersToObject(params, ['id'])
The SDK makes the proper call.
Am I missing something in my configuration or is there a bug in the code generator?

If you are using cloud formation like me. You will need to add it in the RequestParameters.
for a resource like this /api/pets/{id}/attributes/{attrid} following code works
PetsByIdAttributesByAttridGetMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref MyApi
ResourceId: !Ref PetsByIdAttributesByAttridResource
HttpMethod: GET
AuthorizationType: AWS_IAM
RequestParameters:
method.request.path.id : true
method.request.path.attrid : true
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations

I know it's an old question, but for those new to AWS that may still run into this issue like I did, I hope this answer helps:
In the AWS API Gateway Console, make sure that you specify the "URL Query String Parameters" in the "Method Request" for your "GET". Then re-deploy your API and generate the SDK again. This time the apigClient.js will be generated with the defined Query Parameter keys correctly filled in to the calls to assertParametersDefined and parseParametersToObject.

Assuming that you're importing a swagger definition to create the API, defining your parameters at the method level as opposed to the path level will result in a generated SDK with the key filled out and should work correctly.
{
...
"/path/{to}/resource": {
"get": {
"parameters": [ // define here
"name": "to",
"in": "path",
...
],
...
},
"parameters": [] // not here
}
Although defining parameters at the path level is correct according the the Swagger spec and API Gateway does use them in the created API, it appears that API Gateway disregards them in some contexts.

This look's like an issue doesn't parse the params.
https://github.com/aws/chalice/issues/498enter link description here

Related

Sharing API gateway endpoint URL across different stacks in CDK

I have following AWS CDK backed solution:
Static S3 based webpage which communicates with
API Gateway which then sends data to
AWS lambda.
The problem is that S3 page needs to be aware of API gateway endpoint URL.
Obviously this is not achievable within the same CDK stack. So I have defined two stacks:
Backend (API gateway + lambda)
Frontend (S3 based static webpage)
They are linked as dependant in CDK code:
const app = new cdk.App();
const backStack = new BackendStack(app, 'Stack-back', {...});
new FrontendStack(app, 'Stack-front', {...}).addDependency(backStack, "API URL from backend is needed");
I try to share URL as follows.
Code from backend stack definition:
const api = new apiGW.RestApi(this, 'MyAPI', {
restApiName: 'My API',
description: 'This service provides interface towards web app',
defaultCorsPreflightOptions: {
allowOrigins: apiGW.Cors.ALL_ORIGINS,
}
});
api.root.addMethod("POST", lambdaIntegration);
new CfnOutput(this, 'ApiUrlRef', {
value: api.url,
description: 'API Gateway URL',
exportName: 'ApiUrl',
});
Code from frontend stack definition:
const apiUrl = Fn.importValue('ApiUrl');
Unfortunately, instead of URL I get token (${Token[TOKEN.256]}). At the same time, I see URL is resolved in CDK generated files:
./cdk.out/Stack-back.template.json:
"ApiUrlRef": {
"Description": "API Gateway URL",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "MyAPI7DAA778AA"
},
".execute-api.us-west-1.",
{
"Ref": "AWS::URLSuffix"
},
"/",
{
"Ref": "MyAPIDeploymentStageprodA7777A7A"
},
"/"
]
]
},
"Export": {
"Name": "ApiUrl"
}
}
},
What I'm doing wrong?
UPD:
After advice of fedonev to pass data as props, situation did not changed much. Now url looks like that:
"https://${Token[TOKEN.225]}.execute-api.us-west-1.${Token[AWS.URLSuffix.3]}/${Token[TOKEN.244]}/"
I think important part I missed (which was also pointed by
Milan Gatyas) is how I create HTML with URL of gateway.
In my frontend-stack.ts, I use template file. After template is filled, I store it in S3:
const filledTemplatePath: string = path.join(processedWebFileDir,'index.html');
const webTemplate: string = fs.readFileSync(filledTemplatePath, 'utf8')
const Handlebars = require("handlebars")
let template = Handlebars.compile(webTemplate)
const adjustedHtml: string = template({ apiGwEndpoint: apiUrl.toString() })
fs.writeFileSync(filledTemplatePath, adjustedHtml)
// bucket
const bucket: S3.Bucket = new S3.Bucket(this, "WebsiteBucket",
{
bucketName: 'frontend',
websiteIndexDocument: 'index.html',
websiteErrorDocument: 'error.html',
publicReadAccess: true,
})
new S3Deploy.BucketDeployment(this, 'DeployWebsite', {
sources: [S3Deploy.Source.asset(processedWebFileDir)],
destinationBucket: bucket,
});
(I'm new to TS and web, please don't judge much :) )
Am I correct that S3 is populated on synth, deploy does not change anything and this is why I get tokens in html?
Will be grateful for a link or explanation so that I could understand the process better, there are so much new information to me that some parts are still quite foggy.
As #fedonev mentioned, the tokens are just placeholder values in the TypeScript application. CDK app replaces tokens with intrinsic functions when the CloudFormation template is produced.
However, your use case is different. You try to know the information inside the CDK app which is available only at synthesis time, and you can't use the intrinsic function to resolve the URL while being in CDK app to write to file.
If possible you can utilize the custom domain for the API Gateway. Then you can work with beforehand known custom domain in your static file and assign the custom domain to the API Gateway in your CDK App.
[Edit: rewrote the answer to reflect updates to the OP]
Am I correct that S3 is populated on synth, deploy does not change anything and this is why I get tokens in html?
Yes. The API URL will resolve only at deploy-time. You are trying to consume it at synth-time when you write to the template file. At synth-time, CDK represents not-yet-available values as Tokens like ${Token[TOKEN.256]}, the CDK's clever way of handling such deferred values.
What I'm doing wrong?
You need to defer the consumption of API URL until its value is resolved (= until the API is deployed). In most cases, passing constructs as props between stacks is the right approach. But not in your case: you want to inject the URL into the template file. As usual with AWS, you have many options:
Split the stacks into separate apps, deployed separately. Deploy BackendStack. Hardcode the url into FrontendStack. Quick and dirty.
Instead of S3, use Amplify front-end hosting, which can expose the URL to your template as an environment variable. Beginner friendly, has CDK support.
Add a CustomResource construct, which would be backed by a Lambda that writes the URL to the template file as part of the deploy lifecycle. This solution is elegant but not newbie-friendly.
Use a Pipeline to inject the URL variable as a build step during deploy. Another advanced approach.

Cloudformation Error - Resource of type 'AWS::ApiGateway::Model' with identifier 'Empty' already exists

I am creating an APIGateway using Cloudformation. When attempting to create via the AWS CF Console I am recieving this error:
The EmptyModel resource is a AWS::ApiGateway::Model object that looks like this:
EmptyModel:
Type: "AWS::ApiGateway::Model"
Properties:
RestApiId: !Ref ApiGatewayRestApi
Name: "Empty"
Description: "This is a default empty schema model"
Schema: |
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title" : "Empty Schema",
"type" : "object"
}
ContentType: "application/json"
I am referencing this model on every one of my AWS::ApiGateway::Method objects in the CF Template like this:
What am I doing wrong? I used Former2 to reverse engineer my current api and get some of this template for the new api I am creating. So I am wondering if there is just something weird in this? Any help is hella appreciated.
I was able to identify this issue finally through much trial and error. Apparently Models are shared between all of the APIs in your account (or maybe just region)
So the error was indicating that there was already a model called "Empty" and that is because there was one, in a different API. I changed the name to "EmptyModel" and it worked great!
If you used former2 to create your template from existing resources, you can't just deploy the template obtained, as you will get the errors you are getting.
Instead you have to modify the template and import your resources to CloudFormation. Or easier, you have to delete existing resources, and then re-create them using CloudFormation.

Editing API Documentation in CDK

In the AWS Documentation for API Gateway, there are ways to edit your API documentation in the console. Working in CDK, I can't find any way to achieve the same thing. The goal is to create the exact same outputs.
Question 1:
See API Gateway documentation in console. This shows how you can edit pretty much everything you need to get nice headings and so on in your swagger / redoc outputs. But I can't find any way of inserting chunks of yaml / json into the doc in cdk.
Question 2:
Is it possible to prevent your exported OAS file from including all of the options methods? I want to automate the process of updating the API docs after cdk deploy, so it should be done as part of the code.
Question 3:
How can you add tags to break your API into logical groupings. Again, this is something that is very useful in standard API documentation, but I can't find the related section in cdk anywhere?
Really, I think AWS could knock up a short petstore example to help us all out. If I get it working, perhaps I'll come back here and post up one of my own with notes.
Question 1 & Question 3:
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
//https://docs.aws.amazon.com/apigateway/latest/api/API_DocumentationPart.html
new apigateway.CfnDocumentationPart(this, 'GetDocumentationPart', {
location: {
method: 'GET',
path: '/my/path',
type: 'METHOD',
},
properties: `{
"tags": ["example"],
"description": "This is a description of the method."
}`,
restApiId: 'api-id',
});
//https://docs.aws.amazon.com/apigateway/latest/api/API_DocumentationVersion.html
new apigateway.CfnDocumentationVersion(
this,
'DocumentationVersion',
{
documentationVersion: 'generate-version-id',
restApiId: 'api-id',
}
);
Notice that you need to generate a version to publish the documentation changes. For the version value, I'll suggest generating a UUID.
According to the example above, the GET /my/path will be grouped by the "example" tag at a Swagger UI.
Question 2:
No, it is not possible.
I solved it by creating a lambda function listening to the api-gateway deployment event, getting the JSON file from the api-gateway via AWS SDK, parsing it for removing unwanted paths, and storing it in an S3 bucket.

AWS Lambda Rest API: A sibling ({id}) of this resource already has a variable path part -- only one is allowed Unable to create resource at path

I'm particular new to Lambda and to AWS in general. I'm trying to setup a simple REST API Service with Lambda. I've used CloudFormat and CodePipeline to have a simple Express app.
I'm trying to figure out why during the deployment phase, during ExecuteChangeSet I have this error:
Errors found during import: Unable to create resource at path '/stations/{stationId}/allowedUsers': A sibling ({id}) of this resource already has a variable path part -- only one is allowed Unable to create resource at path '/stations/{stationId}/allowedUsers/{userId}': A sibling ({id}) of this resource already has a variable path part -- only one is allowed
This is what I have inside the template.yml
Events:
AllowedUsers:
Type: Api
Properties:
Path: /stations/{stationId}/allowedUsers
Method: get
AddAllowedUsers:
Type: Api
Properties:
Path: /stations/{stationId}/allowedUsers
Method: post
DeleteAllowedUsers:
Type: Api
Properties:
Path: /stations/{stationId}/allowedUsers/{userId}
Method: delete
GetAllowedUser:
Type: Api
Properties:
Path: /stations/{stationId}/allowedUsers/{userId}
Method: get
I searched a bit for this error but I'm not sure how to solve it.
For me, the issue was different from what is described in the GitHub issue Bryan mentioned.
I was using two different parameter names. Finishing the refactoring and using a single id name fixed the issue.
Example:
DeleteAllowedUsers:
Type: Api
Properties:
Path: /stations/{stationId}/allowedUsers/{id}
Method: delete
GetAllowedUser:
Type: Api
Properties:
Path: /stations/{stationId}/allowedUsers/{userId}
Method: get
Here is the walk around for this problem. It was posted on github by pettyalex.
link :https://github.com/serverless/serverless/issues/3785
You might encounter this issue when updating a variable path while using serverless ( and serverless.yaml ) to provision the AWS gatewayApi, here is a walk-around:
comment out the endpoint function to remove it completely
uncomment and deploy again

Versioning using base path mappings in AWS API Gateway

I have a pretty straightforward stack: API Gateway sitting in front of a lambda. Currently my paths looks something like:
/dogs, /dogs/{id}, etc.
All I want to do is add a version to the base path (i.e. api.dogs.com/v1/dogs). I tried doing this by creating a custom domain name with a base path mapping of v1 pointing to my stage in API Gateway.
This routes just fine through API Gateway but has issues once it hits the routing logic in my lambda. My lambda is expecting /dogs but with the base path mapping the path is actually v1/dogs.
What's a good way to approach this? I want to get away from having to deal with versions directly in my code (lambda) if possible.
In the event object your lambda function receives you should actually find all the needed information with and without versioning:
event = {
"resource": "/hi",
"path": "/v1/hi",
"requestContext": {
"resourcePath": "/hi",
"path": "/v1/hi",
....
},
....
}
Just adjust the code in your router logic to access the desired attributes should fix your problem and remove the need to care about versioning again in your code.