Frictionless Serverless Development: Part 2 — Express, Configuration, Integration and Deployment
A step-by-step guide on configuring, integrating, and deploying an Express application in a serverless environment, aiming to simplify the process and improve the developer experience.
Welcome to Part 2 of the Frictionless Serverless Development series. In Part 1, we described setting up your environment to ensure that we had code consistency. In this part, we’re going to discuss starting to configure your serverless environment to utilize express, integrating it and deploying it to AWS so that you have a workable codebase as to which to start working with.
Overview
First things first, in this article, we’ll be targeting configuring the serverless framework through the serverless.yml
file, setting up Express, Github Actions for executing our CI/CD workflow. For now, we won’t have much CI but more CD for deployment measures since our code base is just starting out. Future parts will start to discuss the continuous integration portion as we find ourselves needing testing on a deeper level. As with last time, lets start with an overview of each of the specific technologies involved.
Serverless Framework
The Serverless Framework is one of the largest frameworks for doing serverless based development on AWS. We’ll be targeting the serverless.yml
file specifically which holds our configuration as to how we do things as we move forward. We’ll be touching on this briefly and extending on it more throughout the series as we build up to our end goal of a complete starter template.
Github Actions
GitHub Actions makes it easy to automate all your software workflows. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want by specifying a workflow within your Github project to execute based on various actions.
AWS
At this point in the process, you are going to need an AWS account to be able to move forward. Don’t worry, you won’t be spending much money if any at all (if you’re a new subscriber you’re eligible for the free tier which includes several goodies). In addition to that, you’ll want to install and configure the AWS CLI.
Segue: Express in Serverless Setups
Before I continue, Express and Serverless work extremely well together. However, it is not always the recommended setup especially if you are targeting having micro-services. Why do I mention this? Well, there will be some that completely disagree with this approach and that you should have a single function per operation. This works very nicely until you have what we call “cold-boot times”. Lambda and Serverless work in that each operation that is called will leverage a “warm” container or start up a “cold” container. In smaller sites (any operation being called in less than 15 minutes), will encounter a cold-boot where the operation needs to be spun up, this means that if not all your functions are called within 15 minutes you might be encountering a 500ms+ delay until your function is ready to be called. Let’s face the facts, a monolith will outperform micro-services on a smaller startup and if you’re no longer a smaller startup then express is likely not where you need to be. We’ve done both, we’ve analyzed the facts and unless you’re driving hundreds of thousands to your site in a given day, then express will do a better job for you at a better expense rate than you can do within micro-services.
Getting Started
Now that we know the tools that we’ll be interacting with in this part of the series, how about we get started to carry on from Part 1. We assume at this part that you’ve setup your initial project and now you need to create a Github repository to house the project, if you do not have one, now is the time to create one!
Secondly, we assume that you’ve started from the prior article and that everything is intact and ready to start being worked on.
The serverless.yml
file
The serverless.yml
file contains practically everything that we need to start with. This handles a lot of our AWS configurations as well as creating a CloudFormation template for which it will deploy (we’ll be diving much deeper into this as we progress through this series).
The process ultimately is that serverless is a wrapper around CloudFormation and S3. The serverless framework will upload a zip file of your configuration to an S3 bucket along with the code for Lambda to ultimately deploy everything inside of it. There really are two parts, first of which is the CloudFormation template, if anything is wrong the process will bail out. The second is the code which you’ll find out problems when attempting to run the processes.
Don’t worry, you can always look inside of the CloudFormation or CloudWatch area for various logs and information to determine if there are any issues which we will talk about as we move on!
Configuring serverless.yml
First things first, our project was configured based on our initial install. This is fine, however, we’re probably going to want to do a bit of changes as we move forward. First and foremost, the serverless.yml
file is essentially a proxy to CloudFormation but even more so it offers us the ability to do far more advanced configuration within the framework itself.
As of right now, we have a serverless.yml
file that looks like:
What this means, is that we have a service using the provider of aws and a handler that is just a hello world inside of a specific function. We want to move this to start utilizing the Express framework as we move forward. This is a controversial move which I’ll be explaining more about later on in this article.
Let’s start with the service:
portion of this configuration. This is the name of the project that will appear inside of the CloudFormation area along with the Stage inside of our Stacks. Essentially CloudFormation forms our stack to allow us to setup and execute everything for repeatability.
In this case, if we were to deploy to stage dev
we would have frictionless-serverless-dev
as our CloudFormation template. The CloudFormation template would handle creating our API Gateway, Lambda Function, Log Group (for the hello function and any other functions) and IAM roles required to interact with each area. This will continue to be the same throughout each individual function that we add from here on out. This is different than using AWS SAM specifically as that serverless framework dictates things to not be in a single cloudformation template but multiple by stage.
Now, there are a few things we might want to add to that configuration file. Specifically the following:
frameworkVersion: 3
— This tells us which version of the framework to be using, which at this articles publishing is version 3.configValidationMode: error
— Set this to error so that any deprecations are caught and fixed when they happen rather than having to panic at a deployment when you need to fix it later.provider.memorySize: 256
— Set to 256mb, Express can be larger, you’ll want to ensure you check the optimizer to see what you need which will tell you the appropriate size to save on cost.provider.timeout: 28
— Timeout on lambda is 29 seconds, so let’s timeout a second before so we can capture any errors which will be logged right now into CloudWatch.provider.logRetentionInDays: 7
— I’m not sure about you but logs are not especially valuable later on, sometimes they can be but most often not when they’re transactional logs, configure this for what the company desires.provider.versionFunctions: false
— We already have the version in Github for one, secondly, we’ll quickly run out of space to deploy if we’re versioning each function and that’ll be an issue. Set this tofalse
always.provider.stage: ${opt:stage, 'dev'}
— This determines the stage of your API, without thesls deploy -s STAGE
it’ll default todev
.provider.region: us-east-1
— The AWS region to deploy to. We usually go to us-east-2 or us-west-2 to avoid areas that are more common for catastrophic events or areas that have outages after Re::Invent. :)
Installing Express
So we understand what is happening now with serverless.yml
we need to start get Express working. Well, Express out of the box is not meant to work with how Serverless and AWS works in a serverless context so we need to work within a different library.
Whaat? Yep! Inside of a serverless function, we do not have the same variables available to us, so we need an intermediary to make everything work as designed. This doesn’t mean that we’re overtaking everything but rather doing it where we want to have Express based routing.
Onto the install and cleanup of the existing files:
So now, we have a package installed, but nothing really more than that. This is not end-game, so we have a few things that we need to do to finish up what we started here.
In their documentation, there are a few different approaches but the basic approach seems to work the best overall. I’m assuming here that your website is build out statically and that we simply need an API to interact with here. Use cases vary, however, this is the most common setup.
Now you need to edit api/index.js
to be the contents of:
What this is doing is including serverless-express
and initializing it to marshal the request to Express, however, we do not yet have api/app.js
so let’s create that as well which you will need to yarn add
each of the requirements here with exception of the routes:
Here is api/app.js
in it’s entirety:
Lets create a simple indexRouter
so that we can easily test that the api has been deployed:
Now create a file in /api/routes/index.js
with the following content:
So everything is configured, or is it? Opie, we forgot the serverless.yml
file. If we do not configure that nothing will happen. Ok, so we’ll remove our existing handler and replace it with a new API function. Our serverless.yml
file now looks like:
WHOA…. ok, let’s pause for a second. We kind of explained the provider section above but what in the… what is this whole functions area with this star thing… How did this even work? Well, as you can see, we’re using Express basically like it was intended to inside of Serverless (API Gateway + Lambda). Let’s dig a little deeper shall we?
Routing Express
So, routing is a thing in Serverless, it has a unique syntax where by there are different formats depending on the AWS API type you are using (to be concise we’re not going to support others than the latest). What we are saying to API Gateway is to create a Proxy that navigates anything *
into the handler api/index.handler
on a web request. Now Express has everything it needs to move onwards.
So given our previous serverless.yml
file, we can now route Express routes. Amazing! So we know we have an index route now that should return json of:
When we call the main index of the site. But we have not been able to deploy and test that have we?
Github Actions
For our CI/CD approach, we leverage Github Actions exclusively. Why? They’re low cost, easy to integrate and well, it’s the most simple and easy appraoch!
If you have never used Github actions before, each area is defined inside of a yml
file, inside of a directory: gihub/workflows
. Inside of that folder will contain various workflows that are necessary. Next, we will need to configure secrets to allow our actions to access secret variables that can execute our necessary tasks. Configuring these is as simple as going to your repository -> Settings -> Secrets -> Actions and configuring them as you need. In this case, you need two secrets:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
These are configured through the IAM console inside of the AWS Console. Normally, we will create a user and grant it permissions to everything it needs to create. Depending on your setup this user might have a lot of access, but we normally configure a github
user that determines this with it’s secret key id and access key to store inside of the github secrets area.
Finally, let’s setup the CI/CD process (well more CD than anything else at this point). Let’s create the file: .github/workflows/deploy-main.yml
which is responsible for deploying anything that has been committed to the main
branch.
What Now?
This will deploy the mainline to the stage dev
based on the push to a branch main
. You can do this on pr, branch, tag, etc. However, normally you will want the mainline to be the development environment for the first round of integration testing (more on that later). Go into the AWS console -> API Gateway -> Default Stage and you have the URL to test that you receive:
Next Steps
Following this article, we’ll start to discuss getting our authentication setup so that we can authenticate a user and create protected routes.
Related posts
The best ideas don't wait. Let's talk & make it happen.
© Spark Labs 2025. All rights reserved.