Search code examples
node.jsamazon-web-servicesaws-lambdaserverless-frameworkamazon-dynamodb-local

Passing query params to aws lambda function return wrong results in postman


I am just starting with aws lambda functions with serverless framework. I am trying to run lambda functions locally using dynamodb-local and serverless-offline.

Problem:

I am trying to pass query param to a lambda function which queries the dynamo-db local. I can see the query results in the console.log(fetchResult) in terminal but when returning the the desired value in header object, I don't see it in postman response.

serverless.yaml

service: url-shortener-service
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs18.x
  stage: local
  region: us-east-1 # Replace with your desired AWS region
  environment:
    DYNAMODB_ENDPOINT: http://localhost:9981 # DynamoDB Local endpoint

custom:
  dynamodb:
    start:
      port: 9981
      inMemory: true
    migration:
      dir: './migrations'

plugins:
  - serverless-offline
  - serverless-dynamodb-local
  

functions:
  shortenUrl:
    handler: shorten-url.handler
    events:
      - http:
          path: shortenURL
          method: post

  recordHit:
    handler: record-hit.handler
    events:
      - http:
          path: recordHit
          method: get

  test:
    handler: test.handler
    events:
      - http:
          path: test
          method: post

resources:
  Resources:
    ShortenedUrlsTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: shortenedUrls
        AttributeDefinitions:
          - AttributeName: shortUrl
            AttributeType: S
        KeySchema:
          - AttributeName: shortUrl
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 5
          WriteCapacityUnits: 5

lambda function:

import AWS from 'aws-sdk';
import dotenv from 'dotenv';
dotenv.config();
const dynamoDB =  new AWS.DynamoDB.DocumentClient({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  endpoint: process.env.END_POINT,
  region: process.env.AWS_REGION
});

const tableName = 'shortenedUrls';

export const handler = async (event) => {
  const { shortUrl } = event.queryStringParameters;

  const params = {
    TableName: tableName,
    Key: {
      shortUrl,
    },
    UpdateExpression: 'SET hits = hits + :incr',
    ExpressionAttributeValues: {
      ':incr': 1,
    },
    ReturnValues: 'UPDATED_NEW',
  };

  try {
    
    await dynamoDB.update(params).promise();

    const fetchParams = {
      TableName: tableName,
      Key: {
        shortUrl,
      },
    };

    const fetchResult = await dynamoDB.get(fetchParams).promise();
    console.log(fetchResult);

    if (!fetchResult.Item) {
      console.log('fetchedResult === ', fetchResult.Item.originalUrl);
      return {
        statusCode: 404,
        body: JSON.stringify({ message: 'Short URL not found' }),
      };
    }

    return {
      statusCode: 301,
      headers: {
        Location: fetchResult.Item.originalUrl
      },
    };
  } catch (error) {
    console.error(error);
    return {
      statusCode: 500,
      body: JSON.stringify({ message: 'Internal Server Error' })
    };
  }
};

Here is what i am getting in postman as response and headers.

response:

{
    "currentRoute": "get - /local/www.google.com",
    "error": "Serverless-offline: route not found.",
    "existingRoutes": [
        "get - /local/recordHit",
        "post - /local/shortenURL",
        "post - /local/test"
    ],
    "statusCode": 404
}

Code is trying to redirect to the current url. I just need to return the url and redirect the user to this orignal url. I am not sure what needs to be done here.

When I hit the url http://localhost:3000/local/recordHit?shortUrl=n26L6FWr from browser it gives this output.


Solution

  • So i have figured it out. Issue is not with the way query parameter is handled but how to urls are being handled. I have added some conditional checks to check url is valid or not using validURL and URL modules.

    First thing i did was to check if url contains http or https and prefix it if not found and then check the validity of url.

    Here are the modifications done to the recordHit handler.

    import AWS from 'aws-sdk';
    import dotenv from 'dotenv';
    import { URL }  from 'url';
    dotenv.config();
    const dynamoDB =  new AWS.DynamoDB.DocumentClient({
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
      endpoint: process.env.END_POINT,
      region: process.env.AWS_REGION
    });
    
    const tableName = 'shortenedUrls';
    
    export const handler = async (event) => {
      
      const { shortUrl } = event.queryStringParameters;
    
      if (!shortUrl) {
        return { statusCode: 400, body: JSON.stringify({ message: 'Missing or empty shortUrl value' }) };
      }
    
      const params = {
        TableName: tableName,
        Key: {
          shortUrl,
        },
        UpdateExpression: 'SET hits = hits + :incr',
        ExpressionAttributeValues: {
          ':incr': 1,
        },
        ReturnValues: 'UPDATED_NEW',
      };
    
      try {
        
        await dynamoDB.update(params).promise();
    
        const fetchParams = {
          TableName: tableName,
          Key: {
            shortUrl,
          },
        };
    
        const fetchResult = await dynamoDB.get(fetchParams).promise();
    
        if (!fetchResult.Item) {
          return {
            statusCode: 404,
            body: JSON.stringify({ message: 'Short URL not found' }),
          };
        }
    
    
        // Prepend 'http://' if the scheme is missing
        let normalizedUrl = fetchResult.Item.originalUrl.startsWith('http://') || fetchResult.Item.originalUrl.startsWith('https://') ? fetchResult.Item.originalUrl : `http://${fetchResult.Item.originalUrl}`;
        normalizedUrl = new URL(normalizedUrl).toString();
    
        return {
          statusCode: 301,
          headers: {
            Location: normalizedUrl
          },
        };
      } catch (error) {
        console.error(error);
        return {
          statusCode: 500,
          body: JSON.stringify({ message: 'Internal Server Error' })
        };
      }
    };