micro-lambda-api
TypeScript icon, indicating that this package has built-in type declarations

1.2.4 • Public • Published
Logo

MIT license NPM Package Micro Lambda API Lines of Code Quality Gate Status Coverage Status Tests

Micro Lambda API is a small library for AWS Lambda that provides an easy way to use routing with AWS API Gateway and AWS Application Load Balancer services for API.

That library has taken reference to some libraries such as:

API Reference

About the library

This library aims to abstract the logic to perform REQUESTs and RESPONSEs in a simple and easy way that allows you to focus on the development of your lambda functionality. It has a LOGGER implementation using JSON to have a better traceability and control of events when it is sent to CloudWatch or another log monitoring platform.

Features

  • Integration with Gateway Lambda Proxy Integrator API using REST API or HTTP API.
  • Integration with ALB Lambda Target Support.
  • Enabling CORS for requests.
  • No external dependencies.
  • You can use the separate Request, Response and Logger.
  • The final middleware router runs after all routes and middleware have completed.
  • Creation of multiple routers with different versions.
  • Enable logger using process.stdout and process.stderr.
  • Typescript support.

Installation

Installation via npm

npm install micro-lambda-api

or yarn

yarn add micro-lambda-api

Getting Started

Using JavaScript

const { Api, ApiRouter, HttpStatus } = require("micro-lambda-api");

const api = new Api();
const router = new ApiRouter({
  basePath: "/users",
});

router.get("/", () => {
  return [];
});
router.post("/", (req, res) => {
  res.status(HttpStatus.NO_CONTENT).send("");
});
router.put("/:id", async (req) => {
  return { id: req.params.id };
});

api.use(router.routes());

exports.handler = async (event, context) => {
  return await api.listen(event, context);
};

Using TypeScript

import {
  Api,
  ApiRequest,
  ApiResponse,
  ApiRouter,
  HttpStatus,
} from "micro-lambda-api";

const api = new Api();
const router = new ApiRouter({
  basePath: "/users",
});

router.get("/", () => {
  return [];
});
router.post("/", (req: ApiRequest, res: ApiResponse) => {
  res.status(HttpStatus.NO_CONTENT).send("");
});
router.put("/:id", async (req: ApiRequest) => {
  return { id: req.params.id };
});

api.use(router.routes());

export async function handler(event: any, context: any) {
  return await api.listen(event, context);
}

Options

The api class has the following options:

Option Type Description
logger (Optional) object Custom the configuration logger

The logger option has the following options:

Option Type Description
trace (Optional) boolean Enable trace to request, response and errors
pretty (Optional) boolean Enable the pretty format to logger.
handler (Optional) Function Method that returns the response of each logger

Example:

const api = new Api({
  logger: {
    trace: true,
    pretty: false,
    handler: (log) => {
      // process or transform the log
    }
  }
});

Router

Class that defines the different HTTP methods

Basic use:

import { Api, ApiRouter } from "micro-lambda-api";

const api = new Api();
const router = new ApiRouter();

router.get("/", (req, res) => {
  // Process and return data
});

app.use(router.routes()).use(router.middlewares());

Options

Parameer Type Description
basePath (Optional) String Prefix of all routes
version (Optional) String Router version number. Example: v2

Examples:

const router = new ApiRouter({
  basePath: "/users",
});
const router = new ApiRouter({
  version: "v2",
});
const router = new ApiRouter({
  basePath: "/users",
  version: "v3",
});

Methods ⇒ ApiRouter

Router defines the following 5 methods: get, post, patch, put, delete

router
  .get("/users", async (req, res) => {
    const users = await getUsers();

    return users;
  })
  .post("/users", async (req, res) => {
    await saveUsers();

    return true;
  })
  .patch("/users/:id", async (req, res) => {
    const user = await updateStatus(req.param.id, true);

    return user;
  })
  .put("/users/:id", async (req, res) => {
    const user = await updateUser(user);

    return user;
  })
  .delete("/users/:id", async (req, res) => {
    await deleteUser(req.param.id);

    return true;
  });

When a route is not found it will return the following error: RouteError.

When a method is not found it will return the following error: MethodError.

Middleware => ApiRouter

Middlewares are functions that allow them to be executed before any route.

Example:

router.use((req, res) => {
  req.executionStart = performance.now();
});

Multiple middlewares can be nested

router
  .use((req, res) => {
    // Middleware 1
  })
  .use((req, res) => {
    // Middleware 2
  })
  .use((req, res) => {
    // Middleware 3
  });

