micro-lambda-api
v1.2.4
Published
Micro framework for your lambda applications
Maintainers
Readme
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:
- Lambda API (https://github.com/jeremydaly/lambda-api)
- Koa Router (https://github.com/ZijianHe/koa-router/)
- AWS Lambda Router (https://github.com/art-dc/aws-lambda-router)
API Reference
- API Reference
- About the library
- Features
- Installation
- Getting Started
- Options
- Router
- Socket
- Request
- Response
finally- Logger
- Contributing
- Versioning
- Authors
- License
- Support
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,ResponseandLogger. - The final middleware router runs after all routes and middleware have completed.
- Creation of multiple routers with different versions.
- Enable logger using
process.stdoutandprocess.stderr. - Typescript support.
Installation
Installation via npm
npm install micro-lambda-apior yarn
yarn add micro-lambda-apiGetting 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/saveRouter 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
utilsandhttp.
Properties
idTransaction ID Example:
dc6128b3-bdcb-464d-a1e1-009b041ff9b9stageIf 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.
methodIdentifies the HTTP Method. Possible values:
GET,PUT,POST, etc.pathIdentify the URL. Example:
/users.queryReturns in an object all parameters that were passed in the URL query. Example:
{id: 100}paramsBy default, return an empty object. If you do not work with the
APIclass to be able to assign it a value must use the function:getRouteParams(route, path).headers()Returns an array of all headers in the request. Header names will be lowercase.
bodyReturns an object with all the parameters returned in the request body. Only works with
content-typeapplication/jsonorapplication/x-www-form-urlencoded.NOTE: Doesn't support binary content. Example: files.
hostReturns the host that invoked the request.
If ALB, the DNS will return.
If API Gateway will return the domain.
ipReturns the first ip found in the request.
userAgentReturns the
User-Agentsent in the header.proxyIntegrationIdentifies what integration your Lambda has.
The values to return are:
elborapigw-rest-apiorapigw-http-api.isBase64EncodedA Boolean value that identifies whether the body is in base64
logProperty that allows you to manage the log on each request.
connectionIdProperty that returns the id of the socket connection. This value can be nullable
routeproperty 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.
