Prefinem/lambdify

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# lambdify

[![Version](https://img.shields.io/npm/v/lambdify?style=for-the-badge)](https://npmjs.org/package/lambdify)

[![Build Status](https://img.shields.io/github/workflow/status/Prefinem/lambdify/ci?style=for-the-badge)](https://github.com/Prefinem/lambdify/actions) [![Maintainability](https://img.shields.io/codeclimate/coverage-letter/Prefinem/lambdify?style=for-the-badge)](https://codeclimate.com/github/Prefinem/lambdify/maintainability) [![Test Coverage](https://img.shields.io/codecov/c/github/Prefinem/lambdify?style=for-the-badge)](https://codecov.io/gh/Prefinem/lambdify)

![Monthly Downloads](https://img.shields.io/npm/dm/lambdify?style=for-the-badge)

![Issues](https://img.shields.io/github/issues/Prefinem/lambdify?style=for-the-badge) ![Pull Requests](https://img.shields.io/github/issues-pr/Prefinem/lambdify?style=for-the-badge)

![Dependencies](https://img.shields.io/david/Prefinem/lambdify?style=for-the-badge) ![Dev Dependencies](https://img.shields.io/david/dev/Prefinem/lambdify?style=for-the-badge)

Lambdify is a set of tools that makes building and consuming AWS Lambda functions easier.

# NOTICE

**THESE ARE v4 DOCS. Please see [branch v3](https://github.com/Prefinem/lambdify/tree/v3) for v3 Docs**

**Master branch is v4. v4 drops lambdify-fn and lambdify-utils and is moving to a single package.**

If you used methods from lambdify-fn, you can use [afpf](https://github.com/Prefinem/afpf) instead

**_WARNING: Version >= 3.0.0 is for Node 8.10 or greater_**

# Getting Started

Basic HTTP Lambda Function - JSON Response

```js
const lambdify = require('lambdify');

const helloWorld = (request, response) => {
  response.json({ message: `Hello User, I see that you are coming from IP: ${request.getIp()}` });

  return response;
};

exports.handler = lambdify(helloWorld);
```

Basic HTTP Lambda Function - HTML Response

```js
const lambdify = require('lambdify');

const helloWorld = (request, response) => {
  response.html(`Hello User, I see that you are coming from IP: ${request.getIp()}`);

  return response;
};

exports.handler = lambdify(helloWorld);
```

Basic S3 Trigger

```js
const lambdify = require('lambdify');
const AWS = require('aws-sdk');
const s3 = new AWS.S3({ apiVersion: '2006-03-01' });

const run = async (request) => {
  const { bucket, key } = request.getS3();
  const file = await s3.getObject({ Bucket: bucket, Key: key }).promise();

  if (file && file.Body) {
    // Do something with the file
  }
};

exports.handler = lambdify(run);
```

# Installation

    npm i lambdify

or

    yarn add lambdify

# `lambdify(fn, middleware = [])`

Lambdify accepts a function and an array of middleware

```js
const lambdify = require('lambdify');
const errorMiddleware = require('errorMiddleware');

const run = (request, response) => {
  response.json({ foo: 'bar' });

  return response;
};

exports.handler = lamdify(run, [errorMiddleware]);
```

## request

This is the request object that is built from the lambda event

- `request.get(name)` - Get a value of `name` from a basic key value store
- `request.getAuthToken()` - Get the authorization token from the request
- `request.getBody()` - Get the body of the event and parse into an object if JSON
- `request.getContext()` - Get the lambda context
- `request.getCookie(name)` - Get value of cookie `name` from API Gateway Request
- `request.getCookies()` - Get all cookies from API Gateway Request
- `request.getEvent()` - Get lambda event
- `request.getHeader(name)` - Get value of header `name` from API Gateway Request
- `request.getHeaders()` - Get all headers from API Gateway Request
- `request.getIp()` - Get remote ip (handles X-Forwarded-For)
- `request.getMethod()` - Get HTTP request method from API Gateway Request
- `request.getPath()` - Get URL path from API Gateway Request
- `request.getPathParams()` - Get path paramaters from API Gateway Request
- `request.getQueryParam(name)` - Get value of query parameter `name` from API Gateway Request
- `request.getQueryParams()` - Get query parameters from API Gateway Request
- `request.getS3()` - Get bucket and key from S3 Trigger
- `request.getSns()` - Get record from SNS Trigger
- `request.getSqs()` - Get records from SQS Trigger
- `request.getUa()` - Get UserAgent from API Gateway Request
- `request.set(name, value)` - Set a `value` of `name` into a basic key value store

## response

This is the response object that must be returned from your lambda function (or middleware)

Methods `binary`, `html`, `json`, `redirect`, `xml` can be returned directly

- `response.binary(base64 | buffer, contentType)`- Build a binary response
- `response.enableCors()`- Enable CORS for an API Gateway response
- `response.getBody()`- Get the body of the response
- `response.getHeader(name)`- Get the value of header `name`
- `response.getHeaders()`- Get all headers
- `response.getResponse()`- Get the lambda response object
- `response.getStatusCode()`- Get the status code of the response
- `response.html(body)`- Build an html response
- `response.json(body)`- Build a json response
- `response.file(filePath, contentType)`- Sends file
- `response.redirect(url, statusCode = 302)`- Build a redirect response
- `response.setBinaryResponse(value)`- Set the response as a binary response for API Gateway
- `response.setBody(body)`- Set the body of the response
- `response.setHeader(name, value)`- Set `value` of header `name`
- `response.setStatusCode(value)`- Set the status code of the response
- `response.xml(body)`- Build an xml response

# Middleware

Middleware allows you to wrap your function with another function in such a way that you can execute code before or after your function runs. This is useful for instantiating database connections or error handling

### Example: simple middleware

```js
const middleware = async (req, res, next) => {
  const response = await next(req, res);

  response.setHeader('Cookie', 'test=this');

  return response;
};

module.exports = middleware;
```

Usage:

```js
const lambdify = require('lambdify');
const middleware = require('./middleware');

const run = (request, response) => {
  response.json({ foo: 'bar' });

  return response;
};

exports.handler = lamdify(run, [middleware]);
```

### Example: error middleware

```js
const errorMiddleware = async (req, res, next) => {
  try {
    const response = await next(req, res);

    return response;
  } catch (error) {
    // Fire off log to error system like raygun / sentry

    res.setStatusCode(500);

    return res.json({
      message: error.message,
      stack: error.stack,
      status: 'error',
    });
  }
};

module.exports = errorMiddleware;
```

Usage:

```js
const lambdify = require('lambdify');
const errorMiddleware = require('./errorMiddleware');

const run = (request, response) => {
  response.json({ foo: 'bar' });

  return response;
};

exports.handler = lamdify(run, [errorMiddleware]);
```

### Example: knex middleware

```js
const knex = (config) => async (req, res, next) => {
  const knex = require('knex')(config);

  req.set('knex', knex);

  try {
    const response = await next(req, res);

    await knex.destroy();

    return response;
  } catch (error) {
    await knex.destroy();

    throw error;
  }
};

module.exports = knex;
```

Usage:

```js
const lambdify = require('lambdify');
const knexMiddleware = require('./knexMiddleware');

const run = (request, response) => {
  const knex = request.get('knex');
  const user = knex('user').where({ id: request.getPathParam('userId') });

  response.json({ user });

  return response;
};

exports.handler = lamdify(run, [knexMiddleware(dbConfig)]);
```

# Router

Lambdify provides a router to enable your to attach a lambda to an ALB endpoint or a proxy+ api gateway path

## Example

```js
const lambdify = require('lambdify');
const router = require('lambdify/router')(); # Mind the double parentheses here

const index = (req, res) => res.json({ foo: 'bar' });

router.path('get', '/', index);

exports.handlers = lambdify(router.serve);
```

## `router.path(httpMethod, path, fn, ...middleware)`

This will match the method and path. The path can be a valid url path defined [here](https://www.npmjs.com/package/url-pattern)

## `router.sqs(key/fn, [value, fn], ...middleware)`

If key, value, fn are provided, it will match on the matching key / value inside the sqs request payload (single SQS message only). If just fn is provided, it will execute the fn when there is an SQS event

## `router.serve`

This is the fn that lambdify accepts

# Helpers

Lambdify provides a couple of helpers to help consume other local lambda functions and build / extend events.

## `invoke(event, fn)`

Invoke is used to invoke other local lambda code correctly irregardless if the function uses callback, context or a standard response.

- `event` is the event payload that your lambda accepts. See `event` below for event creation
- `fn` is the function handler

Example

```js

const lambdify = require('lambdify');
const { event, invoke } = require('lambdify/helpers');

const run = (req, res) => {
  const newEvent = event(req.getEvent(), { foo: 'bar' });
  const fn = require('./otherProjectLambdaFunction/turnBarToBaz').handler;
  const response = await invoke(newEvent, fn);

  return res.json((JSON.parse(response.body));
  // => { foo: 'baz' }
};

exports.handler = lambdify(run);
```

## `event(originalEvent = {}, body = {}, overrides = {})`

The goal of event is to ensure a consistent event payload to be sent to other lambda functions in a clean and consistent structure

- `originalEvent` is the original event object you wish to extend
- `body` is the new body payload being sent that is automatically `JSON.stringify`'ed
- `overrides` is an object that will override anything else in the original event

# Lambda Web Server / Express

Run a webserver on your lambda instance! Express, Koa, etc should now run without a hitch.

This is very similar to [aws-serverless-express](https://github.com/awslabs/aws-serverless-express). The main point being, it's a little more clean and simple to understand

## Example

Your Lambda Handler (usually `index.js`)

```js
const lambdaServer = require('lambdify/server');
const app = require('./app');

exports.handler = lambdaServer(app);
```

`app.js`

```js
const express = require('express');
const app = express();

app.get('/', (req, res) => res.send('Hello World!'));

module.export = app;
```

And for testing locally (I like to name it `server.js`)

```js
const app = require('./app.js');

app.listen(3000, () => console.log('Now Listening'));
```

## Why not use `aws-serverless-express`

This focuses on simplicity and standard use cases. It also doesn't worry about legacy implementations of lambda callback / context use and is focused on Node 10 / 12 support only with an emphasis on async / await code

## Other inspiration

This was originally developed to handle next.js SSR on AWS Lambda. Officially there is no support and although packages like [serverless-nextjs-plugin](https://www.npmjs.com/package/serverless-nextjs-plugin) exist, they require more packages and the serverless deployment system.

Example of Next.js package soon to come