This in a practical architecture style: how to structure the API, handle high traffic, scale database, use caching, queues, load balancing, Docker/PM2, monitoring, and deployment patterns.
Scalable Node.js API — Complete Guide
A scalable API means:
More users
More requests
More data
Still fast and stable
1. Use proper folder structure
src/
├── config/
│ └── db.js
├── controllers/
│ └── userController.js
├── routes/
│ └── userRoutes.js
├── services/
│ └── userService.js
├── models/
│ └── User.js
├── middlewares/
│ ├── auth.js
│ ├── errorHandler.js
│ └── validate.js
├── utils/
│ └── logger.js
└── server.js
Use this pattern:
Route → Controller → Service → Model
Do not write all logic inside route files.
2. Use environment variables
PORT=5000
MONGO_URI=mongodb://127.0.0.1:27017/myapp
JWT_SECRET=strong_secret_key
REDIS_URL=redis://localhost:6379
NODE_ENV=production
Do not hardcode database URL, password, API keys, or tokens.
3. Use async error handling
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
module.exports = asyncHandler;
Usage:
exports.getUsers = asyncHandler(async (req, res) => {
const users = await User.find();
res.json({
success: true,
data: users
});
});
4. Use global error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(err.statusCode || 500).json({
success: false,
message: err.message || "Server error"
});
});
5. Use database indexing
Without index:
User.find({ email: "test@gmail.com" });
MongoDB may scan many records.
With index:
const userSchema = new mongoose.Schema({
name: String,
email: {
type: String,
index: true,
unique: true
},
city: {
type: String,
index: true
}
});
For job portal:
jobSchema.index({ title: "text", skills: "text", city: "text" });
jobSchema.index({ city: 1, experience: 1 });
jobSchema.index({ slug: 1 });
6. Use pagination
Never return all records.
Bad:
const users = await User.find();
Good:
const page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 10;
const skip = (page - 1) * limit;
const users = await User.find()
.skip(skip)
.limit(limit)
.sort({ createdAt: -1 });
res.json({
page,
limit,
data: users
});
For large data, cursor pagination is better:
const users = await User.find({
_id: { $gt: req.query.lastId }
})
.limit(10)
.sort({ _id: 1 });
7. Use Redis caching
Use Redis for frequently requested data like:
Job categories
City list
Homepage jobs
Popular jobs
User session
API response cache
Redis provides an official Node.js client called node-redis.
Install:
npm install redis
const redis = require("redis");
const client = redis.createClient({
url: process.env.REDIS_URL
});
client.connect();
module.exports = client;
Example cache middleware:
const redisClient = require("../config/redis");
const cache = (keyPrefix, expireTime = 60) => {
return async (req, res, next) => {
const key = `${keyPrefix}:${req.originalUrl}`;
const cachedData = await redisClient.get(key);
if (cachedData) {
return res.json(JSON.parse(cachedData));
}
res.sendResponse = res.json;
res.json = async (body) => {
await redisClient.setEx(key, expireTime, JSON.stringify(body));
res.sendResponse(body);
};
next();
};
};
module.exports = cache;
Usage:
router.get("/jobs", cache("jobs", 60), getJobs);
8. Use clustering / PM2
Node.js runs JavaScript on a single main thread. To use multiple CPU cores, use clustering. Node’s cluster module can run multiple Node.js processes that share the same server port.
PM2 method
npm install pm2 -g
pm2 start server.js -i max
pm2 save
pm2 startup
Meaning:
-i max = use all CPU cores
Check status:
pm2 status
pm2 logs
pm2 monit
9. Use worker threads for CPU-heavy tasks
Use worker threads for:
Resume parsing
PDF processing
Image processing
Large Excel import
AI scoring
Heavy calculations
Node.js docs say worker threads are useful for CPU-intensive JavaScript operations, but not very useful for I/O-heavy work.
Example:
const { Worker } = require("worker_threads");
function runWorker(filePath) {
return new Promise((resolve, reject) => {
const worker = new Worker("./workers/resumeWorker.js", {
workerData: filePath
});
worker.on("message", resolve);
worker.on("error", reject);
});
}
Do not process heavy files directly inside API request.
10. Use queue system for background jobs
For long tasks, use queue:
User uploads resume
↓
API stores file
↓
Add job to queue
↓
Worker processes resume
↓
Save result in database
Use:
npm install bullmq ioredis
Example use cases:
Send emails
Parse resumes
Generate reports
Import Excel
Webhook retry
Candidate matching
Notification sending
11. Use load balancer
When one server is not enough:
Users
↓
Nginx / Load Balancer
↓
Node Server 1
Node Server 2
Node Server 3
↓
MongoDB / Redis
Example Nginx:
upstream node_api {
server 127.0.0.1:5000;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
server {
listen 80;
location / {
proxy_pass http://node_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
12. Use compression
Express production performance guidance recommends gzip compression for response bodies.
npm install compression
const compression = require("compression");
app.use(compression());
13. Avoid blocking code
Bad:
const fs = require("fs");
const data = fs.readFileSync("large-file.txt");
Good:
const fs = require("fs/promises");
const data = await fs.readFile("large-file.txt");
Avoid:
Large loops
Synchronous file read
Heavy PDF parsing in request
Large JSON response
Unindexed database queries
14. Use connection pooling
MongoDB/Mongoose manages pooling, but configure properly:
mongoose.connect(process.env.MONGO_URI, {
maxPoolSize: 20,
serverSelectionTimeoutMS: 5000
});
15. Use API versioning
/api/v1/users
/api/v1/jobs
/api/v2/jobs
Example:
app.use("/api/v1/users", userRoutes);
This helps when you update your API without breaking old apps.
16. Use logging and monitoring
Use logs for:
API errors
Slow requests
Login failures
Payment issues
Database failures
Install:
npm install winston morgan
const morgan = require("morgan");
app.use(morgan("combined"));
In production, monitor:
CPU usage
Memory usage
API response time
Error rate
Database query time
Redis usage
Server disk space
17. Use health check API
app.get("/health", (req, res) => {
res.json({
success: true,
message: "API running",
uptime: process.uptime(),
timestamp: new Date()
});
});
Load balancer can check this route.
18. Use stateless API
Bad scalable design:
Store user session in Node memory
Good scalable design:
JWT token
Redis session
Database session
Because multiple servers should handle the same user.
19. Use CDN for static files
Do not serve images, resumes, PDFs directly from Node for high traffic.
Use:
S3 / Cloud Storage / CDN
Flow:
User uploads resume
↓
Store file in S3
↓
Save file URL in MongoDB
20. Recommended scalable architecture
For your recruitment/job portal:
Frontend
Next.js / React
↓
Nginx / Load Balancer
↓
Node.js API Servers
↓
Redis Cache
↓
MongoDB / MySQL
↓
Queue Workers
↓
File Storage / CDN
Complete scalable server.js example
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const compression = require("compression");
const rateLimit = require("express-rate-limit");
const mongoose = require("mongoose");
require("dotenv").config();
const userRoutes = require("./routes/userRoutes");
const app = express();
app.use(helmet());
app.use(compression());
app.use(cors({
origin: [
"https://cybotrix.com",
"https://admin.cybotrix.com",
"http://localhost:5173"
],
credentials: true
}));
app.use(express.json({ limit: "1mb" }));
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 300
});
app.use("/api", limiter);
app.get("/health", (req, res) => {
res.json({
success: true,
uptime: process.uptime()
});
});
app.use("/api/v1/users", userRoutes);
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({
success: false,
message: "Server error"
});
});
mongoose
.connect(process.env.MONGO_URI, {
maxPoolSize: 20
})
.then(() => {
app.listen(process.env.PORT || 5000, () => {
console.log("Server running");
});
});
Final checklist
Use proper folder structure
Use pagination
Use database indexes
Use Redis caching
Use PM2 cluster mode
Use queue for background jobs
Use worker threads for CPU-heavy tasks
Use load balancer
Use compression
Avoid blocking code
Use API versioning
Use centralized logging
Use health check endpoint
Use CDN for files
Keep API stateless
Scale database separately
For a job portal API, your best first scalable setup is:
Node.js + Express
MongoDB/MySQL with indexes
Redis cache
PM2 cluster
Nginx reverse proxy
Queue for resume parsing
S3/CDN for resume files
Monitoring + logs
