0

I have node/express apis app. I deploy all apis in one AWS-lambda function using serverless framework.

For some reason, I want to call one of the API routes directly from/within AWS-lambda function.

Node/express serverless app

const express = require("express");
const awsServerlessExpress = require("aws-serverless-express");
const app = express();
const server = awsServerlessExpress.createServer(app)
...

const pointsRoute = require("./src/routes/points.route");

...

app.use(`/api/v5.1/points`, pointsRoute);


module.exports.handler = (event, context) => {
   
    awsServerlessExpress.proxy(server, event, context);
}

points.route.js

const express = require("express");
const router = express.Router();

const { postPoints } = require("../ctrls/points.ctrl.js");

router.route("/").post(postPoints);

module.exports = router;

points.ctrl.js

exports.postPoints = (req, res, next) =>  {

    console.log("hitting postPoints method");
    ...
    ...
}

Calling /api/v5.1/points route from javascript directly.

const AWS = require("aws-sdk");

AWS.config.update({
      accessKeyId: "id",
      secretAccessKey: "AccessKey",
      region: "region"
})

const lambda = new AWS.Lambda();

export const invokeLambda = async (points: any) => {

    return lambda.invoke({                            // pls NOTE it always make GET call by default
        FunctionName: 'my-lambda-fn',
        InvocationType: 'RequestResponse',
        Payload: JSON.stringify({
          path: '/api/v5.1/points',
          body: JSON.stringify(points)
        }),
      }).promise();
}

call invoke method

 const result = await invokeLambda(points);

With above setup I used to get error that route doesn't exist because /api/v5.1/points is defined with POST method as above.

So what I did: I just added GET method to make sure it hits route with GET method. I updated router code as follow,

const express = require("express");
const router = express.Router();

const { postPoints, getPoints } = require("../ctrls/points.ctrl.js");

router.route("/").get(getPoints).post(postPoints);

module.exports = router;

In points.ctrl.js just added,

exports.getPoints = (req, res, next) => {
   res.status(200).send("points being sent !!!");           // this is hitting now with lambda.invoke
}

Now calling invokeLambda function as below,

 await invokeLambda(points);

it returns,

{
    "statusCode": 200,
    "body": "points being sent !!!",
    "headers": {
        "x-powered-by": "Express",
        "access-control-allow-origin": "*",
        "content-type": "text/html; charset=utf-8",
        "content-length": "33",
        "etag": "W/\"21-McMNImVhZFYtkiA9PATcy7GTuZU\"",
        "date": "Tue, 25 Apr 2023 11:12:15 GMT",
        "connection": "close"
    },
    "isBase64Encoded": false
}

THEN question is HOW CAN I MAKE POST method call from lambda.invoke? so it can start hitting POST route.

FYI previous question : invoke lambda(node/express - serverless express route) from javascript(reactjs) using aws-sdk

I tried adding POST as below,

  return lambda.invoke({                            
    FunctionName: 'my-lambda-fn',
    InvocationType: 'RequestResponse',

    httpMethod: "POST"                      // throws validation error

    http:{ method : "POST"                  // throws validation error


    Payload: JSON.stringify({
      path: '/api/v5.1/points',
      body: JSON.stringify(points)
    }),
  }).promise();

Update after creating bounty

Pls check below relevant questions which I asked and didn't receive any answer. This question will help you understand other aspect of the question asked here.

  1. Asked below question after this question.

Invoking Lambda function from JS doesn't receive payload/body/request object

  1. Asked below question before this question.

invoke lambda(node/express - serverless express route) from javascript(reactjs) using aws-sdk

micronyks
  • 54,797
  • 15
  • 112
  • 146
  • By putting `POST` *somewhere* in the payload. The lambda service does not care about your REST API and the HTTP verbs you use in it. https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html - I am surprised it actually works with the `path` top level in the payload. – luk2302 Apr 25 '23 at 11:53
  • outside `Payload` object, I tried with `httpMethod:"POST"` or `http:{method:"Post"}` but then it throws some validation error and doesn't make request. I really don't know what to add, where to add ? – micronyks Apr 25 '23 at 11:57
  • No, *inside* the payload because as I said: Lambda does not care about http verbs and your REST api. Actually the request against lambda is ***always*** a POST but that is unrelated to what the request actually contains. Try printing the `event` to see what it normally contains to understand the difference between invoking the lambda and what the lambda actually is invoked *with*. – luk2302 Apr 25 '23 at 11:59
  • `event` object contains `payload object` only. In other words, `path` and `body` only. – micronyks Apr 25 '23 at 12:03
  • I tried with `method: "POST"` inside `payload`, it still calls GET method. – micronyks Apr 25 '23 at 12:18
  • How about within requestContext -> http -> method !? – luk2302 Apr 25 '23 at 12:40
  • Let me try with it. Also, `payload` or entire `req` is received as buffer. In `event`, it shows data as stringify data which is correct but when it hits the controller and I print `req` object, it shows data as buffer. – micronyks Apr 25 '23 at 12:51
  • That is just how express or aws-serverless-express handles things. – luk2302 Apr 25 '23 at 13:02
  • adding `reqeustContext` throws validation error. – micronyks Apr 25 '23 at 13:04
  • You can check this answer: OP mentioned the similar question as I have. Answerer resolved it but I don't understand how with accepted answer : https://stackoverflow.com/questions/56968055/routing-in-a-lambda-function-without-api-gateway – micronyks Apr 25 '23 at 13:06
  • also I found this one : https://stackoverflow.com/questions/72330089/javascript-how-to-specify-http-method-with-aws-lambda-invoke – micronyks Apr 25 '23 at 13:18
  • You have two options here: 1. invoke the API Gateway (or whatever you have in front of your lambda), there you can use normal POST / GET / http verbs just like you are calling an API. 2. invoke the underlying lambda directly means you need to imitate the request that is normally send from the proxy service in front of the lambda *to* the lambda. And `requestContext...` would need to be added to the payload yet again because **Lambda does not care about http verbs and your REST api**. – luk2302 Apr 25 '23 at 13:44
  • I have API gateway setup. Calling lambda directly because I want to perform some heavy, time consuming operations on S3. So trying to call lambda by bypassing API gateway as API gateway has 30s timeout so it always throws `internal server error`. – micronyks Apr 25 '23 at 13:50
  • I receive `event` with `path` & `body` but when I print `req` object inside `getPoints` controller method, it prints some strange object. Looks exactly like: stackoverflow.com/questions/43669913/node-js-how-to-inspect-request-data - `IncomingMessage` as shown in the link. Also, when I print `req.body`, it just prints `{}` empty object. I really need help to understand what is going on or how to receive expected object in `req.body`. – micronyks Apr 25 '23 at 13:53

1 Answers1

3

Your httpMethod should be in payload instead, something like:

lambda.invoke({                            
        FunctionName: 'my-lambda-fn',
        InvocationType: 'RequestResponse',
        Payload: JSON.stringify({
          path: '/api/v5.1/points',
          body: JSON.stringify(points),
          httpMethod: "POST"
        }),
      }).promise();
Ana Teixeira
  • 186
  • 7
  • I think I already tried it but I can re-try and update you. – micronyks May 09 '23 at 05:47
  • I believe you tried method instead of httpMethod, but let me know ;) – Ana Teixeira May 09 '23 at 08:53
  • It looks to be hitting the POST method. Thanks a lot. However, I still get weird object in `request` object in POST method. It is almost exactly like stackoverflow.com/questions/43669913/node-js-how-to-inspect-request-data as mentioned in above one of the comments. Could you pls help to resolve that small part and I will be more than happy to accept your answer. – micronyks May 11 '23 at 09:32
  • FYI, in my real app, I already use `const bodyParser = require('body-parser'); app.use(bodyParser.json({ strict: false })); app.use(express.json()); app.use(express.urlencoded({ extended: true }));`. I will try to change the value from `false` to `true`. – micronyks May 11 '23 at 09:38
  • @micronyks what's wrong with your request object exactly? Possibly you need to send the payload base64 encoded.. – Ana Teixeira May 11 '23 at 16:46
  • I'm having an issue exactly as described here : tackoverflow.com/questions/43669913/node-js-how-to-inspect-request-data The provided solution is already there in my app but still I get the same objected shown in above link. – micronyks May 12 '23 at 04:35
  • This question (https://stackoverflow.com/questions/56968055/routing-in-a-lambda-function-without-api-gateway) is similar to my original question which you already resolved. Still check given answer's first comment. OP was facing the same problem as I'm facing right now. I'm able to hit the POST method. After hitting the POST method, received `req` object is type of buffer. I expected `Payload` with `body` object to be received in req object but it is sending some weird (buffer type) object as shown in above comment's link. – micronyks May 12 '23 at 04:52
  • Well I'm not 100% sure about why it's like this.. but we can see info in https://stackoverflow.com/questions/62502494 1st ans then it's needed to decode like I tried to mention previously JSON.parse(Buffer.from(req.body, 'base64').toString('utf8')); or after some tests with AWS lambda test section, I found something: if we have app.post("/body2", (req, res, next) => {...}, we print the req.body and we have the Buffer like you mention; if we have app.post("/body", (event) => {...}) and we print the event.body the json is there. Can you please confirm if it works for you? – Ana Teixeira May 12 '23 at 13:11
  • Sure. Let me confirm it to you today. – micronyks May 15 '23 at 06:07
  • Thanks for coming so far to help. I tried with your suggestions and gave my observations here https://stackblitz.com/edit/js-luggey?file=index.js kindly check index.js file where I have written entire observation. I think some core concept is missing in passing the right object. – micronyks May 15 '23 at 07:51
  • hey. Regarding 1), a side question, which middlewares are you using? It's strange the error, IncomingMessage is all the request, not the req.body :/ In the other hand, 2), did you try `exports.postPoints = (event, res, next)` and use event instead of req? Do you need req specifically or just res and next? let me know if it works for you ;) – Ana Teixeira May 15 '23 at 09:57
  • I don't think it matters if I keep `event` or `req` as first parameter/argument. But let me try n update you trmw morning. – micronyks May 15 '23 at 18:09
  • you can check what arrives to your node and see that the body at first level (req) is encoded and inside the event object is "readable". e.g. `url: '/body', method: 'POST', statusCode: null,` **`body: ,`** `apiGateway: { event: { path: '/body',` **`body: '{"a":"A"}',`** `httpMethod: 'POST', requestContext: [Object], headers: {} }, context: { ...` – Ana Teixeira May 16 '23 at 11:17
  • if for example you don't want to change your method signature and remain with `exports.postPoints = (req, res, next)` then you can achieve event body with `req.apiGateway.event.body`. – Ana Teixeira May 16 '23 at 11:29
  • Do you call POST method through api gateway? If not, how can you get `body` object inside `req.apiGateway...`? My main objective is to bypass api gaetway. otherwise it works fine with it.... – micronyks May 16 '23 at 12:12
  • What I remember is: in my case body is an empty object. I need to confirm though. – micronyks May 16 '23 at 12:13
  • I'm not using apiGateway, check what you receive in the lambda function and let me know – Ana Teixeira May 17 '23 at 09:12
  • Sure. actually I'm busy on some production issues. Just give me 1 or 2 days to test it. I'll update you. Thanks.... – micronyks May 18 '23 at 04:41
  • Thank you @Ana Teixeira. In my case I was trying to invoke `serverless` for local lambda testing. The express instance complained that `method` was undefined even though I added it to the query. The magic word was `httpMethod` which serverless stuffs into the expressRequest object. > serverless invoke local -f graphql -p test/query.json – pmont Jul 16 '23 at 04:51