⚡ Advanced Express.js Interview Questions
This lesson targets mid-to-senior backend roles. Topics include JWT authentication, input validation, rate limiting, file uploads, WebSockets, request compression, session management, advanced error patterns, testing, and API versioning. These questions separate developers who build Express APIs from those who architect them.
Questions & Answers
01 How do you implement JWT authentication in Express? ►
Auth JSON Web Tokens (JWT) are the standard for stateless authentication in REST APIs. A signed token is issued on login and sent with every subsequent request in the Authorization header.
npm install jsonwebtoken bcryptjs
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
// โโ Login route โ issue a token โโ
app.post('/auth/login', async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.passwordHash)))
return res.status(401).json({ error: 'Invalid credentials' });
const token = jwt.sign(
{ sub: user._id, role: user.role }, // payload
process.env.JWT_SECRET, // secret
{ expiresIn: '15m' } // short-lived access token
);
const refreshToken = jwt.sign({ sub: user._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' });
res.json({ token, refreshToken });
} catch (err) { next(err); }
});
// โโ Auth middleware โ verify token โโ
function authenticate(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer '))
return res.status(401).json({ error: 'No token provided' });
try {
const payload = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
req.user = payload;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid or expired token' });
}
}
// โโ Protected route โโ
app.get('/profile', authenticate, (req, res) => {
res.json({ userId: req.user.sub, role: req.user.role });
});
02 How do you validate request data in Express? ►
Validation Never trust user input. Validate and sanitise all incoming data before processing it. The most popular libraries are express-validator and Joi / Zod.
npm install express-validator
const { body, param, query, validationResult } = require('express-validator');
// Validation rules as middleware array
const createUserRules = [
body('name').trim().notEmpty().withMessage('Name is required').isLength({ max: 50 }),
body('email').isEmail().normalizeEmail().withMessage('Valid email required'),
body('age').optional().isInt({ min: 0, max: 120 }).withMessage('Age must be 0-120'),
body('password').isLength({ min: 8 }).withMessage('Password min 8 characters'),
];
// Validation result handler โ reuse across routes
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty())
return res.status(422).json({ errors: errors.array() });
next();
};
// Apply to route
app.post('/users', createUserRules, validate, async (req, res, next) => {
// req.body is guaranteed valid here
const user = await User.create(req.body);
res.status(201).json(user);
});
// Response on validation failure:
// { errors: [{ field: "email", msg: "Valid email required", value: "bad-email" }] }
03 What is rate limiting and how do you implement it in Express? ►
Security Rate limiting restricts how many requests a client can make in a time window โ protecting your API from brute-force attacks, DoS attacks, and API abuse.
npm install express-rate-limit
const rateLimit = require('express-rate-limit');
// General API rate limit
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15-minute window
max: 100, // max 100 requests per window per IP
standardHeaders: true, // include RateLimit-* headers
legacyHeaders: false,
message: { error: 'Too many requests. Please try again later.' },
});
app.use('/api', apiLimiter);
// Stricter limit for auth endpoints (prevent brute force)
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { error: 'Too many login attempts. Wait 15 minutes.' },
skipSuccessfulRequests: true, // don't count successful logins
});
app.use('/auth/login', authLimiter);
app.use('/auth/register', authLimiter);
// For distributed systems (multiple servers), use a Redis store:
// npm install rate-limit-redis ioredis
const RedisStore = require('rate-limit-redis');
const limiter = rateLimit({ store: new RedisStore({ client: redisClient }), max: 100, windowMs: 60000 });
04 How do you handle file uploads in Express using Multer? ►
Files Multer is a Node.js middleware for handling multipart/form-data โ the encoding used for file uploads. It parses the uploaded file and makes it available on req.file (single) or req.files (multiple).
npm install multer
const multer = require('multer');
const path = require('path');
// Storage configuration
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => {
const unique = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, unique + path.extname(file.originalname));
}
});
// File filter โ allow only images
const fileFilter = (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
if (allowed.includes(file.mimetype)) cb(null, true);
else cb(new Error('Only JPEG, PNG and WebP images allowed'), false);
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 } // 5MB max
});
// Single file upload
app.post('/upload/avatar', upload.single('avatar'), (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
res.json({ url: `/uploads/${req.file.filename}` });
});
// Multiple files
app.post('/upload/gallery', upload.array('photos', 10), (req, res) => {
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.json({ urls });
});
05 How do you implement session-based authentication in Express? ►
Auth Session-based auth stores the session on the server and gives the client a session ID cookie. Suitable for traditional web apps where the server renders HTML.
npm install express-session connect-mongo
const session = require('express-session');
const MongoStore = require('connect-mongo');
app.use(session({
secret: process.env.SESSION_SECRET, // sign the session ID cookie
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI }),
cookie: {
httpOnly: true, // JS can't read the cookie (XSS protection)
secure: process.env.NODE_ENV === 'production', // HTTPS only in prod
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
}
}));
// Login โ store user in session
app.post('/login', async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (user && await bcrypt.compare(req.body.password, user.passwordHash)) {
req.session.userId = user._id;
req.session.role = user.role;
res.json({ message: 'Logged in' });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
// Auth middleware for session
function requireAuth(req, res, next) {
if (!req.session?.userId) return res.status(401).json({ error: 'Please log in' });
next();
}
// Logout โ destroy session
app.post('/logout', (req, res) => {
req.session.destroy(() => res.clearCookie('connect.sid').json({ message: 'Logged out' }));
});
06 How do you implement request compression in Express? ►
Performance The compression middleware compresses HTTP responses using gzip or deflate. This can reduce response sizes by 70-80% for JSON and HTML โ significantly improving performance over slow connections.
npm install compression
const compression = require('compression');
// Enable compression for all responses
app.use(compression());
// With options
app.use(compression({
level: 6, // 0-9, higher = better compression but more CPU (default: -1 = zlib default)
threshold: 1024, // only compress responses larger than 1KB (default: 1KB)
filter: (req, res) => {
// Don't compress if client doesn't accept it, or it's a media stream
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res); // default filter
}
}));
// How it works:
// 1. Client sends: Accept-Encoding: gzip, deflate, br
// 2. compression checks the header
// 3. If supported, it pipes the response through a gzip stream
// 4. Sets Content-Encoding: gzip on the response
// 5. Client decompresses automatically
// Don't compress already-compressed content (images, video, .zip files)
// compression.filter handles this โ checks Content-Type header
In production with a reverse proxy (Nginx, Cloudflare), offload compression to the proxy layer and disable it in Express โ proxies handle it more efficiently.
07 What is API versioning in Express? What are the approaches? ►
Architecture API versioning allows breaking changes to be released without disrupting existing clients. There are four common approaches:
1. URL path versioning (most common):
app.use('/api/v1', require('./routes/v1'));
app.use('/api/v2', require('./routes/v2'));
// Clients call: GET /api/v1/users or GET /api/v2/users
2. Query string versioning:
app.get('/api/users', (req, res) => {
const version = req.query.version || 'v1';
if (version === 'v2') return res.json(getUsersV2());
res.json(getUsersV1());
});
// Clients call: GET /api/users?version=v2
3. Header versioning:
app.get('/api/users', (req, res) => {
const version = req.headers['accept-version'] || '1.0';
version.startsWith('2') ? res.json(getUsersV2()) : res.json(getUsersV1());
});
// Clients send: Accept-Version: 2.0
4. Accept header / content negotiation:
// Clients send: Accept: application/vnd.myapi.v2+json
URL path versioning is the most visible, cacheable, and easy to test in a browser. It is the recommended approach for most public APIs.
08 How do you use WebSockets (Socket.IO) alongside an Express server? ►
Realtime Express handles HTTP requests but doesn’t natively support WebSockets. Socket.IO sits on top of the same Node HTTP server and adds real-time bidirectional communication.
npm install socket.io
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app); // shared HTTP server
const io = new Server(server, {
cors: { origin: 'http://localhost:3000', methods: ['GET', 'POST'] }
});
// Express routes still work normally
app.get('/api/messages', (req, res) => res.json(messages));
// Socket.IO event handlers
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
socket.on('joinRoom', (roomId) => {
socket.join(roomId);
socket.to(roomId).emit('userJoined', { userId: socket.id });
});
socket.on('sendMessage', ({ roomId, text }) => {
const message = { id: Date.now(), text, sender: socket.id };
io.to(roomId).emit('newMessage', message); // broadcast to room
});
socket.on('disconnect', () => console.log('Client disconnected:', socket.id));
});
server.listen(3000, () => console.log('Server on port 3000'));
09 How do you write unit tests for Express routes? ►
Testing The standard approach for testing Express APIs is Supertest with a test runner (Jest or Mocha). Supertest allows HTTP calls without starting a real server.
npm install --save-dev jest supertest
// app.js โ export the app WITHOUT calling app.listen()
const express = require('express');
const app = express();
app.use(express.json());
app.use('/api/users', require('./routes/users'));
module.exports = app; // tests import this
// server.js โ starts the server (not imported in tests)
const app = require('./app');
app.listen(3000, () => console.log('Running'));
// tests/users.test.js
const request = require('supertest');
const app = require('../app');
describe('GET /api/users', () => {
it('returns a list of users with status 200', async () => {
const res = await request(app).get('/api/users');
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
});
describe('POST /api/users', () => {
it('creates a user and returns 201', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@test.com' });
expect(res.status).toBe(201);
expect(res.body.name).toBe('Alice');
});
it('returns 422 when name is missing', async () => {
const res = await request(app).post('/api/users').send({ email: 'test@test.com' });
expect(res.status).toBe(422);
});
});
10 What is the difference between cookies and localStorage for storing tokens? ►
Auth
- HttpOnly Cookie โ cannot be read by JavaScript (XSS-safe). Automatically sent with every request. Vulnerable to CSRF attacks (mitigate with
SameSite=Strict/Laxand CSRF tokens). Set by server withres.cookie(). - localStorage โ accessible by JavaScript. Vulnerable to XSS (a malicious script can steal the token). Not sent automatically โ must be added to requests manually in the Authorization header. Persists across tabs and sessions.
// Recommended: HttpOnly cookie for refresh token, short-lived JWT in memory
res.cookie('refreshToken', token, {
httpOnly: true,
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 7 * 24 * 3600 * 1000 // 7 days
});
// Short-lived access token in response body โ stored in memory (variable)
// Never in localStorage for sensitive applications
res.json({ accessToken: jwt.sign({ sub: user._id }, secret, { expiresIn: '15m' }) });
Best practice: Store access tokens in memory (a JavaScript variable). Store refresh tokens in HttpOnly cookies. This approach is XSS-resistant (access token) and CSRF-resistant (short-lived, bearer-based access token).
11 What is dependency injection in Express and how do you implement it? ►
Architecture Dependency Injection (DI) passes dependencies (database connections, services) into functions rather than importing them directly โ enabling easier testing (mock the dependency) and cleaner separation of concerns.
// Without DI โ service imports db directly (hard to test)
const db = require('../db');
const userService = { getAll: () => db.collection('users').find().toArray() };
// With DI โ factory function receives dependencies
function createUserService(db) {
return {
getAll: () => db.collection('users').find().toArray(),
getById: (id) => db.collection('users').findOne({ _id: id }),
create: (data) => db.collection('users').insertOne(data),
};
}
// Factory for controller โ receives the service
function createUserController(userService) {
return {
getAll: async (req, res, next) => {
try { res.json(await userService.getAll()); }
catch (err) { next(err); }
}
};
}
// app.js โ wire dependencies
const db = await connectToDatabase();
const userService = createUserService(db);
const userController = createUserController(userService);
app.get('/users', userController.getAll);
// Test โ inject a mock service
const mockService = { getAll: jest.fn().mockResolvedValue([{ name: 'Alice' }]) };
const controller = createUserController(mockService);
12 What is caching in Express and how do you implement HTTP cache headers? ►
Performance HTTP caching reduces server load and improves response times by instructing clients and CDNs to reuse cached responses.
// Cache-Control header โ tell clients how long to cache
app.get('/api/products', (req, res) => {
res.set('Cache-Control', 'public, max-age=300'); // cache for 5 minutes
res.json(products);
});
// No cache โ for authenticated/private data
app.get('/api/profile', authenticate, (req, res) => {
res.set('Cache-Control', 'no-store');
res.json(req.user);
});
// ETag โ conditional requests (304 Not Modified if unchanged)
const etag = require('etag');
app.get('/api/articles/:id', async (req, res) => {
const article = await Article.findById(req.params.id);
const tag = etag(JSON.stringify(article));
if (req.headers['if-none-match'] === tag)
return res.status(304).end(); // browser uses cached version
res.set('ETag', tag).json(article);
});
// Server-side cache with Redis
const redis = require('ioredis');
const cache = new redis();
app.get('/api/stats', async (req, res, next) => {
try {
const cached = await cache.get('stats');
if (cached) return res.json(JSON.parse(cached));
const stats = await computeExpensiveStats();
await cache.setex('stats', 60, JSON.stringify(stats)); // cache 60s
res.json(stats);
} catch (err) { next(err); }
});
13 How do you implement role-based access control (RBAC) in Express? ►
Auth RBAC restricts access based on the user’s assigned role. It is typically implemented as middleware that runs after authentication.
// authorise middleware factory โ takes allowed roles
const authorise = (...roles) => (req, res, next) => {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
if (!roles.includes(req.user.role))
return res.status(403).json({ error: 'Insufficient permissions' });
next();
};
// Routes โ stacked middleware: authenticate first, then authorise
app.get('/admin/users',
authenticate,
authorise('admin'), // only admins
getAllUsers
);
app.delete('/admin/users/:id',
authenticate,
authorise('admin', 'superadmin'), // admins and superadmins
deleteUser
);
app.get('/reports',
authenticate,
authorise('admin', 'manager', 'analyst'),
getReports
);
// Permission-based (more granular than role-based)
const can = (permission) => (req, res, next) => {
const permissions = rolePermissions[req.user.role] || [];
if (!permissions.includes(permission))
return res.status(403).json({ error: `Requires '${permission}' permission` });
next();
};
app.delete('/posts/:id', authenticate, can('posts:delete'), deletePost);
14 What is Passport.js and how does it work with Express? ►
Auth Passport.js is an authentication middleware for Express with 500+ strategies: Local (username/password), JWT, OAuth (Google, GitHub, Facebook), SAML, and more. It abstracts the authentication logic.
npm install passport passport-local passport-jwt
const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
// Local strategy โ username/password
passport.use(new LocalStrategy({ usernameField: 'email' },
async (email, password, done) => {
try {
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.passwordHash)))
return done(null, false, { message: 'Invalid credentials' });
return done(null, user);
} catch (err) { done(err); }
}
));
// JWT strategy โ Bearer token
passport.use(new JwtStrategy(
{ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET },
async (payload, done) => {
try {
const user = await User.findById(payload.sub);
return user ? done(null, user) : done(null, false);
} catch (err) { done(err); }
}
));
app.use(passport.initialize());
// Login route using Local strategy
app.post('/login', passport.authenticate('local', { session: false }),
(req, res) => {
const token = jwt.sign({ sub: req.user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
}
);
// Protected route using JWT strategy
app.get('/profile',
passport.authenticate('jwt', { session: false }),
(req, res) => res.json(req.user)
);
15 How do you implement pagination in an Express REST API? ►
API Design Pagination prevents large datasets from being sent in one response. Two main approaches: offset-based and cursor-based.
Offset-based pagination:
// GET /api/users?page=2&limit=20
app.get('/api/users', async (req, res, next) => {
try {
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, parseInt(req.query.limit) || 20); // cap at 100
const skip = (page - 1) * limit;
const [data, total] = await Promise.all([
User.find().skip(skip).limit(limit).lean(),
User.countDocuments()
]);
res.json({
data,
pagination: {
page, limit, total,
totalPages: Math.ceil(total / limit),
hasNextPage: page * limit < total,
hasPrevPage: page > 1
}
});
} catch (err) { next(err); }
});
Cursor-based pagination (better for large datasets):
// GET /api/users?after=64b2f1a3c9d4e8a1&limit=20
app.get('/api/users', async (req, res, next) => {
try {
const limit = Math.min(100, parseInt(req.query.limit) || 20);
const cursor = req.query.after;
const filter = cursor ? { _id: { $gt: cursor } } : {};
const data = await User.find(filter).sort({ _id: 1 }).limit(limit + 1).lean();
const hasNext = data.length > limit;
if (hasNext) data.pop();
res.json({ data, nextCursor: hasNext ? data[data.length - 1]._id : null });
} catch (err) { next(err); }
});
16 What are Express best practices for production security? ►
Security
- Use Helmet โ sets 14+ security headers (CSP, HSTS, X-Frame-Options) in one line
- Rate limiting โ
express-rate-limiton all endpoints, stricter on auth routes - Input validation โ validate and sanitise all user inputs with express-validator or Joi
- Parameterised queries โ never build queries from string concatenation; use ORMs or parameterised DB drivers
- HTTPS only โ redirect HTTP to HTTPS; set
Strict-Transport-Securityheader - Disable X-Powered-By โ
app.disable('x-powered-by')hides Express from attackers - Body size limits โ
express.json({ limit: '10kb' })prevents large payload DoS - Dependency scanning โ run
npm auditregularly; use Snyk or Dependabot - Secrets management โ use environment variables, never hardcode secrets in source code
- Run as non-root โ don’t run Node.js as the root system user in production containers
// Quick security setup
app.disable('x-powered-by');
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGIN }));
app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }));
app.use(express.json({ limit: '10kb' }));
17 What is request proxying and how do you build a simple API gateway? ►
Architecture An API gateway is a single entry point that routes requests to downstream microservices. Express can act as an API gateway using http-proxy-middleware.
npm install http-proxy-middleware
const { createProxyMiddleware } = require('http-proxy-middleware');
// API Gateway โ routes to downstream services
app.use('/api/users', createProxyMiddleware({ target: 'http://users-service:3001', changeOrigin: true }));
app.use('/api/orders', createProxyMiddleware({ target: 'http://orders-service:3002', changeOrigin: true }));
app.use('/api/products', createProxyMiddleware({ target: 'http://products-service:3003', changeOrigin: true }));
// With path rewriting โ strip the /api prefix before forwarding
app.use('/api/users', createProxyMiddleware({
target: 'http://users-service:3001',
changeOrigin: true,
pathRewrite: { '^/api/users': '' } // /api/users/1 โ /1
}));
// Middleware in the gateway (auth, logging, rate limiting) runs before proxying
app.use('/api', authenticate); // authenticate all API routes
app.use('/api', apiRateLimiter); // rate limit all API routes
app.use('/api/admin', adminOnly); // extra check for admin routes
18 How do you handle graceful shutdown in an Express application? ►
Ops Graceful shutdown ensures that when the process receives a shutdown signal (SIGTERM from Kubernetes, PM2, or deployments), in-flight requests complete before the server closes โ preventing data loss or broken responses.
const server = app.listen(PORT, () => console.log(`Server on port ${PORT}`));
// Track open connections for forced cleanup
let connections = new Set();
server.on('connection', conn => {
connections.add(conn);
conn.on('close', () => connections.delete(conn));
});
// Graceful shutdown handler
async function gracefulShutdown(signal) {
console.log(`Received ${signal}. Starting graceful shutdown...`);
// Stop accepting new connections
server.close(async () => {
console.log('HTTP server closed. No new requests accepted.');
try {
await mongoose.connection.close(); // close DB connections
await redisClient.quit(); // close Redis connections
console.log('All connections closed. Exiting.');
process.exit(0);
} catch (err) {
console.error('Error during shutdown:', err);
process.exit(1);
}
});
// Force close after 30 seconds if pending requests don't complete
setTimeout(() => {
console.error('Forcing shutdown after timeout');
connections.forEach(conn => conn.destroy());
process.exit(1);
}, 30000);
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Docker/K8s
process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Ctrl+C
19 What is the difference between app-level and route-level middleware? ►
Middleware
- Application-level middleware โ bound to the
appinstance. Runs for all requests (or requests matching a path prefix). Used for cross-cutting concerns: logging, body parsing, CORS, compression, security headers. - Route-level middleware โ applied to specific routes or routers only. Enables selective authentication, validation, and authorisation per route.
// App-level โ runs for ALL routes
app.use(morgan('dev'));
app.use(express.json());
app.use(helmet());
// Router-level โ runs only for /api routes
const apiRouter = express.Router();
apiRouter.use(authenticate); // all /api routes require auth
apiRouter.use(apiRateLimiter);
app.use('/api', apiRouter);
// Route-level inline โ single route only
app.get('/admin/stats',
authenticate, // must be logged in
authorise('admin'), // must be admin
validate([query('from').isISO8601()]), // validate query params
getStats // the actual handler
);
// Multiple handlers on one route (middleware array)
app.post('/orders',
authenticate,
[
body('productId').isMongoId(),
body('quantity').isInt({ min: 1 })
],
validate,
createOrder
);
20 How do you implement request logging and monitoring in Express? ►
Observability Production systems need structured logs, request tracing, and performance monitoring to diagnose issues quickly.
npm install pino pino-http
const pino = require('pino');
const pinoHttp = require('pino-http');
// Structured JSON logger (better than console.log in production)
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' } : undefined // pretty-print in dev only
});
// HTTP request logging โ adds req/res fields automatically
app.use(pinoHttp({ logger }));
// Request ID โ trace a request across logs and services
const { v4: uuid } = require('uuid');
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || uuid();
res.set('X-Request-Id', req.id);
next();
});
// Custom structured logging in a route
app.get('/api/orders', authenticate, async (req, res, next) => {
req.log.info({ userId: req.user.id, action: 'list_orders' }, 'Fetching orders');
try {
const orders = await Order.find({ userId: req.user.id });
req.log.info({ count: orders.length }, 'Orders retrieved');
res.json(orders);
} catch (err) { next(err); }
});
For APM, integrate with tools like: Datadog APM, New Relic, OpenTelemetry, or Prometheus + Grafana with the prom-client library for custom metrics.
21 What is the express-validator library and how do you create custom validators? ►
Validation express-validator supports custom validators for business logic checks that go beyond simple type/format validation.
const { body, validationResult } = require('express-validator');
// Custom validator โ check uniqueness in DB
body('email').custom(async (email) => {
const exists = await User.findOne({ email });
if (exists) throw new Error('Email address is already registered');
return true;
});
// Custom validator โ check related resource exists
body('categoryId').custom(async (id) => {
const category = await Category.findById(id);
if (!category) throw new Error('Category not found');
return true;
});
// Custom sanitiser โ slugify a title
body('title')
.trim()
.customSanitizer(value => value.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''));
// Conditional validation โ validate only if field is present
body('phone')
.optional({ nullable: true })
.isMobilePhone('any')
.withMessage('Invalid phone number');
// Cross-field validation โ confirm password matches
body('confirmPassword').custom((value, { req }) => {
if (value !== req.body.password) throw new Error('Passwords do not match');
return true;
});
📝 Knowledge Check
Test your understanding of advanced Express.js patterns and production techniques.