In an AWS lambda written in Node.js, I want to extract the following part of a URL when I do a GET call through the API gateway:
/devices/{id} --> {id} will be replaced by a value, and that is the value I want!
I know that to get QueryStringParameters you just use
event.queryStringParameters.[parameter name]
But how will I do this for path parameters, like for {id} above.
Also is there a good place where I can comprehensively learn about writing lambdas for APIs in Node.js?
Short answer:
const { id } = event.pathParameters;
I recently released a short training video that demonstrates in detail how to create API Gateway REST APIs and integrate them with AWS Lambda (NodeJS). Please check it out here:
Serverless Architecture: AWS API Gateway & Lambda
I'm assuming you are using lambda proxy here i'm pasting the event object sample for lambda proxy.
{
"message": "Good day, John of Seattle. Happy Friday!",
"input": {
"resource": "/{proxy+}",
"path": "/Seattle",
"httpMethod": "POST",
"headers": {
"day": "Friday"
},
"queryStringParameters": {
"time": "morning"
},
"pathParameters": {
"proxy": "Seattle"
},
"stageVariables": null,
"requestContext": {
"path": "/{proxy+}",
"accountId": "123456789012",
"resourceId": "nl9h80",
"stage": "test-invoke-stage",
"requestId": "test-invoke-request",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": "123456789012",
"cognitoIdentityId": null,
"caller": "AIDXXX...XXVJZG",
"apiKey": "test-invoke-api-key",
"sourceIp": "test-invoke-source-ip",
"accessKey": "ASIXXX...XXDQ5A",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": "arn:aws:iam::123456789012:user/kdeding",
"userAgent": "Apache-HttpClient/4.5.x (Java/1.8.0_131)",
"user": "AIDXXX...XXVJZG"
},
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "r275xc9bmd"
},
"body": "{ \"callerName\": \"John\" }",
"isBase64Encoded": false
}
}
the path can be extracted from "path" key in event object, it can be accessed from event.path and after that you can use string manipulation function to further manipulate it.
I hope it helps !
Use brackets in the resource path, as image above. Then in node.js user the code below:
exports.handler = async function(event) {
let serviceId = event.pathParameters.id;
}
The solution is very similar to what you mentioned in the first place. Just use event.pathParameters instead of event.queryStringParameters.
Here I'm using /api/test/{id} as my resource path and using Lambda Proxy integration. I'm getting the following event when I hit https://www.dummyapi.com/dev/api/test/id-123456
This has taken me awhile to figure out on my own so hoping this helps someone. After defining the Path Parameter on the Resource in API Gateway.
AWS Guide including PathParameter Steps: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-step-by-step.html
If using CloudFormation: https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-proxy-path-parameter-error/
My path /user/{userId}
My Path Parameter: userId
Then in my lambda function you can access the path via event.path which will have your parameters in object form: path: { userId: 9812 } }. Lots of documentation has it labeled as pathParameters but for whatever reason with this setup it doesn't come through that way.
export const getUser = async (event) => {
console.log(event);
const { userId } = event.path;
}
serverless.yaml
events:
- http:
path: user/{userId}
method: get
request:
parameters:
paths:
userId: true
integration: lambda
Related
I'm getting some troubles when trying to make a post request to an API made with swagger 2.0 (not by me).
I've imported a collection to postman, and when i perform a post request it works perfect. However in Node.js it outputs a 400 error with swagger library, and a 500 with axios.
Heres the schema that the collection provides in postman:
{
"workflowFunctionID": 1,
"workflowActionParameters": [
{
"name": "Description",
"value": "Probando y wea2",
"workflowFunctionParameterId": 2
},
{
"name": "Price",
"value": "25000",
"workflowFunctionParameterId": 3
}
]
}
As i mentioned it works perfectly. And this is the current code that am using Node.js:
main = async() => {
try {
const token = await acquireTokenWithClientCredentials(RESOURCE, CLIENT_APP_Id, CLIENT_SECRET, AUTHORITY);
const request = {
url: `${WORKBENCH_API_URL}/api/v1/contracts?workflowId=1&contractCodeId=1&connectionId=1`,
method: "POST",
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token.access_token}` },
body: {
workflowActionInput: {
workflowFunctionID: 1,
workflowActionParameters: [{
"name": "description",
"value": "cualkier wea"
},
{
"name": "price",
"value": "20000000"
}
]
}
}
}
let res = await Swagger.http(request);
console.log(res);
}
catch (err) {
console.error(err);
}
}
main();
How should i pass the body/form-data to the post request, or maybe using another package or code? Thanks in advance for any help.
When you have api running in postman, just see this button named "code" i marked with black
click on this button
select language as node.js
It will show you code in node.js for that api, just copy that code and paste where required.
Here i am attaching picture kindly see this
I was reading this blog post from Serverless about the different patterns that exist for Serverless architecture.
I'm interested in the services pattern and thought I'd try it out.
With this configuration in my serverless.yml file.
functions:
apps:
handler: handler.apps
events:
- http: post apps
cors: true
- http: patch users
- http: get users
cors: true
- http: delete users
The following output comes from running serverless deploy.
POST - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
PATCH - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
GET - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
DELETE - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
Now in a CRUD service if I wanted to get a single resource, I'd probably have something like this for a get endpoint, /staging/users/{id}. With the above pattern, is it up to the user to pass in a query string parameter like this /staging/users?id={id} instead of having a path parameter like this /staging/users/{id}? Is it possible to get the endpoint to have a path parameter?
It doesn't seem like the path can be overwritten this way.
You already can use path parameters, e.g.:
{
"myFunction": {
"handler": "src/myFunction/index.index",
"description": "Does awesome things",
"events": [
{
"http": {
"path": "apiPath/{parameterOne}",
"method": "GET",
"integration": "lambda",
"request": {
"parameters": {
"parameterOne": true
},
"template": {
"application/json": "{ \"parameterOne\": \"$input.params(\"parameterOne\")\" }"
}
},
"response": {
"statusCodes": {
"200": {
"pattern": ""
},
"400": {
"pattern": "[\\s\\S]*\\[400\\][\\s\\S]*",
"template": "$input.path('$.errorMessage')"
},
"500": {
"pattern": "[\\s\\S]*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\])[\\s\\S]*",
"template": "$input.path('$.errorMessage')"
}
},
"headers": {
"Cache-Control": "'no-cache, no-store'",
"Pragma": "'no-cache'",
"Expires": "'0'",
"Strict-Transport-Security": "'max-age=31536000; includeSubdomains; preload'"
}
},
"cors": {
"origin": "*",
"headers": [
"Content-Type",
"Pragma",
"Cache-Control",
"X-Amz-Date",
"Authorization",
"X-Api-Key",
"X-Amz-Security-Token",
"X-Amz-User-Agent"
],
"allowCredentials": false
}
}
}
]
}
}
The parameter parameterOne will be mapped into the Lambda event.
I might be interpreting this wrong but, at least in the case of the aws you are able to add several resources into a lambda. You will be responsible to handle the correct behavior, which you can do by parsing the event which will have filled the path parameters in case they exist.
as mentioned on the blog post
You can inspect the incoming HTTP request’s path and method by parsing the event body in your code, and then perform the correct operation in response. It’s like having a small router in the beginning of your Lambda code.
you can specific a more complete event with
- http:
path: users/{id}
method: delete
Can I resolve the issue in JIRA?
I made some trials with REST API as;
var url = "https://hibernate.atlassian.net/rest/api/2/issue/WEBSITE-1/transitions";
var message = [{
"update": {
"comment": [
{
"add": {
"body": "some text for body"
}
}
]
},
"fields": {
"assignee": {
"name": "name1"
},
"resolution": {
"name": "Fix"
}
},
"transition": {
"id": "1"
}
}];
request({
url: url,
method: "POST",
json: true,
body: message,
}, function (error){});
Url(https://hibernate.atlassian.net/rest/api/2/issue/WEBSITE-1/transitions) gives me;
{"expand":"transitions","transitions":[]}
How can I resolve an issue in JIRA? Am I doing wrong?
You've got the right approach, but you need to authenticate your requests with a user that has permission to execute the transition.
Because you execute your requests anonymously, JIRA gives you a response that does not contain any transitions you can execute and will not allow you to perform a transition.
Take a look at the documentation for the request module or another example.
To get the full list of transitions, append the string ?expand=transitions.fields to your existing url. So in this case it would look like
var url = "https://hibernate.atlassian.net/rest/api/2/issue/WEBSITE-1/transitions?expand=transitions.fields";
We have an API that will be used to provision certain resources in AWS using Cloud Formation. This includes a Lambda function that will send events to S3, with the bucket being configurable. The thing is, we will know the bucket name when we provision the lambda, not within the lambda code itself.
As far as I can tell, there is no way to inject the S3 bucket name at the time of provisioning, in the Cloud Formation Template itself. Is that true?
The only solution I can see is to generate the function code on the fly, and embed that into the Cloud Formation template. This would make us unable to use any NPM dependencies along with the function code. Is there a better option?
So, I realized I had never updated this question with my eventual solution. I ended up embedding a proxy lambda function into the cloudformation template, which enabled me to inject template parameters.
Example:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates a function to relay messages from a Kinesis instance to S3",
"Parameters": {
"S3Bucket" : {
"Type": "String",
"Description": "The name of the S3 bucket where the data will be stored"
},
"S3Key": {
"Type": "String",
"Description": "The key of the directory where the data will be stored"
}
},
"Resources": {
"mainLambda": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Description" : "Writes events to S3",
"Role" : { "Ref": "LambdaRoleARN" },
"Runtime" : "nodejs4.3",
"Code" : {
"S3Bucket": "streams-resources",
"S3Key": "astro-bass/${GIT_COMMIT}/lambda/astro-bass.zip"
}
}
},
"lambdaProxy": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Runtime" : "nodejs",
"Code" : {
"ZipFile": { "Fn::Join": ["", [
"var AWS = require('aws-sdk');",
"var lambda = new AWS.Lambda();",
"exports.handler = function(event, context) {",
"event.bundledParams = ['",
{ "Ref": "S3Bucket" },
"','",
{ "Ref": "S3Key" },
"'];",
"lambda.invoke({",
"FunctionName: '",
{ "Ref": "mainLambda" },
"',",
"Payload: JSON.stringify(event, null, 2),",
"InvocationType: 'Event'",
"}, function(err, data) {",
"if(err) {",
"context.fail(err);",
"}",
"context.done();",
"});",
"};"
]]}
}
}
},
},
...
}
The proxy function had the parameters injected into its code (s3bucket/key), and then it invokes the main lambda with a modified event object. It's a little unorthodox but struck me as much cleaner than the other available solutions, such as parse stacknames/etc. Worked well thus far.
Note that this solution only works currently with the legacy node environment. Not an issue, but worrisome in terms of the longevity of this solution.
UPDATE:
We ran into limitations with the previous solution and had to devise yet another one. We ended up with an off-label usage of the description field to embed configuration values. Here is our Lambda
'use strict';
var aws = require('aws-sdk');
var lambda = new aws.Lambda({apiVersion: '2014-11-11'});
let promise = lambda.getFunctionConfiguration({ FunctionName: process.env['AWS_LAMBDA_FUNCTION_NAME'] }).promise();
exports.handler = async function getTheConfig(event, context, cb) {
try {
let data = await promise;
cb(null, JSON.parse(data.Description).bucket);
} catch(e) {
cb(e);
}
};
Then, in the description field, you can embed a simple JSON snipped like so:
{
"bucket": "bucket-name"
}
Moreover, this structure, using the promise outside of the handler, limits the request to only occurring when the container is spawned - not for each individual lambda execution.
Not quite the cleanest solution, but the most functional one we've found.
There is no way of passing parameters to a Lambda function beside the event itself at the moment.
If you are creating a Lambda function with CloudFormation you could use the following workaround:
Use the Lambda function name to derive the CloudFormation stack name.
Use the CloudFormation stack name to access resources, or parameters of the stack when executing the Lambda function.
I would suggest doing it like this.
First create an index.js file and add this code.
var AWS = require('aws-sdk');
const s3 = new AWS.S3();
const https = require('https');
exports.handler = (event, context, callback) => {
const options = {
hostname: process.env.ApiUrl,
port: 443,
path: '/todos',
method: 'GET'
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
};
Zip the index.js file and upload it to an S3 bucket in the same region as your lambda function.
Then use this Cloudformation template make sure you specific the correct bucket name.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ApiWorkflow",
"Metadata": {
},
"Parameters": {
"ApiUrl": {
"Description": "Specify the api url",
"Type": "String",
"Default": "jsonplaceholder.typicode.com"
},
},
"Mappings": {
},
"Conditions": {
},
"Resources": {
"lambdaVodFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "lamdba-exec-tests",
"S3Key": "index.js.zip"
},
"Handler": "index.handler",
"Role": "arn:aws:iam::000000000:role/BasicLambdaExecRole",
"Runtime": "nodejs10.x",
"FunctionName": "ApiWorkflow",
"MemorySize": 128,
"Timeout": 5,
"Description": "Texting Lambda",
"Environment": {
"Variables": {
"ApiUrl": {
"Ref": "ApiUrl"
},
"Test2": "Hello World"
}
},
}
}
},
"Outputs": {
"ApiUrl": {
"Description": "Set api url",
"Value": {
"Ref": "ApiUrl"
}
}
}
}
You should see in the template Environmental variables you can access these in your NodeJS Lambda function like this.
process.env.ApiUrl
my case,I have create rest api which it should call external api and should display the response to the client using rest connector, i have added the below code in datasources.js
{"aci":{
"name": "aci",
"connector": "rest",
"operations": [{
"template": {
"method": "POST",
"url": "http://192.168.220.1:7800/esb/esb_cbs_service/AccountInfo",
"headers": {
"accepts": "application/json",
"content-type": "application/json"
},
"form": {
"ACCTINFOTRNRQ": {
"TRNUID": "12345",
"ACCTINFORQ": {
"DTACCTUP": "20050101"
}
}
},
"responsePath": "$.results[0].AccountInfoResponse.Item"
},
"functions": {
"acif": [
"customerId",
"customerName"
]
}
}]
}}
in model.js
module.exports = function(Acim) {
//remote method
Acim.extcall = function(Acim, cb){
cb(null, res);
}
};
Acim.remoteMethod('extcall',{
accepts: [{arg: 'customerId type" 'string''}]
returns: {arg: 'customerId', type: 'type'}
http: {path:'/getAci', verb: 'post'}
})
and when I reload the project I am facing this error. Please I am stuck here, what am I doing wrong?
C:\Users\user\acinfo>apic loopback:refresh
Updating swagger and product definitions
ERROR Cannot load the LoopBack application: Cannot load the LoopBack application: Cannot create data source "aci": Cannot initialize connector "RequestBuilder": Cannot read property 'root' of undefined
Please fix the problem and run apic loopback:refresh
Please fix the problem and run apic loopback:refresh