- Notifications
You must be signed in to change notification settings - Fork126
Lightweight web framework for your serverless applications
License
jeremydaly/lambda-api
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Lambda API is a lightweight web framework for AWS Lambda using AWS API Gateway Lambda Proxy Integration or ALB Lambda Target Support. This closely mirrors (and is based on) other web frameworks like Express.js and Fastify, but is significantly stripped down to maximize performance with Lambda's stateless, single run executions.
lambda-api@v1 is using AWS SDK v3.If you are using AWS SDK v2, please use lambda-api@v0.12.0.
// Require the framework and instantiate itconstapi=require('lambda-api')();// Define a routeapi.get('/status',async(req,res)=>{return{status:'ok'};});// Declare your Lambda handlerexports.handler=async(event,context)=>{// Run the requestreturnawaitapi.run(event,context);};
For a full tutorial seeHow To: Build a Serverless API with Serverless, AWS Lambda and Lambda API.
Express.js, Fastify, Koa, Restify, and Hapi are just a few of the many amazing web frameworks out there for Node.js. So why build yet another one when there are so many great options already? One word:DEPENDENCIES.
These other frameworks are extremely powerful, but that benefit comes with the steep price of requiring several additional Node.js modules. Not only is this a bit of a security issue (see Beware of Third-Party Packages inSecuring Serverless), but it also adds bloat to your codebase, filling yournode_modules directory with a ton of extra files. For serverless applications that need to load quickly, all of these extra dependencies slow down execution and use more memory than necessary. Express.js has30 dependencies, Fastify has12, and Hapi has17! These numbers don't even include their dependencies' dependencies.
Lambda API hasZERO dependencies.None.Zip.Zilch.
Lambda API was written to beextremely lightweight and built specifically forSERVERLESS applications using AWS Lambda and API Gateway. It provides support for API routing, serving up HTML pages, issuing redirects, serving binary files and much more. Worried about observability? Lambda API has a built-in logging engine that can even periodically sample requests for things like tracing and benchmarking. It has a powerful middleware and error handling system, allowing you to implement just about anything you can dream of. Best of all, it was designed to work with Lambda's Proxy Integration, automatically handling all the interaction with API Gateway for you. It parsesREQUESTS and formatsRESPONSES, allowing you to focus on your application's core functionality, instead of fiddling with inputs and outputs.
You may have heard that a serverless "best practice" is to keep your functions small and limit them to a single purpose. I generally agree since building monolith applications is not what serverless was designed for. However, what exactly is a "single purpose" when it comes to building serverless APIs and web services? Should we create a separate function for our "create user"POST endpoint and then another one for our "update user"PUT endpoint? Should we create yet another function for our "delete user"DELETE endpoint? You certainly could, but that seems like a lot of repeated boilerplate code. On the other hand, you could create just one function that handled all your user management features. It may even make sense (in certain circumstances) to create one big serverless function handling several related components that can share your VPC database connections.
Whatever you decide is best for your use case,Lambda API is there to support you. Whether your function has over a hundred routes, or just one, Lambda API's small size and lightning fast load time has virtually no impact on your function's performance. You can even define global wildcard routes that will process any incoming request, allowing you to use API Gateway or ALB to determine the routing. Yet despite its small footprint, it gives you the power of a full-featured web framework.
- Simple Example
- Why Another Web Framework?
- Table of Contents
- Installation
- Requirements
- Configuration
- Recent Updates
- Routes and HTTP Methods
- Returning Responses
- Route Prefixing
- Debugging Routes
- REQUEST
- RESPONSE
- status(code)
- sendStatus(code)
- header(key, value [,append])
- getHeader(key [,asArray])
- getHeaders()
- hasHeader(key)
- removeHeader(key)
- getLink(s3Path [, expires] [, callback])
- send(body)
- json(body)
- jsonp(body)
- html(body)
- type(type)
- location(path)
- redirect([status,] path)
- cors([options])
- error([code], message [,detail])
- cookie(name, value [,options])
- clearCookie(name [,options])
- etag([boolean])
- cache([age] [, private])
- modified(date)
- attachment([filename])
- download(file [, filename] [, options] [, callback])
- sendFile(file [, options] [, callback])
- Enabling Binary Support
- Path Parameters
- Wildcard Routes
- Logging
- Middleware
- Clean Up
- Error Handling
- Namespaces
- CORS Support
- Compression
- Execution Stacks
- Lambda Proxy Integration
- ALB Integration
- Configuring Routes in API Gateway
- Reusing Persistent Connections
- TypeScript Support
- Contributions
- Are you using Lambda API?
npm i lambda-api --save- AWS Lambda runningNode 8.10+
- AWS API Gateway usingProxy Integration
Require thelambda-api module into your Lambda handler script and instantiate it. You can initialize the API with the following options:
| Property | Type | Description |
|---|---|---|
| base | String | Base path for all routes, e.g.base: 'v1' would prefix all routes with/v1 |
| callbackName | String | Override the default callback query parameter name for JSONP calls |
| logger | boolean orobject | Enables defaultlogging or allows for configuration through aLogging Configuration object. |
| mimeTypes | Object | Name/value pairs of additional MIME types to be supported by thetype(). The key should be the file extension (without the.) and the value should be the expected MIME type, e.g.application/json |
| serializer | Function | Optional object serializer function. This function receives thebody of a response and must return a string. Defaults toJSON.stringify |
| version | String | Version number accessible via theREQUEST object |
| errorHeaderWhitelist | Array | Array of headers to maintain on errors |
| s3Config | Object | Optional object to provide as config to S3 sdk.S3ClientConfig |
// Require the framework and instantiate it with optional version and base parametersconstapi=require('lambda-api')({version:'v1.0',base:'v1'});
For detailed release notes seeReleases.
Lambda API now supports API Gateway v2 payloads for use with HTTP APIs. The library automatically detects the payload, so no extra configuration is needed. Automaticcompression has also been added and supports Brotli, Gzip and Deflate.
Lambda API now allows you to seamlessly switch between API Gateway and Application Load Balancers. Newexecution stacks enables method-based middleware and more wildcard functionality. Plus full support for multi-value headers and query string parameters.
Routes are defined by using convenience methods or theMETHOD method. There are currently eight convenience route methods:get(),post(),put(),patch(),delete(),head(),options() andany(). Convenience route methods require an optionalroute and one or more handler functions. Aroute is simply a path such as/users. If aroute is not provided, then it will default to/* and will execute on every path. Handler functions accept aREQUEST,RESPONSE, and optionalnext() argument. These arguments can be named whatever you like, but convention dictatesreq,res, andnext.
Multiple handler functions can be assigned to a path, which can be used to execute middleware for specific paths and methods. For more information, seeMiddleware andExecution Stacks.
Examples using convenience route methods:
api.get('/users',(req,res)=>{// do something})api.post('/users',(req,res)=>{// do something})api.delete('/users',(req,res)=>{// do something})api.get('/users',(req,res,next)=>{// do some middlewarenext()// continue execution}),(req,res)=>{// do something})api.post((req,res)=>{// do something for ALL post requests})
Additional methods are support by callingMETHOD. Arguments must include an HTTP method (or array of methods), an optionalroute, and one or more handler functions. Like the convenience methods above, handler functions accept aREQUEST,RESPONSE, and optionalnext argument.
api.METHOD('trace','/users',(req,res)=>{// do something on TRACE})api.METHOD(['post','put'],'/users',(req,res)=>{// do something on POST -or- PUT})api.METHOD('get','/users',(req,res,next)=>{// do some middlewarenext()// continue execution}),(req,res)=>{// do something})
AllGET methods have aHEAD alias that executes theGET request but returns a blankbody.GET requests should be idempotent with no side effects. Thehead() convenience method can be used to set specific paths forHEAD requests or to override defaultGET aliasing.
Routes that use theany() method or passANY toapi.METHOD will respond to all HTTP methods. Routes that specify a specific method (such asGET orPOST), will override the route for that method. For example:
api.any('/users',(req,res)=>{res.send('any');});api.get('/users',(req,res)=>{res.send('get');});
APOST to/users will return "any", but aGET request would return "get". Please note that routes defined with anANY method will override defaultHEAD aliasing forGET routes.
Lambda API supports bothcallback-style andasync-await for returning responses to users. TheRESPONSE object has several callbacks that will trigger a response (send(),json(),html(), etc.) You can use any of these callbacks from within route functions and middleware to send the response:
api.get('/users',(req,res)=>{res.send({foo:'bar'});});
You can alsoreturn data from route functions and middleware. The contents will be sent as the body:
api.get('/users',(req,res)=>{return{foo:'bar'};});
If you prefer to useasync/await, you can easily apply this to your route functions.
Usingreturn:
api.get('/users',async(req,res)=>{letusers=awaitgetUsers();returnusers;});
Or using callbacks:
api.get('/users',async(req,res)=>{letusers=awaitgetUsers();res.send(users);});
If you like promises, you can either use a callback likeres.send() at the end of your promise chain, or you can simplyreturn the resolved promise:
api.get('/users',(req,res)=>{getUsers().then((users)=>{res.send(users);});});
OR
api.get('/users',(req,res)=>{returngetUsers().then((users)=>{returnusers;});});
IMPORTANT: You must either use a callback likeres.send()ORreturn a value. Otherwise the execution will hang and no data will be sent to the user. Also, be sure not to returnundefined, otherwise it will assume no response.
While callbacks likeres.send() andres.error() will trigger a response, they will not necessarily terminate execution of the current route function. Take a look at the following example:
api.get('/users',(req,res)=>{if(req.headers.test==='test'){res.error('Throw an error');}return{foo:'bar'};});
The example above would not have the intended result of displaying an error.res.error() would signal Lambda API to execute the error handling, but the function would continue to run. This would cause the function toreturn a response that would override the intended error. In this situation, you could either wrap the return in anelse clause, or a cleaner approach would be toreturn the call to theerror() method, like so:
api.get('/users',(req,res)=>{if(req.headers.test==='test'){returnres.error('Throw an error');}return{foo:'bar'};});
res.error() does not have a return value (meaning it isundefined). However, thereturn tells the function to stop executing, and the call tores.error() handles and formats the appropriate response. This will allow Lambda API to properly return the expected results.
Lambda API makes it easy to create multiple versions of the same api without changing routes by hand. Theregister() method allows you to load routes from an external file and prefix all of those routes using theprefix option. For example:
// handler.jsconstapi=require('lambda-api')();api.register(require('./routes/v1/products'),{prefix:'/v1'});api.register(require('./routes/v2/products'),{prefix:'/v2'});module.exports.handler=(event,context,callback)=>{api.run(event,context,callback);};
// routes/v1/products.jsmodule.exports=(api,opts)=>{api.get('/product',handler_v1);};
// routes/v2/products.jsmodule.exports=(api,opts)=>{api.get('/product',handler_v2);};
Even though both modules create a/product route, Lambda API will add theprefix to them, creating two unique routes. Your users can now access:
/v1/product/v2/product
You can useregister() as many times as you want AND it is recursive, so if you nestregister() methods, the routes will build upon each other. For example:
module.exports=(api,opts)=>{api.get('/product',handler_v1);api.register(require('./v2/products.js'),{prefix:'/v2'});};
This would create a/v1/product and/v1/v2/product route. You can also useregister() to load routes from an external file without theprefix. This will just add routes to yourbase path.NOTE: Prefixed routes are built off of yourbase path if one is set. If yourbase was set to/api, then the first example above would produce the routes:/api/v1/product and/api/v2/product.
Lambda API has aroutes() method that can be called on the main instance that will return an array containing theMETHOD and fullPATH of every configured route. This will include base paths and prefixed routes. This is helpful for debugging your routes.
constapi=require('lambda-api')();api.get('/',(req,res)=>{});api.post('/test',(req,res)=>{});api.routes();// => [ [ 'GET', '/' ], [ 'POST', '/test' ] ]
You can also log the paths in table form to the console by passing intrue as the only parameter.
constapi=require('lambda-api')()api.get('/',(req,res)=>{})api.post('/test',(req,res)=>{})api.routes(true)// Outputs to console╔═══════════╤═════════════════╗║METHOD│ROUTE║╟───────────┼─────────────────╢║GET│/║╟───────────┼─────────────────╢║POST│/test║╚═══════════╧═════════════════╝
TheREQUEST object contains a parsed and normalized request from API Gateway. It contains the following values by default:
app: A reference to an instance of the appversion: The version set at initializationid: The awsRequestId from the Lambdacontextinterface: The interface being used to access Lambda (apigateway,alb, oredge)params: Dynamic path parameters parsed from the path (seepath parameters)method: The HTTP method of the requestpath: The path passed in by the request including thebaseand anyprefixassigned to routesquery: Querystring parameters parsed into an objectmultiValueQuery: Querystring parameters with multiple values parsed into an object with array valuesheaders: An object containing the request headers (properties converted to lowercase for HTTP/2, seerfc7540 8.1.2. HTTP Header Fields). Note that multi-value headers are concatenated with a comma perrfc2616 4.2. Message Headers.rawHeaders: An object containing the original request headers (property case preserved)multiValueHeaders: An object containing header values as multi-value arraysbody: The body of the request. If theisBase64Encodedflag istrue, it will be decoded automatically.- If the
content-typeheader isapplication/json, it will attempt to parse the request usingJSON.parse() - If the
content-typeheader isapplication/x-www-form-urlencoded, it will attempt to parse a URL encoded string usingquerystring - Otherwise it will be plain text.
- If the
rawBody: If theisBase64Encodedflag istrue, this is a copy of the original, base64 encoded bodyroute: The matched route of the requestrequestContext: TherequestContextpassed from the API GatewaypathParameters: ThepathParameterspassed from the API GatewaystageVariables: ThestageVariablespassed from the API GatewayisBase64Encoded: TheisBase64Encodedboolean passed from the API Gatewayauth: An object containing thetypeandvalueof an authorization header. Currently supportsBearer,Basic,OAuth, andDigestschemas. For theBasicschema, the object is extended with additional fields for username/password. For theOAuthschema, the object is extended with key/value pairs of the supplied OAuth 1.0 values.namespaceorns: A reference to modules added to the app's namespace (seenamespaces)cookies: An object containing cookies sent from the browser (see thecookieRESPONSEmethod)context: Reference to thecontextpassed into the Lambda handler functioncoldStart: Boolean that indicates whether or not the current invocation was a cold startrequestCount: Integer representing the total number of invocations of the current function container (how many times it has been reused)ip: The IP address of the client making the requestuserAgent: TheUser-Agentheader sent by the client making the requestclientType: Eitherdesktop,mobile,tv,tabletorunknownbased on CloudFront's analysis of theUser-AgentheaderclientCountry: Two letter country code representing the origin of the requests as determined by CloudFrontstack: An array of function names executed as part of a route'sExecution Stack, which is useful for debugging
The request object can be used to pass additional information through the processing chain. For example, if you are using a piece of authentication middleware, you can add additional keys to theREQUEST object with information about the user. Seemiddleware for more information.
TheRESPONSE object is used to send a response back to the API Gateway. TheRESPONSE object contains several methods to manipulate responses. All methods are chainable unless they trigger a response.
Thestatus method allows you to set the status code that is returned to API Gateway. By default this will be set to200 for normal requests or500 on a thrown error. Additional built-in errors such as404 Not Found and405 Method Not Allowed may also be returned. Thestatus() method accepts a single integer argument.
api.get('/users',(req,res)=>{res.status(304).send('Not Modified');});
ThesendStatus method sets the status code and returns its string representation as the response body. ThesendStatus() method accepts a single integer argument.
res.sendStatus(200);// equivalent to res.status(200).send('OK')res.sendStatus(304);// equivalent to res.status(304).send('Not Modified')res.sendStatus(403);// equivalent to res.status(403).send('Forbidden')
NOTE: If an unsupported status code is provided, it will return 'Unknown' as the body.
Theheader method allows for you to set additional headers to return to the client. By default, just thecontent-type header is sent withapplication/json as the value. Headers can be added or overwritten by calling theheader() method with two string arguments. The first is the name of the header and then second is the value. You can utilize multi-value headers by specifying an array with multiple values as thevalue, or you can use an optional third boolean parameter and append multiple headers.
api.get('/users',(req,res)=>{res.header('content-type','text/html').send('<div>This is HTML</div>');});// Set multiple header valuesapi.get('/users',(req,res)=>{res.header('someHeader',['foo','bar']).send({});});// Set multiple header by adding to existing headerapi.get('/users',(req,res)=>{res.header('someHeader','foo').header('someHeader','bar',true)// append another value.send({});});
NOTE: Header keys are converted and stored as lowercase in compliance withrfc7540 8.1.2. HTTP Header Fields for HTTP/2. Header convenience methods (getHeader,hasHeader, andremoveHeader) automatically ignore case.
Retrieve a specific header value.key is case insensitive. By default (and for backwards compatibility), header values are returned as astring. Multi-value headers will be concatenated using a comma (seerfc2616 4.2. Message Headers). An optional second boolean parameter can be passed to return header values as anarray.
NOTE: The ability to retrieve the current header object by callinggetHeader() is still possible, but the preferred method is to use thegetHeaders() method. By default,getHeader() will return the object withstring values.
Retrieve the current header object. Values are returned asarrays.
Returns a boolean indicating the existence ofkey in the response headers.key is case insensitive.
Removes header matchingkey from the response headers.key is case insensitive. This method is chainable.
This returns a signed URL to the referenced file in S3 (using thes3://{my-bucket}/{path-to-file} format). You can optionally pass in an integer as the second parameter that will changed the default expiration time of the link. The expiration time is in seconds and defaults to900. In order to ensure proper URL signing, thegetLink() must be asynchronous, and therefore returns a promise. You must eitherawait the result or use a.then to retrieve the value.
There is an optional third parameter that takes an error handler callback. If the underlyinggetSignedUrl() call fails, the error will be returned using the standardres.error() method. You can override this by providing your own callback.
// async/awaitapi.get('/getLink',async(req,res)=>{leturl=awaitres.getLink('s3://my-bucket/my-file.pdf');return{link:url};});// promisesapi.get('/getLink',(req,res)=>{res.getLink('s3://my-bucket/my-file.pdf').then((url)=>{res.json({link:url});});});
Thesend methods triggers the API to return data to the API Gateway. Thesend method accepts one parameter and sends the contents through as is, e.g. as an object, string, integer, etc. AWS Gateway expects a string, so the data should be converted accordingly.
There is ajson convenience method for thesend method that will set the headers toapplication/json as well as performJSON.stringify() on the contents passed to it.
api.get('/users',(req,res)=>{res.json({message:'This will be converted automatically'});});
There is ajsonp convenience method for thesend method that will set the headers toapplication/json, performJSON.stringify() on the contents passed to it, and wrap the results in a callback function. By default, the callback function is namedcallback.
res.jsonp({foo:'bar'});// => callback({ "foo": "bar" })res.status(500).jsonp({error:'some error'});// => callback({ "error": "some error" })
The default can be changed by passing incallback as a URL parameter, e.g.?callback=foo.
// ?callback=foores.jsonp({foo:'bar'});// => foo({ "foo": "bar" })
You can change the default URL parameter using the optionalcallback option when initializing the API.
constapi=require('lambda-api')({callback:'cb'});// ?cb=barres.jsonp({foo:'bar'});// => bar({ "foo": "bar" })
There is also anhtml convenience method for thesend method that will set the headers totext/html and pass through the contents.
api.get('/users',(req,res)=>{res.html('<div>This is HTML</div>');});
Sets thecontent-type header for you based on a singleString input. There are thousands of MIME types, many of which are likely never to be used by your application. Lambda API stores a list of the most popular file types and will automatically set the correctcontent-type based on the input. If thetype contains the "/" character, then it sets thecontent-type to the value oftype.
res.type('.html');// => 'text/html'res.type('html');// => 'text/html'res.type('json');// => 'application/json'res.type('application/json');// => 'application/json'res.type('png');// => 'image/png'res.type('.doc');// => 'application/msword'res.type('text/css');// => 'text/css'
For a complete list of auto supported types, seemimemap.js. Custom MIME types can be added by using themimeTypes option when instantiating Lambda API
Thelocation convenience method sets theLocation: header with the value of a single string argument. The value passed in is not validated but will be encoded before being added to the header. Values that are already encoded can be safely passed in. Note that a valid3xx status code must be set to trigger browser redirection. The value can be a relative/absolute path OR a FQDN.
api.get('/redirectToHome',(req,res)=>{res.location('/home').status(302).html('<div>Redirect to Home</div>');});api.get('/redirectToGithub',(req,res)=>{res.location('https://github.com').status(302).html('<div>Redirect to GitHub</div>');});
Theredirect convenience method triggers a redirection and ends the current API execution. This method is similar to thelocation() method, but it automatically sets the status code and callssend(). The redirection URL (relative/absolute path, a FQDN, or an S3 path reference) can be specified as the only parameter or as a second parameter when a valid3xx status code is supplied as the first parameter. The status code is set to302 by default, but can be changed to300,301,302,303,307, or308 by adding it as the first parameter.
api.get('/redirectToHome',(req,res)=>{res.redirect('/home');});api.get('/redirectToGithub',(req,res)=>{res.redirect(301,'https://github.com');});// This will redirect a signed URL using the getLink methodapi.get('/redirectToS3File',(req,res)=>{res.redirect('s3://my-bucket/someFile.pdf');});
Convenience method for addingCORS headers to responses. An optionaloptions object can be passed in to customize the defaults.
The six definedCORS headers are as follows:
- Access-Control-Allow-Origin (defaults to
*) - Access-Control-Allow-Methods (defaults to
GET, PUT, POST, DELETE, OPTIONS) - Access-Control-Allow-Headers (defaults to
Content-Type, Authorization, Content-Length, X-Requested-With) - Access-Control-Expose-Headers
- Access-Control-Max-Age
- Access-Control-Allow-Credentials
Theoptions object can contain the following properties that correspond to the above headers:
- origin(string)
- methods(string)
- headers(string)
- exposeHeaders(string)
- maxAge(number in milliseconds)
- credentials(boolean)
Defaults can be set by callingres.cors() with no properties, or with any combination of the above options.
res.cors({origin:'example.com',methods:'GET, POST, OPTIONS',headers:'content-type, authorization',maxAge:84000000,});
You can override existing values by callingres.cors() with just the updated values:
res.cors({origin:'api.example.com',});
An error can be triggered by calling theerror method. This will cause the API to stop execution and return the message to the client. The status code can be set by optionally passing in an integer as the first parameter. Additional detail can be added as an optional third parameter (or second parameter if no status code is passed). This will add an additionaldetail property to error logs. Details accepts any value that can be serialized byJSON.stringify including objects, strings and arrays. Custom error handling can be accomplished using theError Handling feature.
api.get('/users',(req,res)=>{res.error('This is an error');});api.get('/users',(req,res)=>{res.error(403,'Not authorized');});api.get('/users',(req,res)=>{res.error('Error',{foo:'bar'});});api.get('/users',(req,res)=>{res.error(404,'Page not found','foo bar');});
Convenience method for setting cookies. This method accepts aname,value and an optionaloptions object with the following parameters:
| Property | Type | Description |
|---|---|---|
| domain | String | Domain name to use for the cookie. This defaults to the current domain. |
| expires | Date | The expiration date of the cookie. Local dates will be converted to GMT. Creates session cookie if this value is not specified. |
| httpOnly | Boolean | Sets the cookie to be accessible only via a web server, not JavaScript. |
| maxAge | Number | Set the expiration time relative to the current time in milliseconds. Automatically sets theexpires property if not explicitly provided. |
| path | String | Path for the cookie. Defaults to "/" for the root directory. |
| secure | Boolean | Sets the cookie to be used with HTTPS only. |
| sameSite | Boolean orString | Sets the SameSite value for cookie.true orfalse setsStrict orLax respectively. Also allows a string value. Seehttps://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-4.1.1 |
Thename attribute should be a string (auto-converted if not), but thevalue attribute can be any type of value. Thevalue will be serialized (if an object, array, etc.) and then encoded usingencodeURIComponent for safely assigning the cookie value. Cookies are automatically parsed, decoded, and available via theREQUEST object (seeREQUEST).
NOTE: Thecookie() method only sets the header. A execution ending method likesend(),json(), etc. must be called to send the response.
res.cookie('foo','bar',{maxAge:3600*1000,secure:true}).send();res.cookie('fooObject',{foo:'bar'},{domain:'.test.com',path:'/admin',httpOnly:true}).send();res.cookie('fooArray',['one','two','three'],{path:'/',httpOnly:true}).send();
Convenience method for expiring cookies. Requires thename and optionaloptions object as specified in thecookie method. This method will automatically set the expiration time. However, most browsers require the same options to clear a cookie as was used to set it. E.g. if you set thepath to "/admin" when you set the cookie, you must use this same value to clear it.
res.clearCookie('foo',{secure:true}).send();res.clearCookie('fooObject',{domain:'.test.com',path:'/admin',httpOnly:true,}).send();res.clearCookie('fooArray',{path:'/',httpOnly:true}).send();
NOTE: TheclearCookie() method only sets the header. A execution ending method likesend(),json(), etc. must be called to send the response.
Enables Etag generation for the response if at value oftrue is passed in. Lambda API will generate an Etag based on the body of the response and return the appropriate header. If the request contains anIf-No-Match header that matches the generated Etag, a304 Not Modified response will be returned with a blank body.
Addscache-control header to responses. If the first parameter is aninteger, it will add amax-age to the header. The number should be in milliseconds. If the first parameter istrue, it will add the cache headers withmax-age set to0 and use the current time for theexpires header. If set to false, it will add a cache header withno-cache, no-store, must-revalidate as the value. You can also provide a custom string that will manually set the value of thecache-control header. And optional second argument takes aboolean and will set thecache-control toprivate This method is chainable.
res.cache(false).send();// 'cache-control': 'no-cache, no-store, must-revalidate'res.cache(1000).send();// 'cache-control': 'max-age=1'res.cache(30000,true).send();// 'cache-control': 'private, max-age=30'
Adds alast-modified header to responses. A value oftrue will set the value to the current date and time. A JavaScriptDate object can also be passed in. Note that it will be converted to UTC if not already. Astring can also be passed in and will be converted to a date if JavaScript'sDate() function is able to parse it. A value offalse will prevent the header from being generated, but will not remove any existinglast-modified headers.
Sets the HTTP responsecontent-disposition header field to "attachment". If afilename is provided, then thecontent-type is set based on the file extension using thetype() method and the "filename=" parameter is added to thecontent-disposition header.
res.attachment();// content-disposition: attachmentres.attachment('path/to/logo.png');// content-disposition: attachment; filename="logo.png"// content-type: image/png
This transfers thefile (either a local path, S3 file reference, or JavascriptBuffer) as an "attachment". This is a convenience method that combinesattachment() andsendFile() to prompt the user to download the file. This method optionally takes afilename as a second parameter that will overwrite the "filename=" parameter of thecontent-disposition header, otherwise it will use the filename from thefile. An optionaloptions object passes through to thesendFile() method and takes the same parameters. Finally, a optionalcallback method can be defined which is passed through tosendFile() as well.
res.download('./files/sales-report.pdf')res.download('./files/sales-report.pdf','report.pdf')res.download('s3://my-bucket/path/to/file.png','logo.png',{maxAge:3600000})res.download(<Buffer>, 'my-file.docx',{maxAge:3600000}, (err) =>{if(err){res.error('Custom File Error')}})
ThesendFile() method takes up to three arguments. The first is thefile. This is either a local filename (stored within your uploaded lambda code), a reference to a file in S3 (using thes3://{my-bucket}/{path-to-file} format), or a JavaScriptBuffer. You can optionally pass anoptions object using the properties below as well as a callback functioncallback(err) that can handle custom errors or manipulate the response before sending to the client.
| Property | Type | Description | Default |
|---|---|---|---|
| maxAge | Number | Set the expiration time relative to the current time in milliseconds. Automatically sets theExpires header | 0 |
| root | String | Root directory for relative filenames. | |
| lastModified | Boolean orString | Sets thelast-modified header to the last modified date of the file. This can be disabled by setting it tofalse, or overridden by setting it to a validDate object | |
| headers | Object | Key value pairs of additional headers to be sent with the file | |
| cacheControl | Boolean orString | Enable or disable settingcache-control response header. Override value with custom string. | true |
| private | Boolean | Sets thecache-control toprivate. | false |
res.sendFile('./img/logo.png')res.sendFile('./img/logo.png',{maxAge:3600000})res.sendFile('s3://my-bucket/path/to/file.png',{maxAge:3600000})res.sendFile(<Buffer>, 'my-file.docx',{maxAge:3600000}, (err) =>{if(err){res.error('Custom File Error')}})
Thecallback function supports returning a promise, allowing you to perform additional tasksafter the file is successfully loaded from the source. This can be used to perform additional synchronous tasks before returning control to the API execution.
NOTE: In order to access S3 files, your Lambda function must haveGetObject access to the files you're attempting to access.
SeeEnabling Binary Support for more information.
To enable binary support, you need to add*/* under "Binary Media Types" inAPI Gateway ->APIs ->[ your api ] ->Settings. This will alsobase64 encode all body content, but Lambda API will automatically decode it for you.
Path parameters are extracted from the path sent in by API Gateway. Although API Gateway supports path parameters, the API doesn't use these values but insteads extracts them from the actual path. This gives you more flexibility with the API Gateway configuration. Path parameters are defined in routes using a colon: as a prefix.
api.get('/users/:userId',(req,res)=>{res.send('User ID: '+req.params.userId);});
Path parameters act as wildcards that capture the value into theparams object. The example above would match/users/123 and/users/test. The system always looks for static paths first, so if you defined paths for/users/test and/users/:userId, exact path matches would take precedence. Path parameters only match the part of the path they are defined on. E.g./users/456/test would not match/users/:userId. You would either need to define/users/:userId/test as its own path, or create another path with an additional path parameter, e.g./users/:userId/:anotherParam.
A path can contain as many parameters as you want. E.g./users/:param1/:param2/:param3.
Wildcard routes are supported for matching arbitrary paths. Wildcards only work at theend of a route definition such as/* or/users/*. Wildcards within a path, e.g./users/*/posts are not supported. Wildcard routes do support parameters, however, so/users/:id/* would capture the:id parameter in your wildcard handler.
Wildcard routes will match any deep paths after the wildcard. For example, aGET method for path/users/* would match/users/1/posts/latest. The only exception is for theOPTIONS method. A pathmust exist for a wildcard on anOPTIONS route in order to execute the handler. If a wildcard route is defined for another method higher up the path, then theOPTIONS handler will fire. For example, if there was aPOST method defined on/users/*, then anOPTIONS method for/users/2/posts/* would fire as it assumes that thePOST path would exist.
In most cases,Path Parameters should be used in favor of wildcard routes. However, if you need to support unpredictable path lengths, or your are building single purpose functions and will be mapping routes from API Gateway, the wildcards are a powerful pattern. Another good use case is to use theOPTIONS method to provide CORS headers.
api.options('/*',(req,res)=>{// Return CORS headersres.cors().send({});});
Lambda API includes a robust logging engine specifically designed to utilize native JSON support for CloudWatch Logs. Not only is it ridiculously fast, but it's also highly configurable. Logging is disabled by default, but can be enabled by passing{ logger: true } when you create the Lambda API instance (or by passing aLogging Configuration definition).
The logger is attached to theREQUEST object and can be used anywhere the object is available (e.g. routes, middleware, and error handlers).
constapi=require('lambda-api')({logger:true});api.get('/status',(req,res)=>{req.log.info('Some info about this route');res.send({status:'ok'});});
In addition to manual logging, Lambda API can also generate "access" logs for your API requests. API Gateway can also provide access logs, but they are limited to contextual information about your request (seehere). Lambda API allows you to capture the same dataPLUS additional information directly from within your application.
Logging can be enabled by setting thelogger option totrue when creating the Lambda API instance. Logging can be configured by settinglogger to an object that contains configuration information. The following table contains available logging configuration properties.
| Property | Type | Description | Default |
|---|---|---|---|
| access | boolean orstring | Enables/disables automatic access log generation for each request. SeeAccess Logs. | false |
| errorLogging | boolean | Enables/disables automatic error logging. | true |
| customKey | string | Sets the JSON property name for custom data passed to logs. | custom |
| detail | boolean | Enables/disables addingREQUEST andRESPONSE data to all log entries. | false |
| level | string | Minimum logging level to send logs for. SeeLogging Levels. | info |
| levels | object | Key/value pairs of custom log levels and their priority. SeeCustom Logging Levels. | |
| log | function | Custom function for overriding standardconsole.log. | console.log |
| messageKey | string | Sets the JSON property name of the log "message". | msg |
| multiValue | boolean | Enables multi-value support for querystrings. If enabled, theqs parameter will return all values asarrays and will include multiple values if they exist. | false |
| nested | boolean | Enables/disables nesting of JSON logs for serializer data. SeeSerializers. | false |
| timestamp | boolean orfunction | By default, timestamps will return the epoch time in milliseconds. A value offalse disables log timestamps. A function that returns a value can be used to override the default format. | true |
| sampling | object | Enables log sampling for periodic request tracing. SeeSampling. | |
| serializers | object | Adds serializers that manipulate the log format. SeeSerializers. | |
| stack | boolean | Enables/disables the inclusion of stack traces in caught errors. | false |
Example:
constapi=require('lambda-api')({logger:{level:'debug',access:true,customKey:'detail',messageKey:'message',timestamp:()=>newDate().toUTCString(),// custom timestampstack:true,},});
Logs are generated using Lambda API's standard JSON format. The log format can be customized usingSerializers.
Standard log format (manual logging):
{"level":"info",// log level"time":1534724904910,// request timestamp"id":"41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9",// awsRequestId"route":"/user/:userId",// route accessed"method":"GET",// request method"msg":"Some info about this route",// log message"timer":2,// execution time up until log was generated"custom":"additional data",// addditional custom log detail"remaining":2000,// remaining milliseconds until function timeout"function":"my-function-v1",// function name"memory":2048,// allocated function memory"int":"apigateway",// interface used to access the Lambda function"sample":true// is generated during sampling request?}
Access logs generate detailed information about the API request. Access logs are disabled by default, but can be enabled by setting theaccess property totrue in the logging configuration object. If set tofalse, access logs willonly be generated when other log entries (info,error, etc.) are created. If set to the string'never', access logs will never be generated.
Access logs use the same format as the standard logs above, but include additional information about the request. The access log format can be customized usingSerializers.
Access log format (automatic logging):
{ ...StandardLogData...,"path":"/user/123",// path accessed"ip":"12.34.56.78",// client ip address"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)...",// User-Agent"version":"v1",// specified API version"device":"mobile",// client device (as determined by CloudFront)"country":"US",// client country (as determined by CloudFront)"qs":{// query string parameters"foo":"bar"}}
Logging "levels" allow you to add detailed logging to your functions based on severity. There are six standard log levels as specified in the table below along with their default priority.
| Level | Priority |
|---|---|
trace | 10 |
debug | 20 |
info | 30 |
warn | 40 |
error | 50 |
fatal | 60 |
Logs are written to CloudWatch LogsONLY if they are the same or higher severity than specified in thelevel log configuration.
// Logging level set to "warn"constapi=require('lambda-api')({logger:{level:'warn'}});api.get('/',(req,res)=>{req.log.trace('trace log message');// ignoredreq.log.debug('debug log message');// ignoredreq.log.info('info log message');// ignoredreq.log.warn('warn log message');// write to CloudWatchreq.log.error('error log message');// write to CloudWatchreq.log.fatal('fatal log message');// write to CloudWatchres.send({hello:'world'});});
Custom logging "levels" can be added by specifying an object containing "level names" as keys and their priorities as values. You can also adjust the priority of standard levels by adding it to the object.
constapi=require('lambda-api')({logger:{levels:{test:5,// low priority 'test' levelcustomLevel:35,// between info and warntrace:70,// set trace to the highest priority},},});
In the example above, thetest level would only generate logs if the priority was set totest.customLevel would generate logs iflevel was set to anything with the same or lower priority (e.g.info).trace now has the highest priority and would generate a log entry no matter what the level was set to.
Manual logging also allows you to specify additional detail with each log entry. Details can be added by supplingany variable type as a second parameter to the logger function.
req.log.info('This is the main log message','some other detail');// stringreq.log.info('This is the main log message',{foo:'bar',isAuthorized:someVar,});// objectreq.log.info('This is the main log message',25);// numberreq.log.info('This is the main log message',['val1','val2','val3']);// arrayreq.log.info('This is the main log message',true);// boolean
If anobject is provided, the keys will be merged into the main log entry's JSON. If any othertype is provided, the value will be assigned to a key using the thecustomKey setting as its property name. Ifnested is set totrue, objects will be nested under the value ofcustomKey as well.
Serializers allow you to customize log formats as well as add additional data from your application. Serializers can be defined by adding aserializers property to thelogger configuration object. A property named for an available serializer (main,req,res,context orcustom) needs to return an anonymous function that takes one argument and returns an object. The returned object will be merged into the main JSON log entry. Existing properties can be removed by returningundefined as their values.
constapi=require('lambda-api')({logger:{serializers:{req:(req)=>{return{apiId:req.requestContext.apiId,// add the apiIdstage:req.requestContext.stage,// add the stageqs:undefined,// remove the query string};},},},});
Serializers are passed one argument that contains their corresponding object.reqandmain receive theREQUEST object,res receives theRESPONSE object,context receives thecontext object passed into the mainrun function, andcustom receives custom data passed in to the logging methods. Note that only customobjects will trigger thecustom serializer.
If thenested option is set to true in thelogger configuration, then JSON log entries will be generated with properties forreq,res,context andcustom with their serialized data as nested objects.
Sampling allows you to periodically generate log entries for all possible severities within a single request execution. All of the log entries will be written to CloudWatch Logs and can be used to trace an entire request. This can be used for debugging, metric samples, resource response time sampling, etc.
Sampling can be enabled by adding asampling property to thelogger configuration object. A value oftrue will enable the default sampling rule. The default can be changed by passing in a configuration object with the following availableoptional properties:
| Property | Type | Description | Default |
|---|---|---|---|
| target | number | The minimum number of samples perperiod. | 1 |
| rate | number | The percentage of samples to be taken during theperiod. | 0.1 |
| period | number | Number ofseconds representing the duration of each sampling period. | 60 |
The example below would sample at least2 requests every30 seconds as well as an additional0.1 (10%) of all other requests during that period. Lambda API tracks the velocity of requests and attempts to distribute the samples as evenly as possible across the specifiedperiod.
constapi=require('lambda-api')({logger:{sampling:{target:2,rate:0.1,period:30,},},});
Additional rules can be added by specifying arules parameter in thesampling configuration object. Therules should contain anarray of "rule" objects with the following properties:
| Property | Type | Description | Default | Required |
|---|---|---|---|---|
| route | string | The route (as defined in a route handler) to apply this rule to. | Yes | |
| target | number | The minimum number of samples perperiod. | 1 | No |
| rate | number | The percentage of samples to be taken during theperiod. | 0.1 | No |
| period | number | Number ofseconds representing the duration of each sampling period. | 60 | No |
| method | string orarray | A comma separated list orarray of HTTP methods to apply this rule to. | No |
Theroute property is the only value required and must match a route's path definition (e.g./user/:userId, not/user/123) to be activated. Routes can also use wildcards at the end of the route to match multiple routes (e.g./user/* would match/user/:userIdAND/user/:userId/tags). A list ofmethods can also be supplied that would limit the rule to just those HTTP methods. A comma separatedstring or anarray will be properly parsed.
Sampling rules can be used to disable sampling on certain routes by setting thetarget andrate to0. For example, if you had a/status route that you didn't want to be sampled, you would use the following configuration:
constapi=require('lambda-api')({logger:{sampling:{rules:[{route:'/status',target:0,rate:0}],},},});
You could also use sampling rules to enable sampling on certain routes:
constapi=require('lambda-api')({logger:{sampling:{rules:[{route:'/user',target:1,rate:0.1},// enable for /user route{route:'/posts/*',target:1,rate:0.1},// enable for all routes that start with /posts],target:0,// disable sampling default targetrate:0,// disable sampling default rate},},});
If you'd like to disable sampling forGET andPOST requests to user:
constapi=require('lambda-api')({logger:{sampling:{rules:[// disable GET and POST on /user route{route:'/user',target:0,rate:0,method:['GET','POST']},],},},});
Any combination of rules can be provided to customize sampling behavior. Note that each rule tracks requests and velocity separately, which could limit the number of samples for infrequently accessed routes.
The API supports middleware to preprocess requests before they execute their matching routes. Global middleware is defined using theuse method one or more functions with three parameters for theREQUEST,RESPONSE, andnext callback. For example:
api.use((req,res,next)=>{// do somethingnext();});
Middleware can be used to authenticate requests, create database connections, etc. TheREQUEST andRESPONSE objects behave as they do within routes, allowing you to manipulate either object. In the case of authentication, for example, you could verify a request and update theREQUEST with anauthorized flag and continue execution. Or if the request couldn't be authorized, you could respond with an error directly from the middleware. For example:
// Auth Userapi.use((req,res,next)=>{if(req.headers.authorization==='some value'){req.authorized=true;next();// continue execution}else{res.error(401,'Not Authorized');}});
Thenext() callback tells the system to continue executing. If this is not called then the system will hang (and eventually timeout) unless another request ending call such aserror is called. You can define as many middleware functions as you want. They will execute serially and synchronously in the order in which they are defined.
NOTE: Middleware can use either callbacks likeres.send() orreturn to trigger a response to the user. Please note that calling either one of these from within a middleware function will return the response immediately and terminate API execution.
By default, middleware will execute on every path. If you only need it to execute for specific paths, pass the path (or array of paths) as the first parameter to theuse method.
// Single pathapi.use('/users',(req,res,next)=>{next();});// Wildcard pathapi.use('/users/*',(req,res,next)=>{next();});// Multiple pathapi.use(['/users','/posts'],(req,res,next)=>{next();});// Parameterized pathsapi.use('/users/:userId',(req,res,next)=>{next();});// Multiple paths with parameters and wildcardsapi.use(['/comments','/users/:userId','/posts/*'],(req,res,next)=>{next();});
NOTE: Path matching checks the definedroute. This means that parameterized paths must be matched by the parameter (e.g./users/:param1).
In addition to restricting middleware to certain paths, you can also add multiple middleware using a singleuse method. This is a convenient way to assign several pieces of middleware to the same path or minimize your code.
constmiddleware1=(req,res,next)=>{// middleware code};constmiddleware2=(req,res,next)=>{// some other middleware code};// Restrict middleware1 and middleware2 to /users routeapi.use('/users',middleware1,middleware2);// Add middleware1 and middleware2 to all routesapi.use(middleware1,middleware2);
Middleware can be restricted to a specific method (or array of methods) by using the route convenience methods orMETHOD. Method-based middleware behaves exactly like global middleware, requiring aREQUEST,RESPONSE, andnext parameter. You can specify multiple middlewares for each method/path using a single method call, or by using multiple method calls. Lambda API will merge theexecution stacks for you.
constmiddleware1=(req,res,next)=>{// middleware code};constmiddleware2=(req,res,next)=>{// middleware code};// Execute middleware1 and middleware2 on /users routeapi.get('/users',middleware1,middleware2,(req,res)=>{// handler function});// Execute middleware1 on /users routeapi.get('/users',middleware1);// Add middleware2 and handlerapi.get('/users',middleware2,(req,res)=>{// handler function});
The API has a built-in clean up method called 'finally()' that will execute after all middleware and routes have been completed, but before execution is complete. This can be used to close database connections or to perform other clean up functions. A clean up function can be defined using thefinally method and requires a function with two parameters for the REQUEST and the RESPONSE as its only argument. For example:
api.finally((req,res)=>{// close unneeded database connections and perform clean up});
TheRESPONSECANNOT be manipulated since it has already been generated. Only onefinally() method can be defined and will execute after properly handled errors as well.
Lambda API has sophisticated error handling that will automatically catch and log errors using theLogging system. By default, errors will trigger a JSON response with the error message. If you would like to define additional error handling, you can define them using theuse method similar to middleware. Error handling middleware must be defined as a function withfour arguments instead of three like normal middleware. An additionalerror parameter must be added as the first parameter. This will contain the error object generated.
api.use((err,req,res,next)=>{// do something with the errornext();});
Thenext() callback will cause the script to continue executing and eventually call the standard error handling function. You can short-circuit the default handler by calling a request ending method such assend,html, orjson OR byreturning data from your handler.
Error handling middleware, like regular middleware, also supports specifying multiple handlers in a singleuse method call.
consterrorHandler1=(err,req,res,next)=>{// do something with the errornext()})consterrorHandler2=(err,req,res,next)=>{// do something else with the errornext()})api.use(errorHandler1,errorHandler2)
NOTE: Error handling middleware runs onALL paths. If paths are passed in as the first parameter, they will be ignored by the error handling middleware.
Lambda API provides several different types of errors that can be used by your application.ApiError,RouteError,MethodError,ResponseError, andFileError will all be passed to your error middleware.ConfigurationErrors will throw an exception when you attempt to.run() your route and can be caught in atry/catch block. Most error types contain additional properties that further detail the issue.
consterrorHandler=(err,req,res,next)=>{if(err.name==='RouteError'){// do something with route error}elseif(err.name==='FileError'){// do something with file error}// continuenext()})
Error logs are generated using either theerror orfatal logging level. Errors can be triggered from within routes and middleware by calling theerror() method on theRESPONSE object. If provided astring as an error message, this will generate anerror level log entry. If you supply a JavaScriptError object, or youthrow an error, afatal log entry will be generated.
api.get('/somePath',(res,req)=>{res.error('This is an error message');// creates 'error' log});api.get('/someOtherPath',(res,req)=>{res.error(newError('This is a fatal error'));// creates 'fatal' log});api.get('/anotherPath',(res,req)=>{thrownewError('Another fatal error');// creates 'fatal' log});api.get('/finalPath',(res,req)=>{try{// do something}catch(e){res.error(e);// creates 'fatal' log}});
Lambda API allows you to map specific modules to namespaces that can be accessed from theREQUEST object. This is helpful when using the pattern in which you create a module that exports middleware, error, or route functions. In the example below, thedata namespace is added to the API and then accessed by reference within an included module.
The main handler file might look like this:
// Use app() function to add 'data' namespaceapi.app('data',require('./lib/data.js'));// Create a get route to load user detailsapi.get('/users/:userId',require('./lib/users.js'));
The users.js module might look like this:
module.exports=(req,res)=>{letuserInfo=req.namespace.data.getUser(req.params.userId);res.json({userInfo:userInfo});};
By saving references in namespaces, you can access them without needing to require them in every module. Namespaces can be added using theapp() method of the API.app() accepts either two parameters: a string representing the name of the namespace and a function referenceOR an object with string names as keys and function references as the values. For example:
api.app('namespace',require('./lib/ns-functions.js'));// ORapi.app({namespace1:require('./lib/ns1-functions.js'),namespace2:require('./lib/ns2-functions.js'),});
CORS can be implemented using thewildcard routes feature. A typical implementation would be as follows:
api.options('/*',(req,res)=>{// Add CORS headersres.header('Access-Control-Allow-Origin','*');res.header('Access-Control-Allow-Methods','GET, PUT, POST, DELETE, OPTIONS');res.header('Access-Control-Allow-Headers','Content-Type, Authorization, Content-Length, X-Requested-With');res.status(200).send({});});
You can also use thecors() (see here) convenience method to add CORS headers.
Conditional route support could be added via middleware or with conditional logic within theOPTIONS route.
Currently, API Gateway HTTP APIs do not support automatic compression, but that doesn't mean the Lambda can't return a compressed response. Lambda API supports compression out of the box:
constapi=require('lambda-api')({compression:true,});
The response will automatically be compressed based on theAccept-Encoding header in the request. Supported compressions are Gzip and Deflate, with opt-in support for Brotli:
constapi=require('lambda-api')({compression:['br','gzip'],});
Note: Brotli compression is significantly slower than Gzip due to its CPU intensive algorithm. Please test extensively before enabling on a production environment.
For full control over the response compression, instantiate the API withisBase64 set to true, and a custom serializer that returns a compressed response as a base64 encoded string. Also, don't forget to set the correctcontent-encoding header:
constzlib=require('zlib');constapi=require('lambda-api')({isBase64:true,headers:{'content-encoding':['gzip'],},serializer:(body)=>{constjson=JSON.stringify(body);returnzlib.gzipSync(json).toString('base64');},});
Lambda API v0.10 introduced execution stacks as a way to more efficiently process middleware. Execution stacks are automatically created for you when adding routes and middleware using the standard route convenience methods, as well asMETHOD() anduse(). This is a technical implementation that has made method-based middleware and additional wildcard functionality possible.
Execution stacks are backwards compatible, so no code changes need to be made when upgrading from a lower version. The only caveat is with matching middleware to specific parameterized paths. Path-based middleware creates mount points that require methods to execute. This means that a/users/:userId middleware path would not execute if you defined a/users/test path.
Execution stacks allow you to execute multiple middlewares based on a number of factors including path and method. For example, you can specify a global middleware to run on every/user/* route, with additional middleware running on just/user/settings/* routes, with more middleware running on justGET requests to/users/settings/name. Execution stacks inherit middleware from matching routes and methods higher up the stack, building a final stack that is unique to each route. Definition order also matters, meaning that routes definedbefore global middlewarewill not have it as part of its execution stack. The same is true of any wildcard-based route, giving you flexibility and control over when middleware is applied.
For debugging purposes, a newREQUEST property calledstack has been added. If you name your middleware functions (either by assigning them to variables or using standard named functions), thestack property will return an array that lists the function names of the execution stack in processing order.
Lambda Proxy Integration is an option in API Gateway that allows the details of an API request to be passed as theevent parameter of a Lambda function. A typical API Gateway request event with Lambda Proxy Integration enabled can be foundhere.
Lambda API automatically parses this information to create a normalizedREQUEST object. The request can then be routed using the APIs methods.
AWS recently added support for Lambda functions as targets for Application Load Balancers. While the events from ALBs are similar to API Gateway, there are a number of differences that would require code changes based on implementation. Lambda API detects the eventinterface and automatically normalizes theREQUEST object. It also correctly formats theRESPONSE (supporting both multi-header and non-multi-header mode) for you. This allows you to call your Lambda function from API Gateway, ALB, or both, without requiring any code changes.
Please note that ALB events do not contain all of the same headers as API Gateway (such asclientType), but Lambda API provides defaults for seamless integration between the interfaces. ALB also automatically enables binary support, giving you the ability to serve images and other binary file types. Lambda API reads thepath parameter supplied by the ALB event and uses that to route your requests. If you specify a wildcard in your listener rule, then all matching paths will be forwarded to your Lambda function. Lambda API's routing system can be used to process these routes just like with API Gateway. This includes static paths, parameterized paths, wildcards, middleware, etc.
Sample ALB request and response events can be foundhere.
Routes must be configured in API Gateway in order to support routing to the Lambda function. The easiest way to support all of your routes without recreating them is to useAPI Gateway's Proxy Integration.
Simply create a{proxy+} route that uses theANY method and all requests will be routed to your Lambda function and processed by thelambda-api module. In order for a "root" path mapping to work, you also need to create anANY route for/.
If you are using persistent connections in your function routes (such as AWS RDS or Elasticache), be sure to setcontext.callbackWaitsForEmptyEventLoop = false; in your main handler. This will allow the freezing of connections and will prevent Lambda from hanging on open connections. Seehere for more information.
Anindex.d.ts declaration file has been included for use with your TypeScript projects (thanks @hassankhan). Please feel free to make suggestions and contributions to keep this up-to-date with future releases.
TypeScript Example
// import AWS Lambda typesimport{APIGatewayEvent,Context}from'aws-lambda';// import Lambda API default functionimportcreateAPIfrom'lambda-api';// instantiate frameworkconstapi=createAPI();// Define a routeapi.get('/status',async(req,res)=>{return{status:'ok'};});// Declare your Lambda handlerexports.run=async(event:APIGatewayEvent,context:Context)=>{// Run the requestreturnawaitapi.run(event,context);};
Contributions, ideas and bug reports are welcome and greatly appreciated. Please addissues for suggestions and bug reports or create a pull request.
If you're using Lambda API and finding it useful, hit me up onTwitter or email me at contact[at]jeremydaly.com. I'd love to hear your stories, ideas, and even your complaints!
About
Lightweight web framework for your serverless applications
Topics
Resources
License
Security policy
Uh oh!
There was an error while loading.Please reload this page.

