How to Debug AWS Lambda Locally as a Server with Step-by-Step Guide in Typescript

Denys MedvedievDenys Medvediev
4 min read

Introduction

In this article, we’ll explore an efficient way to debug AWS Lambda locally using the "Lambda as a Server" (LAAS) approach. This method leverages Vite to simulate AWS Lambda functions as a local server, making it easier to test and debug REST APIs.


Challenge: Devs faced difficulty debugging Lambda functions locally without deploying, even with methods described in articles like 50 Shades of Debugging AWS Node.js Lambdas. Using LAAS and this demo repository, we solved this problem. Follow this guide to do the same.


Prerequisites

Before getting started, ensure you have:

  • Node.js (>= 16.x)

  • npm or yarn

Installation

Clone the demo project repository:

git clone https://github.com/medvedevden1s/LAAS.git
cd LAAS
npm install

Setting Up the Local Environment

The heart of this solution lies in vite.config.ts. This configuration file acts as a local server to route HTTP requests to the appropriate Lambda handlers.

Here’s how it’s configured:

tsCopy codeimport { defineConfig } from 'vite';
import { handler as CreatePostLambda } from './app/lambdas/CreatePostLambda';
import { handler as GetPostLambda } from './app/lambdas/GetPostLambda';
import { handler as GetAllPostsV1Lambda } from './app/lambdas/GetAllPostsV1Lambda';
import { handler as GetAllPostsV2Lambda } from './app/lambdas/GetAllPostsV2Lambda';

export default defineConfig({
    server: {
        port: 8080,
    },
    plugins: [
        {
            name: 'lambda-invoker',
            configureServer(server) {
                server.middlewares.use(async (req, res) => {
                    const url = new URL(req.url ?? '', `http://${req.headers.host}`);
                    const path = url.pathname;
                    const method = req.method;

                    const event = {
                        httpMethod: method,
                        path,
                        headers: req.headers,
                        body: method === 'POST' ? await getRequestBody(req) : null,
                    };

                    let result;
                    switch (true) {
                        case method === 'POST' && path === '/v1/posts':
                            result = await CreatePostLambda(event, {});
                            break;
                        case method === 'GET' && /^\/v1\/posts\/[^/]+$/.test(path):
                            result = await GetPostLambda(event, {});
                            break;
                        case method === 'GET' && path === '/v1/posts':
                            result = await GetAllPostsV1Lambda(event, {});
                            break;
                        case method === 'GET' && path === '/v2/posts':
                            result = await GetAllPostsV2Lambda(event, {});
                            break;
                        default:
                            res.statusCode = 404;
                            return res.end(JSON.stringify({ message: 'Not Found' }));
                    }

                    res.statusCode = result.statusCode;
                    res.setHeader('Content-Type', 'application/json');
                    res.end(result.body);
                });
            },
        },
    ],
});

function getRequestBody(req: any): Promise<string> {
    return new Promise((resolve, reject) => {
        let body = '';
        req.on('data', (chunk) => (body += chunk));
        req.on('end', () => resolve(body));
        req.on('error', reject);
    });
}

This file routes HTTP requests to Lambda handlers based on the HTTP method and endpoint.


Implementing Lambda Functions

We’ll create Lambda handlers for the following:

  1. Create a blog post (POST /v1/posts)

  2. Retrieve a blog post by ID (GET /v1/posts/{id})

  3. List all blog posts for version 1 (GET /v1/posts)

  4. List all blog posts for version 2 (GET /v2/posts)

CreatePostLambda.ts

tsCopy codeimport { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (): Promise<APIGatewayProxyResult> => ({
    statusCode: 201,
    body: JSON.stringify({ message: 'Blog post created!' }),
});

GetPostLambda.ts

tsCopy codeimport { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => ({
    statusCode: 200,
    body: JSON.stringify({ id: event.pathParameters?.id, title: 'My Post', content: 'Hello World' }),
});

GetAllPostsV1Lambda.ts

tsCopy codeexport const handler = async (): Promise<APIGatewayProxyResult> => ({
    statusCode: 200,
    body: JSON.stringify([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]),
});

GetAllPostsV2Lambda.ts

tsCopy codeexport const handler = async (): Promise<APIGatewayProxyResult> => ({
    statusCode: 200,
    body: JSON.stringify([{ id: 1, title: 'Post 1', version: 'v2' }, { id: 2, title: 'Post 2', version: 'v2' }]),
});

Running and Testing Locally

Start the server:

npm run dev

Use the following curl commands to test:

  • Create a Post:

      curl -X POST http://localhost:8080/v1/posts \
           -H "Content-Type: application/json" \
           -d '{"title": "My Post", "content": "Hello World"}'
    
  • Get a Post by ID:

      curl -X GET http://localhost:8080/v1/posts/1
    
  • Get All Posts (v1):

      curl -X GET http://localhost:8080/v1/posts
    
  • Get All Posts (v2):

      curl -X GET http://localhost:8080/v2/posts
    

Benefits of the LAAS Approach

  • 1. Reduced Iteration Time

    2. Improved Developer Experience

    3. Enhanced Debugging Capabilities

    4. Cost-Efficiency in Testing and Development

    5. Food for Thought: Deploying Lambdas as a Server

    • While AWS Lambda is designed for event-driven and serverless workloads, LAAS introduces an intriguing possibility: deploying Lambda functions as a traditional server.

      • Scenario: After understanding the traffic patterns, you could run your Lambda functions as a server for constant traffic instead of paying per invocation.

      • Considerations: This approach could reduce your bill under certain conditions but requires careful monitoring and planning.

      • Disclaimer: This method is not officially recommended, nor has it been widely tested. However, it's a concept worth exploring for teams looking to optimize costs.


Conclusion

The "Lambda as a Server" approach provides a simple and effective way to debug AWS Lambda functions locally. With this method, you can speed up development cycles and ensure your code is production-ready.

Use the LAAS repository to get started today, and explore the benefits of debugging locally. Let us know your thoughts in the comments, and share this article to help others in the AWS Lambda community.

0
Subscribe to my newsletter

Read articles from Denys Medvediev directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Denys Medvediev
Denys Medvediev

Cloud Architect | Solutions Architect