Node.js Advanced: Security, Performance and Production
Architecture and Performance
In this lesson you will
- Structure a service into routes, services and data layers
- Measure performance before optimising and use caching sensibly
- Describe career paths for Node.js developers
Introduction
A small API can live in one file. A real service grows, and without structure it becomes hard to change. This final lesson covers how to organise code, handle errors consistently and make a service fast, using evidence instead of guesses.
Layers
A common structure separates three jobs:
- Routes (controllers): handle HTTP: read the request, call a service, send the response.
- Services: contain business rules, with no knowledge of HTTP.
- Data access: talk to the database or other systems.
src/
app.js creates the Express app
server.js calls listen
config.js reads and validates configuration
routes/todos.js HTTP only
services/todos.js business logic
db/todos.js SQL
middleware/ auth, errors, logging
test/Benefits: services can be tested without a web server, the database can change without touching routes, and each file has a clear job. Keep it as simple as the project needs. A tiny service does not need ten folders.
Quick quiz
Why keep business rules in a service layer instead of inside route handlers?
Consistent errors
Create a small set of error types, such as NotFoundError and ValidationError, that services throw. One error-handling middleware converts them to responses with the right status codes and a consistent JSON shape. Unknown errors become a generic 500 and are logged with detail. This avoids scattered res.status(...) calls in services.
Make it fast: measure first
Do not optimise by guessing.
- Define the goal, such as "95 percent of requests under 200 ms at 100 requests a second".
- Measure, with a load testing tool against a realistic environment.
- Find the bottleneck: slow queries, blocking code, too many network calls or memory pressure.
- Fix one thing, and measure again.
Useful tools:
- Node's built-in profiler and the Chrome DevTools inspector (
node --inspect) to see where CPU time goes - Logging of timings for requests and database queries
- Database query plans to find missing indexes
- Monitoring of memory, CPU and event loop delay in production
Common wins
- Add missing database indexes and avoid fetching more rows or columns than needed.
- Avoid doing work per request that can be done once, such as preparing statements.
- Cache expensive results in memory or a cache server, with sensible expiry and a plan for invalidation.
- Use compression and caching headers for responses, usually via a reverse proxy.
- Batch and parallelise independent calls with
Promise.all. - Set timeouts on outgoing requests.
- Paginate long lists.
- Move heavy work to workers or queues, as in the previous lesson.
- Serve static files from a CDN or proxy, not from Node.
Watch out for memory leaks, such as caches that grow forever, listeners never removed and global arrays that keep growing.
Dependencies and maintenance
- Keep few dependencies, and update regularly.
- Pin versions with the lock file, and read changelogs for major updates.
- Follow Node's release schedule: run an active LTS version, and plan upgrades before support ends.
- Write documentation: how to run, test and deploy, and the main design decisions.
Careers
- Back-end developer: APIs, databases and services, often with Node and TypeScript
- Full-stack developer: front end and back end together
- Platform and DevOps engineer: deployment, containers and reliability
- Site reliability engineer: monitoring, performance and incident response
- Tooling and developer experience engineer: build tools and internal platforms
Build a portfolio with a real project: an API with authentication, tests, a database, documentation and a deployed demo, plus a short write-up of your decisions. Keep learning TypeScript, databases, cloud basics and security, which you can find elsewhere on this site.
Try it yourself
Take your to-do API and restructure it into routes, services and data layers with a central error handler. Then measure the time of one endpoint under a simple load test, make one improvement and compare.
Summary
- Organise code into routes, services and data access, keeping it as simple as the project needs.
- Use consistent error types and a central handler.
- Measure before optimising, fix the real bottleneck, cache carefully, and keep dependencies and Node versions maintained.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading