Search code examples
typescriptaws-lambdalambdaserverless

How invoke serverless function locally passing `body` params and retrieve those params?


I am trying to test a lambda function locally, using serverless framework. This function is attached to a POST endpoint, defined as:

createCat:
  handler: app/handler.createCat
  events:
    - http:
        path: cats/
        method: post

I invoke the function locally a according to this source:

# Command line
serverless invoke local --function createCat --path local-invoke/createCat.json --stage local

# createCat.json
{
  "body": {
    "name": "Cat cat blue",
    "age": 4,
    "color": "blue"
  }
}

I am trying to parse the body and get the items like it is shown here, but it does not work:

import { Context } from 'aws-lambda';

const createCat = async (event: any, context?: Context) => {
  try{
  const data  = JSON.parse(event.body)

  let name = data?.name
  let age = Number(data?.age)
  let color = data?.color

  return {
    statusCode: 500,
    message: data
  }
  }
  catch(error) {
  return {
    statusCode: 500,
    body: JSON.stringify({
      message: getMessageFromError(error)
    })
  }
  } 
}

I am getting this output, instead of the parsed body:

{
    "statusCode": 500,
    "body": "{\"message\":\"Unexpected token l in JSON at position 0\"}"
}

Serverless documentation on local invoke does not touch on how to pass/retrieve body params either.

What am I doing wrong here?


Solution

  • When invoking the deployed lambda, event.body is a scaped JSON string. However, when invoking locally with serverless invoke local, it is an object literal in javascript, and the parsing error happens since it is trying to parse this object literal.

    I wrote a helper function to help cover both cases seamlessly:

    // types.ts
    export interface ParsedEvent {
      body: any | undefined,
      pathParameters: any | undefined
    }
    
    // util.ts
    import { ParsedEvent } from "./types"
    
    export const parseEvent = (event: any) : ParsedEvent => {
      let parsedEvent : ParsedEvent = {
        body: undefined,
        pathParameters: undefined
      }
    
      parsedEvent.pathParameters = event?.pathParameters
    
      // Parse body if it is stringified JSON
      if(typeof event?.body === 'string' )
      {   
        parsedEvent.body = JSON.parse(event?.body)
      } else {
        parsedEvent.body = event?.body
      }
    
      return parsedEvent
    }
    

    and use it as:

    import { Context } from 'aws-lambda';
    import { parseEvent } from './util.js';
    
    const createCat = async (event: any, context?: Context) => {
      let parsedEvent = parseEvent(event)
      
      // body params
      let name = parsedEvent.body?.name
      let age = parsedEvent.body?.age
      
      // path params
      let id = parsedEvent.pathParameters?.id
    }