LambdaUtils

Lambda handler middleware.

Overview

There’s a lot of boilerplate in Lambda handlers. This collection of utility functions leverages the great Middy library to add middleware functionality to Lambda handlers. You can extend it with your own middleware.

Middy gives you a great start as a solid middleware framework, but by itself you are still repeating the middleware registrations on each handler, its exception handler only works with errors created by the http-errors package, and you still have to format your response in the shape required by API Gateway.

LambadUtils takes Middy further and is extendable so that you can add your own middleware (authentication & authorization, maybe?) on top of it.

Used with API Gateway, the included middlewares:

  • Set CORS headers.
  • Normalize incoming headers to mixed-case
  • If incoming content is JSON text, replaces event.body with parsed object.
  • Ensures that event.queryStringParameters and event.pathParameters are defined, to avoid TypeErrors.
  • Ensures that handler response is formatted properly as a successful API Gateway result.
    • Unique to LambdaUtils!
    • Simply return what you want as the body of the HTTP response.
  • Catch http-errors exceptions into proper HTTP responses.
  • Catch other exceptions and return as HTTP 500.
    • Unique to LambdaUtils!
    • Besides providing better feedback to the client, not throwing an exception out of your handler means that your instance will not be destroyed and suffer a cold start on the next invocation.
  • Leverages async syntax.

See Middy middlewares for details on those. Not all Middy middlewares are in this implementation, only common ones that are generally useful in all APIs. You may extend LambdaUtils’s wrapApiHandler() function in your projects, or use it as an example to write your own, to add more middleware!

WARNING: Middy has, historically, introduced some breaking changes in previous minor version bumps. LambdaUtils has been built and tested with Middy 0.29.0, and should continue to work with future versions, but bumping your Middy version beyond 0.29.0 may produce some weird errors.

LambdaUtils depends on two other utilities to work:

Install

npm install @sailplane/lambda-utils @sailplane/logger middy

Examples

General use

import {APIGatewayEvent} from 'aws-lambda';
import * as LambdaUtils from "@sailplane/lambda-utils";
import * as createError from "http-errors";

export const hello = LambdaUtils.wrapApiHandler(async (event: LambdaUtils.APIGatewayProxyEvent) => {
    // These event objects are now always defined, so don't need to check for undefined. 🙂
    const who = event.pathParameters.who;
    const points = parseInt(event.queryStringParameters.points || 0);

    if (points > 0) {
        let message = 'Hello ' + who;
        for (; points > 0; --points)
            message = message + '!';

        return {message};
    }
    else {
        // LambdaUtils will catch and return HTTP 400
        throw new createError.BadRequest('Missing points parameter');
    }
});

See More Examples for another example.

Extending LambdaUtils for your own app

import {ProxyHandler} from "aws-lambda";
import * as LambdaUtils from "@sailplane/lambda-utils";
import {userAuthMiddleware} from "./user-auth"; //you write this

export interface WrapApiHandlerOptions {
    noUserAuth?: boolean;
    requiredRole?: string;
}

export function wrapApiHandlerWithAuth(options: WrapApiHandlerOptions,
                                       handler: LambdaUtils.AsyncProxyHandler): ProxyHandler {
    let wrap = LambdaUtils.wrapApiHandler(handler);

    if (!options.noUserAuth) {
        wrap.use(userAuthMiddleware(options.requiredRole));
    }

    return wrap;
}

Type Declarations

import { APIGatewayProxyEvent as AWS_APIGatewayProxyEvent, APIGatewayEvent, Context, ProxyResult } from "aws-lambda";
import * as middy from "middy";
/** Define the async version of ProxyHandler */
export declare type AsyncProxyHandler = (event: APIGatewayEvent, context: Context) => Promise<any>;
/**
 * Casted interface for APIGatewayProxyEvents as converted through the middleware
 */
export interface APIGatewayProxyEvent extends AWS_APIGatewayProxyEvent {
    /**
     * HTTP Request body, parsed from a JSON string into an object.
     */
    body: any | null;
    /**
     * HTTP Path Parameters, always defined, never null
     */
    pathParameters: {
        [name: string]: string;
    };
    /**
     * HTTP URL query string parameters, always defined, never null
     */
    queryStringParameters: {
        [name: string]: string;
    };
}
/**
 * Middleware to handle any otherwise unhandled exception by logging it and generating
 * an HTTP 500 response.
 *
 * Fine tuned to work better than the Middy version.
 */
export declare const unhandledExceptionMiddleware: () => {
    onError: (handler: middy.HandlerLambda<AWS_APIGatewayProxyEvent, import("aws-lambda").APIGatewayProxyResult>, next: middy.NextFunction) => void;
};
/**
 * Middleware to allow an async handler to return its exact response body.
 * This middleware will wrap it up as an APIGatewayProxyResult.
 * Must be registered as the last (thus first to run) "after" middleware.
 */
export declare const resolvedPromiseIsSuccessMiddleware: () => {
    after: (handler: middy.HandlerLambda<AWS_APIGatewayProxyEvent, import("aws-lambda").APIGatewayProxyResult>, next: middy.NextFunction) => void;
};
/**
 * Wrap an API Gateway proxy lambda function handler to add features:
 * - Set CORS headers.
 * - Normalize incoming headers to mixed-case
 * - If incoming content is JSON text, replace event.body with parsed object.
 * - Ensures that event.queryStringParameters and event.pathParameters are defined,
 *   to avoid TypeErrors.
 * - Ensures that handler response is formatted properly as a successful
 *   API Gateway result.
 * - Catch http-errors exceptions into proper HTTP responses.
 * - Catch other exceptions and return as HTTP 500
 *
 * This wrapper includes commonly useful middleware. You may further wrap it
 * with your own function that adds additional middleware, or just use it as
 * an example.
 *
 * @param handler async function to wrap
 * @see https://middy.js.org/docs/middlewares.html
 * @see https://www.npmjs.com/package/http-errors
 */
export declare function wrapApiHandler(handler: AsyncProxyHandler): middy.Middy<APIGatewayEvent, ProxyResult>;
/**
 * Construct the object that API Gateway wants back upon a successful run. (HTTP 200 Ok)
 *
 * This normally is not needed. If the response is simply the content to return as the
 * body of the HTTP response, you may simply return it from the handler given to
 * #wrapApiHandler(handler). It will automatically transform the result.
 *
 * @param result object to serialize into JSON as the response body
 * @returns {ProxyResult}
 */
export declare function apiSuccess(result?: any): ProxyResult;
/**
 * Construct the object that API Gateway wants back upon a failed run.
 *
 * Often, it is simpler to throw an http-errors exception from your #wrapApiHandler
 * handler.
 *
 * @see https://www.npmjs.com/package/http-errors
 * @param statusCode HTTP status code, between 400 and 599.
 * @param message string to return in the response body
 * @returns {ProxyResult}
 */
export declare function apiFailure(statusCode: number, message?: string): ProxyResult;