NOTE: Remember that middlewares run according to the order that were created.

Router prefixes

When a basePath is defined, a prefix is added to all routes that you define.

Example:

const router = new ApiRouter({ basePath: "/users" });

router
  .get("/", ...) // responds to /users
  .put("/:id", ...) // responds to /users/100
  .post("/:id/save", ...) // responds to /users/100/save

Router responses

There are 2 ways to respond to the execution of a route

Using the response class:

router.get("/users", (req, res) => {
  res.send([
    { id: 1, username: "admin" },
    { id: 2, username: "demo" },
  ]);
});

Using return:

router.get("/users", (req, res) => {
  return [
    { id: 1, username: "admin" },
    { id: 2, username: "demo" },
  ];
});

The response class provides more functionality such as, for example, changing the response status, adding custom headers, enabling cors, and so on

You can also combine the use of response with return:

router.get("/users", (req, res) => {
  res.status(201);
  res.cors();
  res.header("x-custom-header", "my-header");

  return [
    { id: 1, username: "admin" },
    { id: 2, username: "demo" },
  ];
});

Functions

routes()

Returns an array with the routes that were defined.

router.routes();

middlewares()

Returns an array with the middlewares that were defined.

router.middlewares();

Static

getRouteParams(route, path)

A method that is not part of the Router class and is used to get the parameters of a URL.

Parameter Type Description
route String Route with dynamic parameters
path String The url that was invoked by the user

Example:

const params = getRouteParams("/users/:id/:username", "/users/1/admin");
// return { id: 1, username: "admin" }

Socket

Class that defines the different Actions to Api Gateway WebSocket.

Basic use:

import { Api, ApiSocket } from "micro-lambda-api";

const api = new Api();
const socket = new ApiSocket();

socket.action("message", (req, res) => {
  // Process and return data
});

app.use(socket.actions()).use(socket.middlewares());

Example: Sending a message from the Server to the Application

import { ApiGatewayManagementApi } from "aws-sdk";

socket
  .action("message", async (req, res) => {
    const connectionId = req.connectionId || "";
    const apiWs = new ApiGatewayManagementApi({
      apiVersion: "2018-11-29",
      endpoint: `https://${req.host}/${req.stage}`,
    });

    await apiWs
      .postToConnection({
        ConnectionId: connectionId,
        Data: // My Custom Data in JS Object,
      })
      .promise();

    res.send("Message sent!!!");
  });

Methods ⇒ ApiSocket

Socket defines the following 3 methods: connect, disconnect, action

socket
  .connect(async (req, res) => {
    await suscribe();

    res.send("Connect!!!");
  })
  .disconnect(async (req, res) => {
    await unsuscribe();
    
    res.send("Disconnect!!!");
  })
  .action("message", (req, res) => {
    return "message";
  })
  .action("register", async (req, res) => {
    const user = await register(req.body);

    return user;
  })

When an action is not found it will return the following error: ActionError.

Middleware => ApiSocket

Middlewares are functions that allow them to be executed before any action.

Example:

socket.use((req, res) => {
  req.executionStart = performance.now();
});

Multiple middlewares can be nested

socket
  .use((req, res) => {
    // Middleware 1
  })
  .use((req, res) => {
    // Middleware 2
  })
  .use((req, res) => {
    // Middleware 3
  });

NOTE: Remember that middlewares run according to the order that were created.

Action responses

There are 2 ways to respond to the execution of a action

Using the response class:

socket.action("message", (req, res) => {
  res.send([
    { id: 1, username: "admin" },
    { id: 2, username: "demo" },
  ]);
});

Using return:

socket.action("register", (req, res) => {
  return [
    { id: 1, username: "admin" },
    { id: 2, username: "demo" },
  ];
});

The response class provides more functionality such as, for example, changing the response status, adding custom headers, enabling cors, and so on

You can also combine the use of response with return:

socket.action("message", (req, res) => {
  res.status(201);
  res.cors();
  res.header("x-custom-header", "my-header");

  return [
    { id: 1, username: "admin" },
    { id: 2, username: "demo" },
  ];
});

Request

The request class allows you to parse the event that was invoked through an API Gateway or Application Load Balancer.

So you don't need to use all the functionality of the paths, api, errors, etc., you can use it in your Lambda handler function.

Example:

exports.handler = (event, context) => {
  const request = new ApiRequest(event);

  console.log(request.query);
  console.log(request.params);
  console.log(request.body);
};

