One of the most popular use cases of serverless functions is deploying and running a web server complete with routing. In this tutorial, I'll show you how to get this up and running in only a few minutes using AWS Lambda, Amazon API Gateway, and AWS Amplify.
The library I'll be using is the Serverless Express project that's made specifically for this use case.
Using this library, you can easily proxy the event
and context
into the express server, and from there you'll have access to the different routes and HTTP methods like get
, post
, put
, and delete
.
app.get('/', (req, res) => {
res.json(req.apiGateway.event)
})
Getting started
There are many ways to deploy a Lambda function, you can go directly into the AWS console, use the serverless framework, or a multitude of other tools that utilize infrastructure as code under the hood.
I will be using a CLI based approach with the Amplify Framework.
To get started, first install and configure the Amplify CLI.
To see a video walkthrough of how to configure the CLI, click here.
$ npm install -g @aws-amplify/cli
$ amplify configure
Now, create a project using your JavaScript framework of choice (React, Angular, Vue etc..).
$ npx create-react-app myapp
$ cd myapp
$ npm install aws-amplify
Next, initialize a new Amplify project in the root of your JS project:
$ amplify init
# Answer the questions prompted by the CLI
Now, we can create the API and web server. To do so, we can use the Amplify add
command:
$ amplify add api
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: myapi
? Provide a path (e.g., /items): /items (or whatever path you would like)
? Choose a Lambda source: Create a new Lambda function
? Provide a friendly name for your resource to be used as a label for this category in the project: mylambda
? Provide the AWS Lambda function name: mylambda
? Choose the function template that you want to use: Serverless express function
? Do you want to access other resources created in this project from your Lambda function? N
? Do you want to edit the local lambda function now? N
? Restrict API access: N
? Do you want to add another path? N
The CLI has created a few things for us:
- API endpoint
- Lambda function
- Web server using Serverless Express in the function
- Some boilerplate code for different methods on the
/items
route
Let's open the code for the server.
Open amplify/backend/function/mylambda/src/index.js. Here, you will see the main function handler with the event
and context
being proxied to an express server located at ./app.js
:
const awsServerlessExpress = require('aws-serverless-express');
const app = require('./app');
const server = awsServerlessExpress.createServer(app);
exports.handler = (event, context) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
awsServerlessExpress.proxy(server, event, context);
};
Next, open amplify/backend/function/mylambda/src/app.js.
Here, you will see the code for the express server and some boilerplate for the different HTTP methods for the route we declared. Find the route for app.get('/items')
and update it to the following:
// amplify/backend/function/mylambda/src/app.js
app.get('/items', function(req, res) {
const items = ['hello', 'world']
res.json({ success: 'get call succeed!', items });
});
We can test it locally before deploying it, but we first need to install the dependencies for the Lambda:
$ cd amplify/backend/function/mylambda/src && npm install && cd ../../../../../
To invoke the function and start the server, run the following command:
$ amplify function invoke mylambda
Now, the server is running on port 3000 and we can make requests against it. To do this from the command line, we can run this curl command:
$ curl http://localhost:3000/items
# {"success":"get call succeed!","items":["hello","world"]}%
To deploy the API and function, we can run the push
command:
$ amplify push
Now, from any JS client, you can start interacting with the API:
// get request
const items = await API.get('myapi', '/items')
// post with data
const data = { body: { items: ['some', 'new', 'items'] } }
await API.post('myapi', '/items', data)
From here, you may want to add additional path. To do so, run the update command:
$ amplify update api
From there, you can add, update, or remove paths.
For more info on the API category, click here.
Top comments (6)
Thank you for a very interesting article. I just have a quick question regarding this architecture.
There have been concerns surrounding using Lambda to serve as express servers, specifically surrounding the fact that Lambda has a cold start and therefore is unable to serve traffic on the same level as a regular docker container / EC2 instance.
As I am quite new to this architecture, would you mind providing some insight into this issue, as well as why one would look to migrate to this new design?
The python and javascript runtimes provided by AWS for lambda are quite fast, and experience fairly short cold starts in comparison with larger VM based frameworks, such as Java and .NET. If you are using a smaller framework like express or flask, a typical cold start is usually under one second.
Two new features were announced recently to help performance issues. The first is provisioned concurrency, which allows you to set a number of lambda instances that are kept warm. This helps alleviated some of the issues with cold-start penalties and helps with long-tail latency. Secondly, HTTP API's were announced for API Gateway - this mode strips back some of the features of the existing API Gateway service and offers better performance for web frameworks like flask and express.
In most circumstances it's a cost optimization. Re-homing from EC2 or a container will likely result in less management and lower cost - though it does depend on the traffic to the application in question. For example, if you experience spiky traffic load, and/or demand that follows a certain pattern (e.g. significantly fewer users at certain periods), lambda's scale-to-zero model results in lower costs over other alternatives.
There are more advanced ways to architect an application use API Gateway, with things like VTL transformations/service integrations etc, and these offer better cost and performance, but asks for greater investment from a development team to learn and understand. You can often get a quicker return on your investment sticking with existing frameworks that your team is already familiar with.
As such, using light frameworks such as express and flask are viable ways to serve content from lambda and is a good way to start getting to grips with serverless without needing to throw out all your existing knowledge. It's a good example of AWS meeting people where they are.
Details:
aws.amazon.com/blogs/compute/annou...
aws.amazon.com/blogs/aws/new-provi...
amplify function invoke mylambda
I have found this to be cleaner than distributing my endpoints through several unique functions, even using the Serverless Framework for NodeJS. There are some drawbacks, for example, with a dedicated function per endpoint I can scale and warm independently and the Serverless Framework gives me the ability to keep everything in one codebase, configuration as code and share code similar to this approach. More ramblings: Amplify looks like its starting to sprawl.
I know that "monolith Lambdas" are supposed to be an anti-pattern, but this seems like a great way to facilitate code reuse and help migrate existing codebases to serverless.
It's weird that the npm module page for the official tooling has such poor documentation and you have to then find the documentation yourself.