Deploying Express/Fastify Apps to AWS Lambda with Serverless Framework
Min-jun Kim
Dev Intern · Leapcell

Introduction
In today's fast-paced cloud-native landscape, developers are constantly seeking ways to build and deploy applications more efficiently, cost-effectively, and scalably. Traditional server-based deployments often come with the overhead of managing infrastructure, scaling, and patching. Serverless computing, particularly with AWS Lambda, offers a compelling alternative by abstracting away server management, allowing developers to focus solely on their application logic. However, migrating existing monolithic or even microservice-based Express or Fastify applications to a serverless architecture can seem daunting. The good news is, you don't always need to rewrite your entire codebase. This article will explore how to effortlessly deploy your Express or Fastify applications to AWS Lambda using the Serverless Framework, unlocking the benefits of serverless without abandoning your familiar JavaScript frameworks.
Understanding the Serverless Transformation
Before diving into the deployment process, let's clarify some core concepts crucial for understanding how our traditional web applications fit into a serverless paradigm.
Core Terminology
- AWS Lambda: A compute service that lets you run code without provisioning or managing servers. You only pay for the compute time you consume.
- API Gateway: A fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. It acts as the "front door" for applications running on Lambda.
- Serverless Framework: An open-source CLI tool that helps you build, deploy, and manage serverless applications on various cloud providers, including AWS. It simplifies the definition and deployment of serverless resources.
- Lambda Proxy Integration: A specific integration type in API Gateway that allows API Gateway to pass the entire request as-is to the backend Lambda function and return the entire Lambda response as-is to the client. This is crucial for frameworks like Express or Fastify that expect direct access to HTTP request and response objects.
serverless-http
: A lightweight Node.js module that acts as a bridge between HTTP frameworks (like Express, Fastify) and AWS Lambda's event object. It adapts the Lambda event structure to match the standard Node.jshttp.IncomingMessage
andhttp.ServerResponse
objects that these frameworks expect.
How It Works
The fundamental idea is to "wrap" your Express or Fastify application within an AWS Lambda function. When a request comes in through API Gateway, instead of directly invoking your Lambda function with a simple event, we configure API Gateway to use Lambda Proxy Integration. This means API Gateway sends the raw HTTP request details (headers, body, method, path, query parameters) to your Lambda function as a JSON object.
Inside your Lambda function, the serverless-http
library plays a pivotal role. It intercepts this API Gateway proxy event, transforms it into an http.IncomingMessage
and http.ServerResponse
object, and then passes these to your Express or Fastify application's request handler. Your application processes the request as it normally would. Once your application generates a response, serverless-http
captures that, transforms it back into the format API Gateway expects (a JSON object with statusCode
, headers
, and body
), and returns it to API Gateway, which then sends it back to the client.
This architecture allows your existing Express/Fastify logic to run almost unmodified, benefiting from Lambda's auto-scaling, pay-per-execution model, and reduced operational overhead.
Practical Deployment Example
Let's walk through an example using an Express application. The process for Fastify is very similar.
1. Project Setup
First, initialize a new Node.js project.
mkdir express-lambda-app cd express-lambda-app npm init -y
2. Install Dependencies
Install Express and serverless-http
. We'll also install serverless
globally or locally.
npm install express serverless-http npm install -g serverless # or npm install --save-dev serverless
3. Create Your Express Application (app.js
)
Create a file named app.js
with your Express application logic.
// app.js const express = require('express'); const app = express(); const port = 3000; // This port won't be used in Lambda, but useful for local testing app.use(express.json()); // For parsing application/json app.get('/', (req, res) => { res.send('Hello from Express on Lambda!'); }); app.get('/users/:id', (req, res) => { const userId = req.params.id; res.json({ message: `Fetching user with ID: ${userId}` }); }); app.post('/data', (req, res) => { const data = req.body; res.json({ message: 'Data received', data: data }); }); // For local testing if (process.env.NODE_ENV !== 'production') { app.listen(port, () => { console.log(`Local Express app listening at http://localhost:${port}`); }); } module.exports = app;
4. Create Your Lambda Handler (handler.js
)
This file will contain the actual Lambda function that serverless-http
uses.
// handler.js const serverless = require('serverless-http'); const app = require('./app'); // Wrap the Express app with serverless-http module.exports.handler = serverless(app);
5. Configure Serverless (serverless.yml
)
This is the heart of your serverless deployment. Create a serverless.yml
file.
# serverless.yml service: express-lambda-app frameworkVersion: '3' provider: name: aws runtime: nodejs18.x # Choose a suitable Node.js runtime region: us-east-1 # Your preferred AWS region memorySize: 128 # Minimum memory size stage: dev # Deployment stage (e.g., dev, prod) environment: # Environment variables for your Lambda function NODE_ENV: production apiGateway: # This setting crucial for API Gateway to pass raw requests to Lambda minimumCompressionSize: 1024 # Enable gzip compression for responses larger than 1KB functions: api: handler: handler.handler # Points to handler.js and its 'handler' export events: - http: # This defines an API Gateway HTTP endpoint path: / # Catches requests to the root path method: any # Catches any HTTP method (GET, POST, PUT, DELETE, etc.) cors: true # Enable CORS for this endpoint - http: path: /{proxy+} # Catches all sub-paths (e.g., /users, /users/123, /data) method: any cors: true
Explanation of serverless.yml
:
service
: The name of your serverless service.frameworkVersion
: Specifies the version of the Serverless Framework being used.provider
: Configures the cloud provider (AWS in this case).runtime
: The Node.js version for your Lambda function.region
: The AWS region where your resources will be deployed.memorySize
: Allocated memory for the Lambda function.stage
: The deployment stage.environment
: Environment variables accessible inside your Lambda.apiGateway
: Specific API Gateway configurations.minimumCompressionSize
is good for performance.
functions
: Defines your AWS Lambda functions.api
: The name of our Lambda function.handler
: Specifies the file and export function (e.g.,handler.js
'shandler
export).events
: This is where we link our Lambda to an API Gateway HTTP endpoint.- The two
http
events are critical: one for the root path/
and another for all subsequent paths/{proxy+}
. This ensures all requests to your API Gateway are routed to your single Lambda function and processed by your Express app.proxy+
is a wild card that captures all paths after the base path. method: any
allows your Express app to handle all HTTP methods.cors: true
enables Cross-Origin Resource Sharing, often vital for front-end applications.
- The two
6. Deploy Your Application
Finally, deploy your application using the Serverless Framework CLI:
serverless deploy
The CLI will package your code, create the necessary AWS resources (Lambda function, API Gateway endpoint, IAM roles), and provide you with the API Gateway endpoint URL upon successful deployment.
7. Test Your Application
Once deployed, you can access your Express application via the provided API Gateway URL.
For example, if your URL is https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
:
GET https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
will return "Hello from Express on Lambda!".GET https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/123
will return{"message": "Fetching user with ID: 123"}
.POST https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/data
with a JSON body like{ "name": "Alice" }
will return{"message": "Data received", "data": {"name": "Alice"}}
.
Fastify Application Example
The process for Fastify is almost identical.
1. Install Dependencies
npm install fastify serverless-http # And serverless as before
2. Create Your Fastify Application (fastify-app.js
)
// fastify-app.js const fastify = require('fastify'); const app = fastify({ logger: true }); // Enable logger for better development experience app.get('/', async (request, reply) => { return { message: 'Hello from Fastify on Lambda!' }; }); app.get('/users/:id', async (request, reply) => { const userId = request.params.id; return { message: `Fetching user with ID: ${userId}` }; }); app.post('/data', async (request, reply) => { const data = request.body; return { message: 'Data received', data: data }; }); // For local testing if (process.env.NODE_ENV !== 'production') { app.listen({ port: 3000 }, (err, address) => { if (err) { app.log.error(err); process.exit(1); } console.log(`Local Fastify app listening at ${address}`); }); } module.exports = app;
3. Create Your Lambda Handler (fastify-handler.js
)
// fastify-handler.js const serverless = require('serverless-http'); const app = require('./fastify-app'); // Wrap the Fastify app with serverless-http (ensure it's initialized first) // Note: serverless-http needs the Fastify instance, not just the function that creates it. // We also need to await the ready promise for Fastify before passing it to serverless-http const handler = async (event, context) => { await app.ready(); // Ensure Fastify plugins are loaded const serverlessHandler = serverless(app); return serverlessHandler(event, context); }; module.exports.handler = handler;
Important Note for Fastify: serverless-http
expects an already initialized application instance. For Fastify, you should usually call await app.ready()
to ensure all plugins are loaded before serverless-http
attempts to process requests. This is handled by wrapping serverless(app)
in an async
function.
4. Configure Serverless (serverless.yml
) for Fastify
Just update the handler
path in serverless.yml
:
# serverless.yml (excerpt for Fastify) functions: api: handler: fastify-handler.handler # Points to fastify-handler.js and its 'handler' export events: - http: path: / method: any cors: true - http: path: /{proxy+} method: any cors: true
Then serverless deploy
as before.
Application Scenarios and Best Practices
This approach is ideal for:
- Migrating existing web applications: No need for full rewrites.
- Building APIs with established frameworks: Leverage the robust features of Express or Fastify in a serverless environment.
- Rapid prototyping: Quickly deploy an API without managing servers.
- Microservices: Each microservice can be deployed as a separate Express/Fastify app on Lambda.
Best Practices:
- Keep Lambda cold starts in mind: While
serverless-http
is efficient, complex Express/Fastify apps can still incur cold start penalties. Consider provisioned concurrency for performance-critical endpoints. - Optimize dependencies: Bundle only necessary dependencies using tools like
webpack
oresbuild
with Serverless Framework plugins to reduce deployment package size and cold start times. - State management: Remember Lambda functions are stateless. Use external services like DynamoDB, RDS, S3, or ElastiCache for persistent data storage.
- Logging and monitoring: Utilize AWS CloudWatch for logs and metrics.
- Error handling: Implement robust error handling within your Express/Fastify app and consider using services like AWS X-Ray for distributed tracing.
- Environment variables: Use Serverless Framework's
environment
section inserverless.yml
or AWS Secrets Manager/Parameter Store for sensitive configurations.
Conclusion
Deploying Express or Fastify applications to AWS Lambda with the Serverless Framework and serverless-http
offers a powerful pathway to serverless adoption. It allows developers to harness the scalability, cost-effectiveness, and operational simplicity of Lambda without abandoning their preferred JavaScript web frameworks. By understanding the integration mechanism and following best practices, you can successfully transition your existing, or build new, serverless web applications with minimal friction. This approach empowers developers to focus on building features, not managing infrastructure.