NOTE: Remember that while the Request class is independent it uses some functions that are within utils and http.

Properties

  1. id

    Transaction ID Example: dc6128b3-bdcb-464d-a1e1-009b041ff9b9

  2. stage

    If you are working with API Gateway Stages, it will contain a value. Example: develop.

    If you set no stage in the Gateway API by default it will be empty.

  3. method

    Identifies the HTTP Method. Possible values: GET, PUT, POST, etc.

  4. path

    Identify the URL. Example: /users.

  5. query

    Returns in an object all parameters that were passed in the URL query. Example: {id: 100}

  6. params

    By default, return an empty object. If you do not work with the API class to be able to assign it a value must use the function: getRouteParams(route, path).

  7. headers()

    Returns an array of all headers in the request. Header names will be lowercase.

  8. body

    Returns an object with all the parameters returned in the request body. Only works with content-type application/json or application/x-www-form-urlencoded.

    NOTE: Doesn't support binary content. Example: files.

  9. host

    Returns the host that invoked the request.

    If ALB, the DNS will return.

    If API Gateway will return the domain.

  10. ip

    Returns the first ip found in the request.

  11. userAgent

    Returns the User-Agent sent in the header.

  12. proxyIntegration

    Identifies what integration your Lambda has.

    The values to return are: elb or apigw-rest-api or apigw-http-api.

  13. isBase64Encoded

    A Boolean value that identifies whether the body is in base64

  14. log

    Property that allows you to manage the log on each request.

  15. connectionId

    Property that returns the id of the socket connection. This value can be nullable

  16. route

    property that returns the router key to be used with the ApiSocket.

You can add additional parameters if you are working with ApiRouter or ApiSocket this will allow you to have more customization when you are using middlewares.

Response

The response class allows you to configure and return the response that Lambda will return to the Gateway API or Application Load Balancer.

If you don't need to use all the functionality of your paths, api, errors, etc., you can use it in your Lambda handler function.

Example:

exports.handler = (event, context) => {
  ...
  const response = new ApiResponse();

  return response
    .status(200)
    .send({
      id: 1,
      name: "admin",
      status: true
    });
}

Options

Options are only available if you use without Api and Router.

Option Type Description
cors boolean Static value that enable the headers cors in all responses.
credentials boolean Static value that enable the headers credentials in all responses.
request object Parameter optional in the initialization the class.

Example:

exports.handler = (event, context) => {
  const request = new ApiRequest(event, context);
  ...
  ApiResponse.cors = true;
  ApiResponse.credentials = true;

  const response = new ApiResponse(request);

  response.status(200);
  response.header("my-custom-header", "my-header");

  return response
    .send({
      id: 1,
      name: "admin",
      status: true
    });
}

Methods

status(code)ApiResponse

Sets the status of the response that was returned to the Gateway API or ALB. By default, the response status is 200 and 500 for a general error.

Status 405 is used when the method does not exist and 404 when the route is not found.

For better control you can use the HttpStatus object which has a listing of all http codes that you can return.

Example:

router.get("/", (req, res) => {
  res.status(HttpStatus.OK).send(true);
});

header(key, value)ApiResponse

Use the header function if you want to add custom header that They will be returned to the Gateway API or ALB.

Remember that all keys will be converted to lowercase.

Example:

router.get("/", (req, res) => {
  res.header("X-Custom-Header", "my-custom-header");
  // return to "x-custom-header" in lowercase
  ...
})

cors(options)ApiResponse

Allows you to add custom cors for each request. If you want to work with the cors by default, use the static value cors.

Example:

router.get("/", (req, res) => {
  res.cors({
    credentials: true,
    origin: "https://mydomain.com",
  });
  ...
})

toResponse()

Returns the previous object that will be returned.

Structure:

interface Response {
  isBase64Encoded: boolean;
  statusCode: number;
  statusDescription?: string;
  body: string;
  headers: {
    [key: string]: string | undefined;
  };
}

toResponseError()

Returns the previous object that will be returned.

Structure:

export interface ResponseError {
  status?: HttpStatus;
  code: string;
  message: string;
  data?: {
    [key: string]: unknown;
  };
}

send(payload, isError)

Allows you to send a json object or string that will be transformed to a string that is the data that API Gateway or ALB expects to receive.

The parameter isError is optional is used only by the method error.

res.send("Hola Mundo");

// Return
{
  isBase64Encoded: false;
  statusCode: 200;
  body: "Hola Mundo";
  headers: {
    ...
  };
}

