Episode 3 — NodeJS MongoDB Backend Architecture / 3.3 — Backend Architectures
3.3.a — Introduction to Backend Architecture
Software architecture is the blueprint of your application. It defines how code is organized, how components communicate, and how the system evolves over time. Bad architecture turns small changes into week-long nightmares; good architecture makes even complex features feel manageable.
Home | Next: 3.3.b — MVC Architecture
1. What Is Software Architecture?
Software architecture is the high-level structure of a software system. It defines:
- How code is organized into modules, layers, or services
- How those parts communicate with each other
- What rules govern data flow through the system
- Where boundaries exist between different responsibilities
Think of it like a building's blueprint. You can build a house without a plan, but the plumbing will end up in the wrong place, the walls won't be load-bearing where they need to be, and adding a second floor later will require tearing everything down.
Architecture vs. Design
| Aspect | Architecture | Design |
|---|---|---|
| Scope | System-wide decisions | Component-level decisions |
| Changes | Expensive to change later | Easier to refactor |
| Focus | Structure, communication, constraints | Algorithms, data structures, interfaces |
| Who decides | Tech leads, architects | Individual developers |
| Example | "We use microservices with an event bus" | "This function uses a binary search" |
2. Why Architecture Matters
2.1 Scalability
Architecture determines how your app handles growth.
BAD ARCHITECTURE (everything tangled)
+------------------------------------------+
| route handler: |
| - validates input |
| - queries database directly |
| - formats response |
| - sends emails |
| - logs analytics |
| - handles errors |
| (one function doing EVERYTHING) |
+------------------------------------------+
Cannot scale individual pieces.
Cannot add caching without rewriting.
One bug can break everything.
GOOD ARCHITECTURE (separated concerns)
+----------+ +------------+ +---------+ +----------+
| Routes |->| Controller |->| Service |->| Model |
+----------+ +------------+ +---------+ +----------+
|
+-----+------+
| Email | Analytics |
| Service | Service |
+------------+------------+
Each piece scales independently.
Add caching at the service layer only.
Bug in email doesn't break user creation.
2.2 Maintainability
When a bug appears in a well-architected system, you know exactly where to look:
- Data coming back wrong? Check the Model.
- Wrong HTTP status code? Check the Controller.
- Business rule violated? Check the Service layer.
2.3 Team Collaboration
Good architecture lets multiple developers work without stepping on each other's code.
| Team Member | Works On | Doesn't Touch |
|---|---|---|
| Frontend developer | Routes, Controllers (API contract) | Model internals |
| Backend developer | Services, Models | Route definitions |
| Database engineer | Models, Schemas | Controllers |
| DevOps engineer | Deployment, Config | Business logic |
2.4 Testability
Separated architecture means you can test each layer in isolation:
// Testing a service WITHOUT needing Express or a real database
const userService = require('../services/userService');
// Mock the model
jest.mock('../models/User');
const User = require('../models/User');
test('createUser hashes password before saving', async () => {
User.create.mockResolvedValue({ id: 1, name: 'Alice' });
const result = await userService.createUser({
name: 'Alice',
password: 'secret123'
});
// Verify the password was hashed (not stored raw)
const callArgs = User.create.mock.calls[0][0];
expect(callArgs.password).not.toBe('secret123');
expect(callArgs.password).toMatch(/^\$2[aby]\$/); // bcrypt hash pattern
});
3. Monolithic vs. Microservices (Overview)
3.1 Monolithic Architecture
Everything lives in one codebase, deployed as one unit.
+--------------------------------------------------+
| MONOLITH |
| |
| +--------+ +---------+ +--------+ +-------+ |
| | Auth | | Users | | Orders | | Email | |
| +--------+ +---------+ +--------+ +-------+ |
| |
| +--------------------------------------------+ |
| | Shared Database | |
| +--------------------------------------------+ |
+--------------------------------------------------+
Deployed as ONE application
Advantages:
- Simple to develop, test, and deploy initially
- No network latency between components
- Easy to debug (one process, one log stream)
- Single database means simple transactions
Disadvantages:
- Entire app redeploys for any change
- One module's crash can bring down everything
- Hard to scale individual components
- Codebase becomes unwieldy over time
3.2 Microservices Architecture
Each feature is an independent service with its own database.
+--------+ +----------+ +---------+
| Auth | | Users | | Orders |
| Service| | Service | | Service |
+---+----+ +----+-----+ +----+----+
| | |
+---+----+ +----+-----+ +----+----+
| Auth DB| | Users DB | |Orders DB|
+--------+ +----------+ +---------+
| | |
+---+---------------+----------------+---+
| API Gateway |
+----------------------------------------+
Advantages:
- Deploy services independently
- Scale bottleneck services only
- Different teams own different services
- Technology flexibility per service
Disadvantages:
- Network complexity (services calling services)
- Distributed transactions are hard
- Deployment infrastructure is complex
- Debugging across services is difficult
3.3 Quick Comparison
| Factor | Monolith | Microservices |
|---|---|---|
| Deployment | All at once | Per service |
| Scaling | Entire app | Per service |
| Database | Shared | Per service |
| Team coupling | High | Low |
| Initial complexity | Low | High |
| Best starting point | Almost always YES | Rarely for new projects |
4. Choosing Architecture Based on Project Size and Team
Decision Framework
START HERE
|
v
Is your team < 5 people?
| |
YES NO
| |
v v
Monolith Do you have DevOps expertise?
(MVC) | |
YES NO
| |
v v
Microservices Monolith (MVC)
(if needed) (evolve later)
Practical Recommendations
| Project Type | Recommended Architecture | Why |
|---|---|---|
| Personal project / MVP | Monolith with MVC | Ship fast, iterate quickly |
| Startup (seed stage) | Monolith with clean layers | Maintain speed while keeping code organized |
| Growing company (20+ devs) | Modular monolith or SOA | Teams need boundaries without full microservices overhead |
| Enterprise (100+ devs) | Microservices | Teams need independence, different scaling needs |
| Event-heavy system (IoT, real-time) | Event-driven | Decouples producers from consumers |
| Sporadic workloads (cron jobs, webhooks) | Serverless | Pay only for what you use |
5. Common Architectural Patterns
5.1 Layered Architecture
Code is organized into horizontal layers, each with a specific responsibility.
+---------------------------+
| Presentation Layer | (Routes, Controllers)
+---------------------------+
| Business Logic Layer | (Services, Use Cases)
+---------------------------+
| Data Access Layer | (Models, Repositories)
+---------------------------+
| Database Layer | (MongoDB, PostgreSQL)
+---------------------------+
Rule: Each layer only talks to the layer directly below it.
5.2 Event-Driven Architecture
Components communicate through events, not direct calls.
// Event emitter pattern in Node.js
const EventEmitter = require('events');
const appEvents = new EventEmitter();
// Publisher: User service emits an event
async function createUser(data) {
const user = await User.create(data);
appEvents.emit('user:created', user); // fire and forget
return user;
}
// Subscriber: Email service listens for the event
appEvents.on('user:created', async (user) => {
await sendWelcomeEmail(user.email);
});
// Subscriber: Analytics service also listens
appEvents.on('user:created', async (user) => {
await trackSignup(user.id);
});
5.3 Microservices Architecture
Independent services communicating over HTTP or message queues.
// User service (runs on port 3001)
app.post('/api/users', async (req, res) => {
const user = await UserModel.create(req.body);
// Call order service via HTTP (inter-service communication)
await axios.post('http://order-service:3002/api/carts', {
userId: user._id
});
res.status(201).json(user);
});
5.4 Serverless Architecture
Individual functions deployed to cloud platforms, invoked on demand.
// AWS Lambda function (no Express, no server)
exports.handler = async (event) => {
const { name, email } = JSON.parse(event.body);
const user = await db.collection('users').insertOne({ name, email });
return {
statusCode: 201,
body: JSON.stringify({ id: user.insertedId, name, email })
};
};
6. Architecture Documentation and Diagrams
Good architecture must be documented. Undocumented architecture is just tribal knowledge that disappears when people leave.
What to Document
| Document | Purpose | Audience |
|---|---|---|
| System context diagram | Shows how the system interacts with external systems | Everyone |
| Component diagram | Shows major components and their relationships | Developers |
| Data flow diagram | Shows how data moves through the system | Developers, QA |
| API contracts | Defines request/response formats | Frontend + Backend |
| Decision records (ADRs) | Explains WHY a decision was made | Future developers |
Architecture Decision Record (ADR) Template
# ADR-001: Use MVC with Service Layer
## Status
Accepted
## Context
We are building a REST API for an e-commerce platform.
The team has 4 developers, all familiar with Express.js.
## Decision
We will use MVC architecture with an added Service layer:
Routes -> Controllers -> Services -> Models
## Consequences
- Positive: Clear separation of concerns, easy to test
- Positive: New team members understand the structure quickly
- Negative: More files and folders than a flat structure
- Negative: Simple CRUD operations require touching multiple files
ASCII Diagram Example (System Context)
+------------------+
| Mobile App |
+--------+---------+
|
+--------v---------+
+----------+ | | +-------------+
| Payment | <--> | Our Backend | <--> | Email (SMTP) |
| Gateway | | (Express API) | | Service |
+----------+ | | +-------------+
+--------+---------+
|
+--------v---------+
| MongoDB |
+------------------+
7. The Architecture Spectrum
Most real-world systems are not purely one pattern. They sit on a spectrum:
Simple ◄──────────────────────────────────────────► Complex
Monolith Modular SOA Microservices Nano-
(all in one) Monolith (services) (independent) services
(bounded (serverless
modules) functions)
Fewer moving parts ◄─────────────────────► More moving parts
Easier to start ◄─────────────────────► Harder to start
Harder to scale ◄─────────────────────► Easier to scale
The golden rule: Start with the simplest architecture that meets your needs. Evolve when the pain of the current architecture outweighs the cost of migration.
Key Takeaways
- Architecture is about trade-offs, not about picking the "best" pattern. Every choice has costs.
- Monoliths are not bad. They are the right starting point for most projects. The goal is a well-structured monolith.
- Separation of concerns is the single most important architectural principle. Every pattern enforces it differently.
- Architecture affects everything: how fast you ship, how many bugs you create, how easily new developers onboard, and how well the system scales.
- Document your decisions. Future developers (including future you) will thank you.
- Start simple, evolve intentionally. Premature complexity is as dangerous as no architecture at all.
Explain-It Challenge
Scenario: Your friend is starting a new project, a food delivery app. They have a 3-person team and want to build with microservices because "that's what Netflix uses."
Explain to them, in plain language:
- Why starting with microservices is likely a mistake for their team size
- What architecture you would recommend instead and why
- At what point it might make sense to start extracting microservices
- Which part of their system might be the first candidate for extraction and why