const express = require('express');
const app = express();
// ── Middleware ──
app.use(express.json()); // parse JSON bodies
app.use(express.urlencoded({ extended: true })); // parse URL-encoded
app.use(express.static('public')); // serve static files
app.use(require('cors')()); // enable CORS
app.use(require('helmet')()); // security headers
app.use(require('morgan')('dev')); // HTTP request logger
// ── Basic Routes ──
app.get('/', (req, res) => {
res.json({ message: 'Hello Express!' });
});
app.get('/users/:id', (req, res) => {
const { id } = req.params;
const { fields } = req.query;
res.json({ id, fields });
});
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name) return res.status(400).json({ error: 'Name is required' });
res.status(201).json({ id: 1, name, email });
});
app.put('/users/:id', (req, res) => res.json({ updated: true }));
app.patch('/users/:id', (req, res) => res.json({ patched: true }));
app.delete('/users/:id', (req, res) => res.status(204).send());
// ── Route Patterns ──
app.get('/users/:userId/posts/:postId', (req, res) => {
res.json({ userId: req.params.userId, postId: req.params.postId });
});
// Regex routes
app.get(/^\/api\/users\/(\d+)$/, (req, res) => {
res.json({ id: req.params[0] });
});
// ── Router (modular routes) ──
const userRouter = express.Router();
userRouter.get('/', (req, res) => res.json({ users: [] }));
userRouter.get('/:id', (req, res) => res.json({ user: req.params.id }));
userRouter.post('/', (req, res) => res.status(201).json({ created: true }));
app.use('/api/users', userRouter);
// ── Custom Middleware ──
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
};
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'No token' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
};
// Apply middleware
app.use('/api', logger);
app.use('/api/protected', authenticate);
// Error-handling middleware (must be last!)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({ error: err.message });
});
app.listen(3000, () => console.log('Server on :3000'));
| Property | Description |
|---|
| req.params | URL parameters (/users/:id) |
| req.query | Query string (/users?page=1) |
| req.body | Parsed request body (JSON) |
| req.headers | HTTP headers |
| req.method | HTTP method (GET, POST, etc.) |
| req.url | Full path |
| req.path | Path without query string |
| req.cookies | Parsed cookies (cookie-parser) |
| req.ip | Client IP address |
| req.protocol | http or https |
| Method | Description |
|---|
| res.json() | Send JSON response |
| res.send() | Send various types |
| res.status() | Set HTTP status code |
| res.redirect() | Redirect (302 default) |
| res.render() | Render template |
| res.sendFile() | Send file |
| res.download() | Prompt file download |
| res.set() | Set response header |
| res.cookie() | Set cookie |
| res.clearCookie() | Clear cookie |
// ── Custom Error Classes ──
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404);
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401);
}
}
class ForbiddenError extends AppError {
constructor(message = 'Forbidden') {
super(message, 403);
}
}
class ValidationError extends AppError {
constructor(errors) {
super('Validation failed', 400);
this.errors = errors;
}
}
// ── Async Error Wrapper ──
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new NotFoundError('User');
res.json(user);
}));
// ── Global Error Handler ──
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
// Development: full error details
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
message: err.message,
stack: err.stack,
});
} else {
// Production: hide details for 5xx errors
res.status(err.statusCode).json({
status: err.status,
message: err.isOperational ? err.message : 'Something went wrong',
});
}
});
// ── 404 Handler (no route matched) ──
app.all('*', (req, res) => {
res.status(404).json({
status: 'fail',
message: `Route ${req.originalUrl} not found`,
});
});
🚫Error-handling middleware must have 4 parameters. Define it after all routes and other middleware. Express identifies it by the 4 parameters (err, req, res, next). Without it, errors crash the process. Always handle unhandled promise rejections.
const multer = require('multer');
const path = require('path');
// ── Multer (disk storage) ──
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
cb(null, `${Date.now()}-${file.fieldname}${ext}`);
},
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max
fileFilter: (req, file, cb) => {
const allowed = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
const ext = path.extname(file.originalname).toLowerCase();
if (allowed.includes(ext)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
},
});
// Single file upload
app.post('/upload', upload.single('avatar'), (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No file' });
res.json({ url: `/uploads/${req.file.filename}` });
});
// Multiple files
app.post('/upload-multi', upload.array('photos', 10), (req, res) => {
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.json({ urls });
});
// Named fields
app.post('/upload-mixed', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'documents', maxCount: 5 },
]), (req, res) => {
res.json({ avatar: req.files.avatar, docs: req.files.documents });
});
// ── Stream Response (large files) ──
const fs = require('fs');
app.get('/download/:file', (req, res) => {
const filePath = path.join('uploads', req.params.file);
if (!fs.existsSync(filePath)) return res.status(404).json({ error: 'Not found' });
const stream = fs.createReadStream(filePath);
res.setHeader('Content-Disposition', `attachment; filename="${req.params.file}"`);
stream.pipe(res);
});
⚠️Never trust file uploads blindly. Always validate file type (by extension and MIME type), limit file size, rename files to prevent directory traversal, store uploads outside the web root, and scan files with an antivirus in production.
Q: What is Express middleware?A function with access to req, res, and next. It can execute code, modify req/res, end the response cycle, or call next() to pass control to the next middleware. Order matters. Types: application-level, router-level, error-handling, built-in, third-party, and custom.
Q: How does Express routing work?Express matches incoming requests against registered routes by URL pattern and HTTP method. Routes are matched in order of registration. First match wins. Use express.Router() for modular routes. Supports pattern matching, regex, and parameter extraction via req.params.
Q: How do you handle errors in Express?1) Synchronous: wrap in try/catch or use asyncHandler wrapper. 2) Async: use asyncHandler or .catch(next). 3) Error-handling middleware (4 params) catches all errors. 4) Create custom error classes (AppError) with statusCode. 5) Never expose stack traces in production.
Q: How does JWT authentication work?Client sends credentials to /login. Server verifies and returns a signed JWT. Client includes JWT in Authorization: Bearer header. Server middleware verifies token and attaches user to req. JWT is stateless — no session storage needed. Use short-lived access tokens + refresh tokens.
Q: What are security best practices?Use helmet() for security headers, express-rate-limit for brute-force protection, bcryptjs for password hashing, cors() for CORS, express-mongo-sanitize for NoSQL injection, xss-clean for XSS, input validation (joi/zod), httpOnly cookies for tokens, and environment variables for secrets.
Q: Express vs Fastify vs Koa?Express: most popular, huge ecosystem, middleware-based. Fastify: 2-3x faster, JSON schema validation, plugin-based. Koa: by Express authors, async/await native, no bundled middleware, more lightweight. All are compatible with Node.js middleware ecosystem.
Q: How do you handle file uploads?Use multer middleware. Configure storage (disk/memory), file size limits, file type validation, and file naming. For production, consider S3-compatible storage (multer-s3). Always validate files on the server side, never trust client-side validation.
💡Top Express interview topics: middleware chain (next function), routing (params, query, patterns), error handling (4-param middleware), authentication (JWT + bcrypt), security (helmet, rate limiting, CORS), file uploads (multer), testing (supertest), and comparison with Fastify/Koa.