Episode 3 — NodeJS MongoDB Backend Architecture / 3.3 — Backend Architectures
3.3 — Backend Architectures: Quick Revision
Episode 3 supplement -- print-friendly cheat sheet.
How to use
Skim -> drill weak spots in 3.3.a through 3.3.d -> 3.3-Exercise-Questions.md -> 3.3-Interview-Questions.md.
MVC at a Glance
+------- REQUEST ------+
| |
v |
+-------------+ |
| Router | (URL -> handler mapping)
+------+------+ |
| |
v |
+-------------+ |
| Controller | (extract input, send response)
+------+------+ |
| |
v |
+-------------+ |
| Service | (business logic, orchestration)
+------+------+ |
| |
v |
+-------------+ |
| Model | (schema, validation, DB operations)
+------+------+ |
| |
v |
+-------------+ |
| Database | |
+-------------+ --- RESPONSE-+
MVC Component Responsibilities
| Component | Does | Does NOT |
|---|---|---|
| Model | Schema, validation, DB CRUD, pre/post hooks, business rules tied to data | Access req/res, send HTTP responses, know about routing |
| View | JSON response formatting (REST APIs), HTML rendering (template engines) | Query the database, contain business logic |
| Controller | Extract req.params/req.query/req.body, call service, send response, call next(error) | Contain business logic, query DB directly, send emails |
| Service | Business logic, orchestrate multiple models, call external APIs, apply business rules | Access req/res, know about HTTP status codes |
Architecture Comparison Table
| Monolith | Modular Monolith | SOA | Microservices | Serverless | |
|---|---|---|---|---|---|
| Codebase | Single | Single, feature-organized | Multiple | Multiple | Individual functions |
| Database | Shared | Shared (module boundaries) | Shared or per-service | Per-service | Per-function or managed |
| Deployment | All at once | All at once | Per-service | Per-service | Per-function |
| Communication | Function calls | Function calls via interfaces | ESB / API Gateway | HTTP / gRPC / Message queues | Event triggers |
| Scaling | Entire app | Entire app | Per-service | Per-service | Automatic per-function |
| Complexity | Low | Low-Medium | Medium-High | High | Medium |
| Best team size | 1-15 devs | 5-30 devs | 20-100 devs | 20+ devs (multiple teams) | Any |
| Best for | MVPs, startups | Growing startups | Enterprise integration | Large-scale, independent teams | Event-driven, spiky workloads |
Standard Folder Structure (MVC + Service Layer)
project/
├── server.js # Entry point: app.listen()
├── app.js # Express config: middleware, routes
├── config/
│ └── db.js # Database connection
├── routes/
│ ├── userRoutes.js # router.get/post/put/delete
│ ├── productRoutes.js
│ └── orderRoutes.js
├── controllers/
│ ├── userController.js # Extract req, call service, send res
│ ├── productController.js
│ └── orderController.js
├── services/
│ ├── userService.js # Business logic, orchestration
│ ├── productService.js
│ └── orderService.js
├── models/
│ ├── User.js # Mongoose schema + model
│ ├── Product.js
│ └── Order.js
├── middlewares/
│ ├── auth.js # Authentication
│ ├── errorHandler.js # Global error handler
│ └── validate.js # Input validation
├── utils/
│ ├── AppError.js # Custom error class
│ └── email.js # Email helper
├── .env
└── package.json
Request Flow (Step by Step)
Example: POST /api/users with JSON body { name, email, password }
| Step | Layer | What Happens |
|---|---|---|
| 1 | Middleware | express.json() parses body, morgan logs, auth middleware checks token |
| 2 | Router | router.post('/users', userController.create) matches the route |
| 3 | Controller | Extracts { name, email, password } from req.body, calls userService.create(data) |
| 4 | Service | Checks for duplicate email, hashes password, calls User.create(data), sends welcome email |
| 5 | Model | Validates schema, runs pre-save hooks, inserts document into MongoDB |
| 6 | Service | Returns the created user to the controller |
| 7 | Controller | Sends res.status(201).json({ status: 'success', data: { user } }) |
When-to-Use Decision Matrix
| Question | If Yes | If No |
|---|---|---|
| Fewer than 15 developers? | Monolith or Modular Monolith | Consider SOA / Microservices |
| Need independent scaling per feature? | Microservices or Serverless | Monolith is fine |
| Spiky / infrequent traffic? | Serverless | Dedicated server / container |
| Multiple teams needing autonomy? | Microservices | Modular Monolith |
| Integrating legacy enterprise systems? | SOA with ESB | Direct API calls |
| Building an MVP / prototype? | Monolith | Do not over-architect |
| Need real-time event processing? | Event-Driven Architecture | Request-Response |
| Product boundaries still unclear? | Monolith (extract later) | Microservices if boundaries are well-defined |
Fat Controller vs Thin Controller
// FAT (bad) -- business logic in the controller
exports.register = async (req, res) => {
const { email, password } = req.body;
if (password.length < 8) return res.status(400).json({ error: 'Too short' });
const exists = await User.findOne({ email });
if (exists) return res.status(409).json({ error: 'Taken' });
const hashed = await bcrypt.hash(password, 12);
const user = await User.create({ email, password: hashed });
await sendWelcomeEmail(user.email);
res.status(201).json(user);
};
// THIN (good) -- controller delegates to service
exports.register = async (req, res, next) => {
try {
const user = await userService.register(req.body);
res.status(201).json({ status: 'success', data: { user } });
} catch (error) {
next(error);
}
};
Key Principles (One-Liners)
- Separation of concerns = each layer has one job and delegates everything else.
- Model = data brain. Knows schema, validation, DB. Knows nothing about HTTP.
- Controller = traffic cop. Extracts input, calls service, sends response. No business logic.
- Service = business logic hub. Orchestrates models and external services. No
req/res. - Monolith first = start simple, extract services when you have clear scaling or team signals.
- Distributed monolith = the worst outcome. Microservice complexity + monolith coupling.
- Event-driven = publish events, consumers react independently. Great for decoupling.
- Serverless = no server management, pay per invocation. Watch out for cold starts.
End of 3.3 quick revision.