When building a backend on AWS, one of the first decisions is not which framework to use.
It’s where and how the backend should run.
As a Node.js developer, I’ve worked with Express-based APIs and AWS EC2, and one question that keeps coming up is:
Should I run my backend on a traditional server, or move toward a serverless architecture such as API Gateway + Lambda?
There isn’t one answer for every application.
The right choice depends on traffic, workload, architecture, team experience, and operational requirements.
Here’s how I think about the decision.
The requirement
For a typical Node.js backend, I usually need:
- REST APIs
- Authentication and authorization
- MongoDB or another database
- File uploads
- Background processing
- Logging and monitoring
- Reliable deployment
- The ability to handle changing traffic
A simple backend might look like:
Client
↓
Node.js + Express API
↓
MongoDB
Enter fullscreen mode Exit fullscreen mode
When deploying this to AWS, there are several ways to run it.
Options I considered
Option A: EC2 + Node.js + Express
This is the traditional approach.
Client
↓
Nginx
↓
AWS EC2
↓
Node.js + Express
↓
MongoDB
Enter fullscreen mode Exit fullscreen mode
The biggest advantage is control.
I can manage:
- The operating system
- Node.js process
- PM2
- Nginx
- Networking
- Installed dependencies
- Background processes
It’s also a straightforward model for developers who already understand traditional server deployment.
But that control also creates more responsibility.
I need to think about:
- Server maintenance
- Scaling
- Memory usage
- Process management
- Security updates
- Monitoring
- High availability
I’ve seen this firsthand with production Node.js applications: the application itself may be working correctly, but server resources such as memory can become a problem under real traffic.
Option B: API Gateway + Lambda
The serverless approach changes the architecture:
Client
↓
API Gateway
↓
Lambda
↓
Database
Enter fullscreen mode Exit fullscreen mode
Instead of keeping a Node.js server running continuously, individual Lambda functions execute in response to requests.
This can reduce the amount of server infrastructure I need to manage.
It can also make automatic scaling much easier for workloads that aren’t constantly running at high utilization.
But serverless introduces its own considerations.
For example:
- Cold starts
- Execution limits
- Function design
- Database connection management
- Logging
- Local development
- Vendor-specific architecture
So serverless isn’t automatically “better.”
Option C: ECS / Fargate
Another option is containerizing the Node.js application:
Client
↓
Load Balancer
↓
ECS / Fargate
↓
Node.js + Express
↓
MongoDB
Enter fullscreen mode Exit fullscreen mode
This gives me many of the benefits of containers without requiring me to manage the underlying EC2 instances directly.
For larger applications or teams already using Docker, this can be a very attractive middle ground.
The architecture I’d choose
For a small-to-medium Node.js API with predictable traffic, EC2 can still be a practical choice.
A basic architecture could be:
┌──────────────┐
│ Client │
└──────┬───────┘
│
▼
┌──────────────┐
│ Nginx │
└──────┬───────┘
│
▼
┌──────────────────┐
│ AWS EC2 │
│ │
│ Node.js + │
│ Express + PM2 │
└────────┬─────────┘
│
▼
┌──────────────┐
│ MongoDB │
└──────────────┘
Enter fullscreen mode Exit fullscreen mode
This architecture is simple, familiar, and gives me direct control over the application environment.
But I wouldn’t automatically use it for every new project.
If the application has highly variable traffic, independent functions, or a strong serverless requirement, I’d seriously consider API Gateway + Lambda.
The trade-offs
The interesting part isn’t choosing the “most modern” AWS service.
It’s understanding what you’re giving up and what you’re gaining.
Factor EC2 Lambda Server management Higher Lower Scaling Requires configuration Automatic Long-running processes Good Limited Cold starts No Possible Infrastructure control High Lower Deployment model Server/application Function-based Operational complexity Higher Different, not zero Best fit Persistent services Event/API-driven workloadsThis is why I don’t think “serverless means no problems.”
It means you’re moving some operational responsibilities away from servers and taking on a different set of architectural considerations.
A lesson from production
One thing that changed how I think about architecture is realizing that application behavior matters just as much as infrastructure choice.
For example, a Node.js API can run perfectly on a powerful EC2 instance and still have problems if an endpoint loads thousands of MongoDB documents into memory.
Likewise, moving that endpoint to Lambda wouldn’t automatically fix inefficient data processing.
The architecture can help, but good application design still matters.
What I’d change next time
If I were starting a new AWS backend today, I’d make the architecture decision based on the workload instead of choosing a service simply because it’s popular.
I’d ask:
- How predictable is the traffic?
- Does the application need long-running processes?
- How much infrastructure do we want to manage?
- What are the database access patterns?
- How important is automatic scaling?
- What are the latency requirements?
- Does the team already have experience with containers or serverless?
Only after answering those questions would I choose between EC2, ECS/Fargate, and Lambda.
Final takeaway
There is no single AWS architecture that is perfect for every Node.js application.
EC2, ECS/Fargate, and Lambda solve different problems.
For me, the important lesson is to start with the application’s requirements and workload, then choose the AWS service that fits.
The goal isn’t to use the most services.
The goal is to build a system that is reliable, maintainable, scalable, and appropriate for the actual workload.
And that’s one of the things I’m continuing to learn as I work deeper with AWS and backend engineering.