What is Middleware in Express?
Middleware is a function that executes between the incoming HTTP request and the final route handler. It has access to req, res, and next. Calling next() passes control to the next middleware or route handler. Calling next(error) passes to the error handling middleware.
Request → Middleware 1 → Middleware 2 → Route Handler → Response
Types of middleware:
- Application-level:
app.use(fn)— runs for all requests - Router-level:
router.use(fn)— runs for routes in that router - Route-level:
router.get('/path', fn, handler)— runs for specific route - Error-handling:
app.use((err, req, res, next) => {})— 4 parameters
---
cookie-parser Package
npm install cookie-parser
import cookieParser from 'cookie-parser';
app.use(cookieParser());
// Now req.cookies.tokenName is accessible in all route handlers
---
Cookie Options Explained
| Option | Description | Security Impact |
|---|---|---|
httpOnly: true | Cookie cannot be read by JavaScript (document.cookie) | Prevents XSS token theft |
secure: true | Cookie sent only over HTTPS | Prevents transmission over insecure HTTP |
sameSite: 'strict' | Cookie not sent on cross-site requests | Strongest CSRF protection |
sameSite: 'lax' | Cookie sent on safe cross-site GET requests | Balanced CSRF protection (recommended) |
sameSite: 'none' | Cookie sent on all cross-site requests | Requires secure: true |
maxAge | Expiry in milliseconds from now | Sets when cookie expires |
expires | Specific expiry Date object | Sets when cookie expires |
path | URL path where cookie is sent | Scope the cookie to a path |
domain | Domain cookie applies to | Use for subdomain sharing |
Recommended production cookie options:
const cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // HTTPS only in prod
sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax',
maxAge: 24 * 60 * 60 * 1000, // 1 day in milliseconds
};
---
verifyJWT Auth Middleware — Complete Implementation
The auth middleware protects routes that require authentication. It:
- Reads the access token from cookies or the Authorization header
- Verifies and decodes the JWT
- Fetches the user from the database
- Attaches user to
req.user - Calls
next()to proceed to the route handler
import jwt from 'jsonwebtoken';
import { User } from '../models/user.model.js';
import { ApiError } from '../utils/ApiError.js';
import { asyncHandler } from '../utils/asyncHandler.js';
export const verifyJWT = asyncHandler(async (req, res, next) => {
// Step 1: Read token from cookie OR Authorization header
const token =
req.cookies?.accessToken ||
req.header('Authorization')?.replace('Bearer ', '');
// Step 2: Token not found
if (!token) {
throw new ApiError(401, 'Unauthorized: No access token provided');
}
// Step 3: Verify token (throws JsonWebTokenError or TokenExpiredError)
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Step 4: Fetch user from DB (exclude sensitive fields)
const user = await User.findById(decoded._id).select('-password -refreshToken');
if (!user) {
throw new ApiError(401, 'Unauthorized: User no longer exists');
}
// Step 5: Attach user to request object
req.user = user;
// Step 6: Proceed to next middleware or route handler
next();
});
---
Using verifyJWT on Protected Routes
import { verifyJWT } from '../middlewares/auth.middleware.js';
// Public routes (no auth required)
router.post('/login', loginUser);
router.post('/register', upload.fields([...]), registerUser);
// Protected routes (auth required)
router.route('/logout').post(verifyJWT, logoutUser);
router.route('/profile').get(verifyJWT, getCurrentUser);
router.route('/change-password').post(verifyJWT, changePassword);
router.route('/update-account').patch(verifyJWT, updateAccountDetails);
---
Token Expiry Handling
When the access token expires (after 15 minutes), the jwt.verify() call throws a TokenExpiredError. The error handler middleware catches this and returns a 401 response. The frontend should then:
- Detect the 401 response
- Call the
/api/auth/refresh-tokenendpoint with the refresh token (sent automatically via cookie) - Receive a new access token
- Retry the original failed request
This is handled in the Axios response interceptor (see Lesson 3).
---
Custom Properties on req Object
TypeScript users need to declare custom properties added to req:
// src/types/express.d.ts
import { IUser } from '../models/user.model';
declare global {
namespace Express {
interface Request {
user?: IUser;
}
}
}
---
Middleware Chain Example
// Multiple middleware on a single route:
router.post(
'/videos',
verifyJWT, // 1. Check authentication
checkSubscriptionTier, // 2. Check if user can upload (business logic)
upload.fields([ // 3. Handle file uploads
{ name: 'video', maxCount: 1 },
{ name: 'thumbnail', maxCount: 1 }
]),
publishVideo // 4. Final controller
);
Each middleware calls next() on success. If any middleware calls next(error) or throws (with asyncHandler), the chain is stopped and the error middleware runs.