Serverless Node.js — Building Lambda Functions with JavaScript
In this tutorial, you will learn about Serverless Node.js. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js is the most popular Lambda runtime, offering excellent cold start performance, a massive npm ecosystem, and native async/await support for building efficient serverless APIs.
What You'll Learn
By the end of this lesson you will understand Node.js Lambda patterns, dependency bundling with webpack, async handler patterns, ES module support, and optimizing Node.js functions for performance.
Why It Matters
Node.js has the fastest cold start of major Lambda runtimes and the largest package ecosystem. Understanding Node.js-specific patterns helps you build efficient, maintainable serverless applications.
Real-World Use
DodaBrowser's API gateway endpoints use Node.js Lambda functions because of their fast cold starts and excellent performance for HTTP request-response patterns.
// handler.js
// Basic Node.js Lambda handler
exports.handler = async (event, context) => {
console.log("Event:", JSON.stringify(event, null, 2));
const response = {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: "Hello from Node.js Lambda",
eventType: typeof event,
}),
};
return response;
};
// Test
const result = await exports.handler({ test: true }, {});
console.log(result.body);
Expected output:
Event: {
"test": true
}
{"message": "Hello from Node.js Lambda", "eventType": "object"}
Async/Await Patterns
Node.js Lambda supports async handlers natively. Use await for all async operations and try/catch for error handling.
// async_patterns.js
// Async/await patterns for Lambda
const https = require("https");
function httpGet(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => resolve(JSON.parse(data)));
}).on("error", reject);
});
}
exports.handler = async (event) => {
try {
const apiUrl = event.url || "https://api.github.com";
const data = await httpGet(apiUrl);
return {
statusCode: 200,
body: JSON.stringify({ url: apiUrl, data }),
};
} catch (error) {
console.error("Request failed:", error.message);
return {
statusCode: 502,
body: JSON.stringify({ error: "External API call failed" }),
};
}
};
Dependency Management
Use npm for dependencies and webpack for bundling to reduce deployment size.
// package.json
{
"name": "my-serverless-function",
"version": "1.0.0",
"dependencies": {
"aws-sdk": "^2.1500.0",
"lodash": "^4.17.21",
"axios": "^1.6.0"
},
"devDependencies": {
"serverless-webpack": "^5.13.0",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
}
// Using dependencies
const AWS = require("aws-sdk");
const _ = require("lodash");
const axios = require("axios");
const dynamodb = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
const params = {
TableName: process.env.TABLE_NAME,
Key: { id: event.pathParameters.id },
};
const result = await dynamodb.get(params).promise();
const user = _.pick(result.Item, ["id", "name", "email"]);
return {
statusCode: 200,
body: JSON.stringify(user),
};
};
Common Mistakes
Forgetting to handle promises: Async errors that are not caught cause unhandled promise rejections and silent failures.
Using synchronous fs in handlers: Blocking operations in Node.js Lambda freeze the event loop. Use async versions.
Not bundling with webpack: Including node_modules directly increases deployment size 10x. Use webpack or Esbuild.
Missing callback for non-async handlers: Non-async handlers must call the callback parameter or the function times out.
Storing sensitive data in package.json: API keys in package.json are exposed in the deployment package. Use environment variables.
Practice Questions
How do you handle async operations in Node.js Lambda? Use async/await with try/catch blocks. Return the response from the async handler function.
Why use webpack for Node.js Lambda? Webpack bundles only the code used, reducing deployment size and cold start time significantly.
What is the callback parameter in Lambda handlers? The third parameter for non-async handlers. Call it with (null, response) on success or (error) on failure.
How do you access environment variables in Node.js? Use Process.env.VARIABLE_NAME or process.env["VARIABLE_NAME"].
Challenge: Create a Node.js Lambda function that uses axios to fetch data from an external API, transforms it with lodash, and stores the result in DynamoDB.
FAQ
Mini Project
Create a Node.js Lambda function that handles CRUD operations for a DynamoDB-backed todo list API.
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB.DocumentClient();
const TABLE_NAME = process.env.TABLE_NAME || "todos";
exports.handler = async (event) => {
const method = event.httpMethod;
const body = event.body ? JSON.parse(event.body) : {};
if (method === "GET") {
const result = await dynamodb.scan({ TableName: TABLE_NAME }).promise();
return { statusCode: 200, body: JSON.stringify(result.Items) };
}
if (method === "POST") {
const item = { id: Date.now().toString(), ...body, completed: false };
await dynamodb.put({ TableName: TABLE_NAME, Item: item }).promise();
return { statusCode: 201, body: JSON.stringify(item) };
}
if (method === "PUT") {
const { id, ...updates } = body;
await dynamodb.update({
TableName: TABLE_NAME,
Key: { id },
UpdateExpression: "SET #n = :name, completed = :completed",
ExpressionAttributeNames: { "#n": "name" },
ExpressionAttributeValues: { ":name": updates.name, ":completed": updates.completed },
}).promise();
return { statusCode: 200, body: JSON.stringify({ id, ...updates }) };
}
if (method === "DELETE") {
await dynamodb.delete({ TableName: TABLE_NAME, Key: { id: event.pathParameters.id } }).promise();
return { statusCode: 204, body: "" };
}
return { statusCode: 405, body: JSON.stringify({ error: "Method not allowed" }) };
};
What's Next
Next: Serverless Database for data persistence patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro