Skip to content

macromania/lambda-result

Repository files navigation

Lambda Result

With respect to API Gateway Lambda Proxy results, there are certain conventions we need to provide from Lambda response detailed at: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html

In order to achieve this a simple lambda should always return success and error response in the following format:

Success

statusCode: result.statusCode,
body: JSON.stringify({
    status: result.status,
    data: result.content,
    error: result.error,
}),

Error

statusCode: 401,
body: JSON.stringify({
    errorType: 'Anauthorized',
    errorMessage: 'No Access',
}),

This can be achieved by wrapping the service layer methods with a ServiceResult entity influenced by Java’s Optional pattern which is used for explicitly expressing the possible result of an action and keeping Exceptions as exceptional as possible at infrastructure level or new runtime issues that needs fixing.

A simple ServiceResult wrapper could be like below:

export interface ErrorResponse {
    errorMessage: string;
    errorType: string;
    details?: any;
}

export class ServiceResult<T> {
    public readonly success: boolean;
    public readonly failure: boolean;
    public readonly statusCode: number;
    public readonly status: string;
    public readonly content: T | null;
    public readonly error: ErrorResponse | null;

    private constructor(content: T | null, error: ErrorResponse | null, success: boolean) {
        this.content = content;
        this.error = error;
        this.success = success;
        this.failure = !success;
        this.statusCode = success ? 200 : 400;
        this.status = success ? "Success" : "Error";
    }

    public static Succeeded<T>(content: T): ServiceResult<T> {
        return new ServiceResult<T>(content, null, true);
    }

    public static Failed<T>(error: ErrorResponse): ServiceResult<T> {
        return new ServiceResult<T>(null, error, false);
    }
}

Recently, I have used this pattern a lot and am keen to provide this as a public npm module for your Serverless API layer.

Benefits

  • Human readable error codes returned by conventions
  • AWS CloudWatch and API Gateway compatible conventions
  • Developer productivity

Usage

import { APIGatewayProxyEvent } from 'aws-lambda';
import { SuccessResult, ErrorResult } from 'lambda-result';
import { Service } from './service';
import { ServiceValidator } from './validator';

exports.handler = async (event: APIGatewayProxyEvent) => {
    // Validate incoming payload and convert to request
    const request = ServiceValidator.validate(event);
    if (request.failure) {
        return ErrorResult.responseFromFailure(request.error);
    }

    // Run request via server (Adaptor pattern)
    const result = Service.doSomething(request.content!);

    // If service results a failure/error, return with API Gateway Proxy compatible result
    if (result.failure) {
        console.error(result);
        return ErrorResult.responseFromFailure(result.error);
    }

    // Return an API Gateway Proxy compatible result
    return SuccessResult.response(result);
};

The same simplicity will be achieved at the service layer with something like this:

import { ServiceResult } from 'lambda-result';
import { ServiceRequest, ServiceResponse } from './service-types';

export class Service {
    public static doSomething(request: ServiceRequest): ServiceResult<ServiceResponse> {
        const message: ServiceResponse = {
            message: `Hello ${request.name}!`,
        };
        return ServiceResult.Succeeded(message);
    }
}