micro-lambda-api
v1.2.4
Published
Micro framework for your lambda applications
Downloads
71
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
,Response
andLogger
. - The final middleware router runs after all routes and middleware have completed.
- Creation of multiple routers with different versions.
- Enable logger using
process.stdout
andprocess.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
andhttp
.
Properties
id
Transaction ID Example:
dc6128b3-bdcb-464d-a1e1-009b041ff9b9
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.
method
Identifies the HTTP Method. Possible values:
GET
,PUT
,POST
, etc.path
Identify the URL. Example:
/users
.query
Returns in an object all parameters that were passed in the URL query. Example:
{id: 100}
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)
.headers()
Returns an array of all headers in the request. Header names will be lowercase.
body
Returns an object with all the parameters returned in the request body. Only works with
content-type
application/json
orapplication/x-www-form-urlencoded
.NOTE: Doesn't support binary content. Example: files.
host
Returns the host that invoked the request.
If ALB, the DNS will return.
If API Gateway will return the domain.
ip
Returns the first ip found in the request.
userAgent
Returns the
User-Agent
sent in the header.proxyIntegration
Identifies what integration your Lambda has.
The values to return are:
elb
orapigw-rest-api
orapigw-http-api
.isBase64Encoded
A Boolean value that identifies whether the body is in base64
log
Property that allows you to manage the log on each request.
connectionId
Property that returns the id of the socket connection. This value can be nullable
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.