How to build a pipeline that checks requests before they reach your business logic.
Every request needs certain checks:
- Do you have a valid API token? (Authentication)
- Can this user perform this action? (Authorization)
- Is the data valid? (Validation)
Instead of repeating these checks in every endpoint, use a middleware chain.
Request
↓
Auth Handler ✓ or ✗
↓
Permission Handler ✓ or ✗
↓
Validation Handler ✓ or ✗
↓
Business Logic (create task, etc.)
↓
Response
If any handler fails, stop immediately and return error.
A system where:
- Each handler checks one specific thing
- Handlers run in order
- Failed check = immediate error response
- Passing all checks = request continues forward
This is the Chain of Responsibility pattern.
See src/middleware/ folder:
AuthHandler.js— checks authentication tokenPermissionHandler.js— checks if user can perform action (Admin vs User)ValidationHandler.js— checks if data is valid
Look at how they call next() to pass control to the next handler.
class AuthHandler {
handle(req, res, next) {
const token = req.headers["authorization"];
if (!token) {
res.writeHead(401, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Missing token" }));
return; // Stop here, don't call next()
}
req.user = verifyToken(token); // Attach user to request
next(); // Pass to next handler
}
}const handlers = [
new AuthHandler(),
new PermissionHandler(),
new ValidationHandler()
];
function runMiddleware(req, res, handler Index = 0) {
if (handlerIndex >= handlers.length) {
// All checks passed, run business logic
businessLogic(req, res);
return;
}
const handler = handlers[handlerIndex];
const next = () => runMiddleware(req, res, handlerIndex + 1);
handler.handle(req, res, next);
}Flow:
- Call first handler, pass
next - If handler calls
next(), run second handler - If handler doesn't call
next(), stop (error response already sent) - After all handlers pass, run business logic
Question: "Who are you?"
# With valid token:
curl http://localhost:3000/tasks \
-H "Authorization: Bearer secret-admin-123"
# Without token:
curl http://localhost:3000/tasks
# Returns: 401 UnauthorizedTokens are stored in your .env file.
Question: "Are you allowed to do this?"
Admin users (token secret-admin-123) can:
- Create tasks
- Update tasks
- Delete tasks
Regular users (token secret-user-123) can only:
- View tasks
# User trying to create task:
curl -X POST http://localhost:3000/tasks \
-H "Authorization: Bearer secret-user-123" \
-d '{"title":"test"}'
# Returns: 403 ForbiddenQuestion: "Is your data valid?"
Checks:
- Required fields present (title, priority)
- Types are correct (priority is number, not string)
- Values make sense (priority between 1-10)
# Invalid priority:
curl -X POST http://localhost:3000/tasks \
-H "Authorization: Bearer secret-admin-123" \
-d '{"title":"test","priority":"high"}'
# Returns: 422 Unprocessable EntityWithout middleware:
function createTask(req, res) {
// Check auth
if (!req.headers.auth) {
res.end("401");
return;
}
// Check permission
if (user.role !== "admin") {
res.end("403");
return;
}
// Check validation
if (!req.body.title) {
res.end("422");
return;
}
// Finally: actual business logic
const task = createTask(req.body);
res.end(JSON.stringify(task));
}
function updateTask(req, res) {
// Repeat all checks again...
}
function deleteTask(req, res) {
// Repeat all checks again...
}With middleware:
// Each endpoint only has business logic
function createTask(req, res) {
const task = store.create(req.body);
res.writeHead(201);
res.end(JSON.stringify(task));
}Checks are done once, reused everywhere.
Task: Add a new middleware handler that checks request body size.
class SizeHandler {
handle(req, res, next) {
// Only allow requests under 1MB
const sizeKB = /* calculate from req */;
if (sizeKB > 1024) {
res.writeHead(413); // Payload Too Large
res.end('{"error":"Request too large"}');
return;
}
next();
}
}Every real API uses middleware:
- Express.js: middleware functions
- Django: middleware classes
- AWS Lambda: middleware/interceptors
- Kubernetes: admission controllers (same pattern!)
Understanding this deeply helps you design secure, maintainable APIs.
You now have requests flowing safely through a middleware chain. But what happens when something goes wrong?
Next: Error Handling & Logging.