TL;DR: This guide explains the shift from traditional 24/7 Node.js Express servers to event-driven serverless architecture on AWS. You will learn how to replace persistent
app.listen()setups with the Serverless Framework, using aserverless.ymlfile to map HTTP GET requests to dormant Node.js 18.x functions that only bill by the millisecond.
⚡ Key Takeaways
- Replace traditional 24/7 Express.js
app.listen()servers to eliminate paying for idle CPU and RAM usage. - Deploy event-driven functions to AWS that remain dormant and only bill by the millisecond of active execution time.
- Configure the Serverless Framework using a
serverless.ymlfile to define your AWS provider and Node.js 18.x runtime. - Map HTTP GET requests directly to exported JavaScript functions (like
handler.hello) to trigger code execution on demand. - Leverage automatic scaling to instantly provision isolated function instances during sudden traffic surges without crashing.
Imagine renting a massive, 24-room mansion. You only sleep in one bedroom and cook in one kitchen. Yet, at the end of the month, the landlord charges you rent, electricity, and heating for all 24 rooms, 24 hours a day, 7 days a week.
This scenario sounds like a terrible waste of money. But for decades, this is exactly how businesses have built and hosted software on the internet.
When you hear the term "serverless," it sounds like magic. How can a website run without a server? The truth is, the name is a bit of a misnomer. Serverless computing still relies on physical servers. The difference is that you don't own them, you don't maintain them, and most importantly, you don't pay for them when they aren't actively processing requests.
In this guide, we will break down exactly what serverless computing is, why it is replacing older hosting models, and how you can write your very first piece of serverless code.
The Problem: Paying for Empty Servers (Traditional Hosting)
To understand serverless, we first have to understand the problem it solves.
In traditional hosting, when you want to deploy a website or an application, you rent a dedicated computer (a server) or a virtual machine from a cloud provider. That server is turned on and running continuously.
Let's look at what a standard backend application looks like in Node.js (a popular programming language).
// A Traditional Server (Express.js)
// 1. Import the Express library to build our server
const express = require('express');
const app = express();
// 2. Define how the server responds to web traffic
app.get('/hello', (req, res) => {
res.send('Hello, World!');
});
// 3. THE PROBLEM: The server actively listens 24/7 on port 3000
app.listen(3000, () => {
console.log('Server is running and listening forever...');
});
Look closely at step 3. The app.listen() command is the core of traditional hosting. It means your application is continuously running, waiting for someone to make a request.
If no one visits your website at 3:00 AM, your server is still awake. Its CPU is spinning, its RAM is occupied, and your hosting provider is charging you by the hour. Conversely, if a sudden surge of 10,000 visitors arrives at 9:00 AM, your single server might get overwhelmed and crash because its memory and processing power are limited.
You are paying for empty rooms in the mansion, and if you throw a massive party, the house isn't big enough to hold everyone.
The Answer: What Exactly is Serverless?
Serverless computing changes the rules of the game. Instead of renting a persistent server, you write individual functions and upload them to a cloud provider, such as AWS (Amazon Web Services).
Think of serverless like tap water in your home. When you turn the handle, the water flows immediately. When you turn it off, the water stops. You only pay for the exact volume of water you consume.
With serverless, your code stays dormant and costs absolutely nothing. When a user interacts with your application, AWS instantly allocates resources, runs your code, sends the response, and then spins the resources back down. You are billed by the millisecond of active execution time.
To tell AWS how to handle this code, developers typically use a configuration file. Here is what a beginner-friendly serverless.yml file looks like using the Serverless Framework (a popular tool that automates deployment):
# serverless.yml - The Blueprint for our App
# 1. Name of your application
service: my-first-serverless-app
# 2. Define the cloud provider and environment
provider:
name: aws
runtime: nodejs18.x # We are using Node.js version 18
# 3. Define the functions and what triggers them
functions:
helloFunction:
handler: handler.hello # Look for the 'hello' exported function inside 'handler.js'
events:
- http:
path: /hello # When someone visits yoursite.com/hello
method: get # Trigger this function via a GET request
Beginner Tip: YAML (
.yml) is a standard format for writing configuration files. It uses indentation (spaces) instead of brackets to organize data. In the file above, we are simply giving AWS a map: "When a user makes a GET request to/hello, run myhelloFunction."
Why It Matters: The Benefits of Going Serverless
Why are massive enterprises and tiny startups alike moving to this model?
- You never pay for idle time: If you get zero visitors on Tuesday, your server bill for Tuesday is exactly $0.00.
- Infinite, automatic scaling: If 10,000 users click a link to your app at the exact same second, AWS instantly provisions 10,000 isolated instances of your function. Every request gets processed simultaneously.
- Zero server maintenance: You don't have to install security patches, update operating systems, or worry about failing hard drives.
When we design systems for our clients using our comprehensive DevOps and cloud deployment services, serverless is often our first choice. It drastically reduces the anxiety of an application crashing on launch day due to unexpected traffic spikes.
In the AWS ecosystem, a serverless function is called an AWS Lambda. To prove that the code only runs when requested, here is a command-line snippet showing how a developer can manually trigger a serverless function using the AWS Command Line Interface (CLI):
# This command forcefully invokes the function in the cloud,
# executes it once, and saves the output to a file called response.json
aws lambda invoke \
--function-name my-first-serverless-app-dev-helloFunction \
response.json
# Example Output in your terminal:
# {
# "StatusCode": 200,
# "ExecutedVersion": "$LATEST"
# }
How to Use It: Writing Your First Serverless Function
Let's look at the actual code that goes inside an AWS Lambda function. Notice how there is no app.listen() command here.
Create a file called handler.js:
// handler.js
// We export a function called 'hello' so AWS can execute it
module.exports.hello = async (event) => {
// 'event' contains all the data about the incoming HTTP request,
// including headers, query parameters, and the request body.
console.log("Function triggered by an event!", event);
// We return an HTTP-formatted response back to the client
return {
statusCode: 200, // 200 means "OK" or "Success"
body: JSON.stringify({
message: "Hello from the Serverless Cloud! You only paid for 10 milliseconds of compute time.",
}),
};
};
Here is exactly what happens when this code is deployed:
- AWS securely stores your
handler.jsfile in the cloud. - A user visits your API endpoint URL.
- AWS provisions a secure micro-container, loads Node.js, and executes your
hellofunction. - The user receives the JSON response.
- AWS puts the container back to sleep (and eventually destroys it if unused).
You don't worry about servers, ports, or operating systems. You just write the business logic.
A Real-World Example: Processing User Signups
Let's apply this to a practical scenario. Imagine you have a simple webpage with a form where users can join a waitlist.
Your frontend (the part the user sees in their browser) is built with standard HTML and JavaScript. It sends a message to your serverless backend.
<!-- index.html (Frontend) -->
<script>
async function joinWaitlist() {
const userEmail = "john@example.com";
// Send data to our serverless API
const response = await fetch('https://api.yoursite.com/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: userEmail })
});
const data = await response.json();
alert(data.message);
}
</script>
<button onclick="joinWaitlist()">Join Waitlist</button>
Now, let's look at the serverless backend that receives this POST request, extracts the email, and simulates saving it to a database.
// signupHandler.js (Backend AWS Lambda)
module.exports.signup = async (event) => {
// 1. Extract the payload sent by the frontend
// The event.body contains the stringified JSON from the fetch() call
const requestData = JSON.parse(event.body);
const newEmail = requestData.email;
// 2. Database logic goes here
// await database.save(newEmail);
console.log(`Successfully saved ${newEmail} to the database.`);
// 3. Send a success response back to the browser
return {
statusCode: 200,
// CORS headers allow browsers to safely make requests across different domains
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({
message: "Welcome to the waitlist! We saved your email."
})
};
};
In a traditional setup, the server waiting for this form submission would be consuming resources all night. In our serverless architecture, the database-saving logic only runs for the split second the user clicks the "Join Waitlist" button.
How Serverless Changes the Development Process
Moving to serverless doesn't just lower your cloud bill; it transforms how your entire engineering team works.
Because developers no longer have to spend hours setting up Linux environments, configuring firewall rules, or worrying about server memory limits, they can focus 100% of their time on building features that users actually care about.
This perfectly aligns with our structured development process and how we build applications. We use automated pipelines to instantly push code from a developer's local machine directly into the AWS cloud.
Here is an example of an automated deployment script using GitHub Actions. Every time a developer merges code, this workflow packages and deploys the new serverless infrastructure automatically:
# .github/workflows/deploy.yml
name: Deploy Serverless App
on:
push:
branches:
- main # Run this workflow when code is pushed to the 'main' branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Dependencies
run: npm ci
- name: Deploy to AWS
# This single command packages the app and provisions the AWS infrastructure
run: npx serverless deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
The Catch: Understanding "Cold Starts"
No technology is perfect, and serverless computing has one well-known drawback that beginners must understand: the Cold Start.
Remember the tap water analogy? When your serverless function hasn't been invoked for a while (usually around 15–30 minutes), AWS reclaims the resources and puts the function into a "cold" state. When a new user visits, AWS has to fetch your code, boot up a micro-container, load the runtime environment (like Node.js), and then execute your code.
This initialization process takes time—sometimes adding a 1 to 2-second delay to the user's request.
Production Warning: A 2-second delay can negatively impact user experience on user-facing APIs. To mitigate this, developers use plugins or scheduled events to routinely "ping" the function every few minutes, preventing it from ever falling asleep.
Here is a common pattern for handling automated "warmup" pings so they don't execute your full business logic:
// handler.js (With Cold Start protection)
module.exports.hello = async (event) => {
// If the event is generated by an automated warmup plugin, exit early
if (event.source === 'serverless-plugin-warmup') {
console.log('Keeping the function warm!');
return 'Warmed';
}
// Otherwise, process the real user request
return {
statusCode: 200,
body: JSON.stringify({ message: "Instant response for the user!" }),
};
};
Taking the Next Step
Serverless computing has fundamentally shifted modern web architecture. It encourages modular code, drastically reduces idle server costs, and handles sudden traffic spikes seamlessly.
To deploy your first application, all you need is an AWS account, the Serverless Framework, and a simple terminal command:
# Deploys your entire architecture to the cloud in under 2 minutes
npx serverless deploy
If you are planning a new software project and want to ensure it is built to handle thousands of users without bankrupting your hosting budget, you might need a guiding hand. If you want to transition your current app to this highly efficient architecture, you can book a free architecture review with our backend engineers to see if serverless is the right fit for your business goals.
Need help building this in production?
SoftwareCrafting is a full-stack dev agency — we ship fast, scalable React, Next.js, Node.js, React Native & Flutter apps for global clients.
Get a Free ConsultationFrequently Asked Questions
Does "serverless" mean there are no physical servers involved?
No, serverless computing still relies on physical servers managed by cloud providers like AWS. The term simply means that developers do not have to provision, maintain, or pay for these servers when they are idle. You upload your code, and the cloud provider handles the underlying infrastructure entirely.
How does serverless pricing differ from traditional server hosting?
Traditional hosting requires you to pay for a server running 24/7, meaning you are charged for idle time even when traffic is zero. Serverless computing bills you only for the exact milliseconds your code takes to execute. If your application receives no visitors, your hosting cost for that period is exactly zero.
How do serverless applications handle sudden spikes in web traffic?
Serverless architectures offer infinite, automatic scaling by provisioning resources strictly on demand. If thousands of users access your app simultaneously, the cloud provider instantly spins up an isolated instance of your function for each request. This prevents the crashes often seen when traditional single servers get overwhelmed by traffic surges.
What is the role of the serverless.yml file in deployment?
The serverless.yml file acts as a configuration blueprint for your application when using the Serverless Framework. It tells the cloud provider which runtime to use (like Node.js), defines your functions, and maps out the specific HTTP events (like a GET request) that trigger those functions to run.
How can I transition my existing Express.js application to a serverless architecture?
Transitioning requires decoupling your continuous app.listen() server into individual, event-triggered functions mapped via a configuration file. If you need expert help migrating legacy applications, SoftwareCrafting services include comprehensive DevOps and cloud deployment solutions to ensure a smooth, cost-effective transition to AWS.
Do I still need to perform server maintenance and security patching with serverless?
No, one of the primary benefits of serverless computing is zero server maintenance. The cloud provider automatically handles operating system updates, security patches, and hardware failures for you. However, if you want to ensure your overall cloud architecture is optimized and secure, SoftwareCrafting services can design and manage your complete DevOps pipeline.
📎 Full Code on GitHub Gist: The complete
app.jsfrom this post is available as a standalone GitHub Gist — copy, fork, or embed it directly.
