Skip to content

Serverless Node.js — Building Lambda Functions with JavaScript

DodaTech Updated 2026-06-28 4 min read

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

  1. Forgetting to handle promises: Async errors that are not caught cause unhandled promise rejections and silent failures.

  2. Using synchronous fs in handlers: Blocking operations in Node.js Lambda freeze the event loop. Use async versions.

  3. Not bundling with webpack: Including node_modules directly increases deployment size 10x. Use webpack or Esbuild.

  4. Missing callback for non-async handlers: Non-async handlers must call the callback parameter or the function times out.

  5. Storing sensitive data in package.json: API keys in package.json are exposed in the deployment package. Use environment variables.

Practice Questions

  1. 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.

  2. Why use webpack for Node.js Lambda? Webpack bundles only the code used, reducing deployment size and cold start time significantly.

  3. 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.

  4. How do you access environment variables in Node.js? Use Process.env.VARIABLE_NAME or process.env["VARIABLE_NAME"].

  5. 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

What Node.js versions does Lambda support?

Node.js 18 and 20 are the current supported versions. Node.js 16 reached end-of-life.

Can I use ES modules in Lambda?

Yes. Use .mjs extension or set type: module in package.json for ES module support.

Does Lambda support npm workspaces?

Yes, but bundling with webpack or esbuild is recommended for deployment.

How do I debug Node.js Lambda locally?

Use serverless-offline, or the --inspect flag with Node.js debugger.

Can I use TypeScript with Lambda?

Yes. Use serverless-plugin-typescript or compile with tsc before deployment.

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