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 .
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' })
};
}
};