Or

res.send({
  id: 1,
  name: "admin",
  status: true
});

// Return
{
  isBase64Encoded: false;
  statusCode: 200;
  body: "{'user': 1, 'name': 'admin', 'status': true}";
  headers: {
    ...
  };
}

json(body)

A metode that defaults to the conten-type header in application/json And that receives as a parameter a JSON.

res.json({
  id: 1,
  name: "admin",
  status: true,
});

html(body)

A metode that defaults to the conten-type header in text/html; charset=UTF-8 And that receives as a parameter a String.

res.json("<h1>Hello World</h1>");

file(body)

A method that defaults to the conten-type header in tapplication/octet-stream And that receives as a parameter a String.

res.json("This is a file");

error(data)

A metode that receives as a parameter an object with a structure Predefined (ResponseError) that will be sent to the send method

Structure:

interface ResponseError {
  status?: HttpStatus;
  code: string;
  message: string;
  data?: {
    [key: string]: unknown;
  };
}

Remember that if you use the response object only, you must assign the status of the response.

Example:

res.status(400);
res.error(...);

You can use a throw to generate an exception in your route and be captured by the global error handler.

Example:

router.get("/", () => {
  throw Error("Unknown error");
});

// Return
{
  status: 500,
  code: "GENERIC_ERROR",
  message: "Unknown Error",
}

All errors extend from the HttpError class and this In turn of the generic class Error.

class HttpError extends Error {
  constructor(public code: string, public message: string) {
    super(message);
  }
}

finally

An api functionality is 'finally' which is a middleware that is invoked at the end of all executions (paths and middlewares) used if you want to clean variables, connect to database, perform tracing, and so on.

api.finally((req, res) => {
  ...
});

Logger

The logger class allows you to centralize logs in your Lambda using process.stdout or process.stderr.

This component can be used independently.

Example:

const log = Logger.create();

log.trace("Log trace");
log.debug("Log debug");
log.info("Log info");
log.warn("Log warn");
log.error("Log error");
log.fatal("Log fatal");

/*
{"id":"ca4d341d-2c86-41a1-989b-9cd3b25ea05e","level":"trace","time":1612848015979,"timeStamp":"2021-02-09T05:20:15.979Z","message":"Log trace","lambda":{"name":"myFunction","version":"1.0.0","memoryLimitInMB":1024,"arn":"my:arn:myFunction"}}
*/

Configuration

Option Type Description
context (Optional) boolean Object containing the request id and lambda data
pretty (Optional) boolean Enable the pretty format to logger.
handler (Optional) Function Method that returns the response of each logger

Example:

Logger.configure({
  context: { ... },
  pretty: false,
  handler: (log) => { ... },
})
const log = Logger.create();

toLog()

Method that returns a json object with the basic structure of the log:

interface Log {
  id: string;
  level: string;
  group?: string;
  message: string;
  data?: unknown;
  time: number;
  timeStamp: string;
  lambda?: LogLambda;
  [key: string]: any;
}
Option Type Description
id string Request ID in format UUID V4
level string Logger level. Example: info, trace, error, etc.
group (Optional) string Grouping logs
message string Log message
data (Optional) object Additional information that can be shared to the log
time number The datetime in long
timeStamp string The datetime in ISO String
lambda (Optional) string Information about lambda. Example: name, memory, arn, etc.
[key: string] any any additional parameters

Grouping

To group logs you should use the group(name) function

const log = Logger.create();
const grupo1 = Logger.group("group1");
const grupo2 = Logger.group("group2");

grupo1.trace("Log trace");
grupo2.debug("Log debug");

Extras

If you want to add additional parameters when displaying the log.

const log = Logger.create();

log.addExtra("request", {...});
log.getExtra("request");
log.removeExtra("request");
log.clearExtras();

Pretty format

By default all the logs print in json format, enable the option pretty in true is posible return the logs in text.

Example:

Logger.configure({ pretty: true });

const log = Logger.create();

log.trace("This is a log");

// Return => [2021-02-09T05:20:15.985Z] [TRACE] - This is a log.

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

The repository use SemVer for versioning. For the versions available, see the tags.

Authors

  • Luis Diego - Project Creator - ldiego73

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

If you have any problem or suggestion please open an issue here.

Package Sidebar

Install

npm i micro-lambda-api

Weekly Downloads

11

Version

1.2.4

License

MIT

Unpacked Size

65.2 kB

Total Files

29

Last publish

Collaborators

  • ldiego73