Skip to content

Middleware Patterns Project — Build a Complete Middleware Pipeline

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Middleware Patterns Project. We cover key concepts, practical examples, and best practices to help you master this topic.

This middleware patterns project guides you through building a production-ready Express API with a complete middleware pipeline combining logging, authentication, validation, Rate Limiting, error handling, and compression.

What You'll Learn

By completing this project, you will integrate all middleware patterns into a single application, handle real-world edge cases, and understand how middleware layers work together.

Why It Matters

Individual middleware tutorials teach isolated concepts. This project shows how they compose into a working system, just like DodaTech's production APIs.

Real-World Use

The API you build mirrors DodaZIP's file management API: authenticated users upload files, view their files, and manage their account, all protected by a layered middleware pipeline.

Project Learning Path

flowchart LR
  A[Security Middleware] --> B[Project]
  B --> C[Middleware Pipeline]
  C --> D[Complete API]
  B --> E{You Are Here}
  style E fill:#f90,color:#fff

Project Overview

Build a file management API with the following middleware layers in order:

  1. Request logging (morgan)
  2. Security headers (helmet)
  3. CORS configuration
  4. Body Parsing with size limits
  5. Compression
  6. Rate limiting
  7. Authentication (JWT)
  8. Request validation
  9. Error handling

Step 1: Setup and Dependencies

npm init -y
npm install express helmet morgan cors compression express-rate-limit jsonwebtoken joi
npm install -D nodemon
const express = require("express");
const helmet = require("helmet");
const morgan = require("morgan");
const cors = require("cors");
const compression = require("compression");
const rateLimit = require("express-rate-limit");
const jwt = require("jsonwebtoken");
const Joi = require("joi");

const app = express();
const PORT = process.env.PORT || 3000;
const JWT_SECRET = process.env.JWT_SECRET || "project-secret-key";

Step 2: Global Middleware Pipeline

app.use(helmet());
app.use(morgan("combined"));
app.use(cors({ origin: process.env.FRONTEND_URL || "http://localhost:5173" }));
app.use(compression());
app.use(express.json({ limit: "1mb" }));

const limiter = rateLimit({
  windowMs: 60000,
  max: 60,
  message: { error: "Rate limit exceeded" }
});
app.use(limiter);

Expected behavior: Every request passes through security headers, logging, CORS, compression, body parsing, and rate limiting before reaching any route.

Step 3: Authentication Middleware

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return res.status(401).json({ error: "Authorization header required" });
  }

  const token = authHeader.split(" ")[1];

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = { id: decoded.userId, username: decoded.username };
    next();
  } catch (err) {
    return res.status(401).json({ error: "Invalid or expired token" });
  }
}

Step 4: Validation Middleware

const fileSchema = Joi.object({
  filename: Joi.string().min(1).max(255).required(),
  content: Joi.string().max(10485760).allow("").optional(),
  isPublic: Joi.boolean().default(false)
});

function validateFile(req, res, next) {
  const { error, value } = fileSchema.validate(req.body);

  if (error) {
    return res.status(400).json({
      error: "Validation failed",
      details: error.details.map(d => d.message)
    });
  }

  req.body = value;
  next();
}

Step 5: Routes

app.post("/auth/login", (req, res) => {
  const { username, password } = req.body;
  if (username === "admin" && password === "secret") {
    const token = jwt.sign(
      { userId: 1, username: "admin" },
      JWT_SECRET,
      { expiresIn: "1h" }
    );
    return res.json({ token });
  }
  res.status(401).json({ error: "Invalid credentials" });
});

const files = [];
let fileId = 1;

app.get("/api/files", authenticate, (req, res) => {
  const userFiles = files.filter(f => f.userId === req.user.id);
  res.json({ files: userFiles });
});

app.post("/api/files", authenticate, validateFile, (req, res) => {
  const file = {
    id: fileId++,
    userId: req.user.id,
    filename: req.body.filename,
    content: req.body.content,
    isPublic: req.body.isPublic,
    createdAt: new Date().toISOString()
  };
  files.push(file);
  res.status(201).json({ file });
});

app.get("/api/files/:id", authenticate, (req, res) => {
  const file = files.find(f => f.id === Number(req.params.id));
  if (!file) return res.status(404).json({ error: "File not found" });
  if (file.userId !== req.user.id && !file.isPublic) {
    return res.status(403).json({ error: "Access denied" });
  }
  res.json({ file });
});

Step 6: Error Handling

app.use((req, res) => {
  res.status(404).json({ error: "Route not found" });
});

app.use((err, req, res, next) => {
  console.error("Unhandled error:", err.message);
  res.status(500).json({
    error: process.env.NODE_ENV === "production"
      ? "Internal server error"
      : err.message
  });
});

app.listen(PORT, () => {
  console.log(`File API running on port ${PORT}`);
});

Step 7: Test the API

# Login
curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"secret"}'

Expected output:

{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
# Create file (with token)
curl -X POST http://localhost:3000/api/files \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"filename":"notes.txt","content":"Hello World"}'

Expected output:

{"file": {"id": 1, "userId": 1, "filename": "notes.txt", "content": "Hello World", "isPublic": false, "createdAt": "2026-06-28T10:30:00.000Z"}}

Common Mistakes

  1. Middleware order — Authentication must come after body parsing. Rate limiting must come before authentication to block unauthenticated attacks.

  2. Not validating token expiry — Expired tokens pass JWT signature verification. Always check the exp claim.

  3. Exposing internal IDs — Use UUIDs instead of sequential IDs for file identifiers to prevent enumeration.

  4. Not limiting file content size — A 1GB file content in the body exhausts memory. Always limit body size.

  5. Storing passwords in plain text — This example uses plain text for simplicity. Production applications must hash passwords.

Practice Questions

  1. Why is rate limiting placed before authentication in the pipeline? To block excessive requests before they reach the authentication middleware, preventing DoS attacks on the auth system.

  2. How would you add file type validation to this API? Add a validation middleware that checks the filename extension against an allowed list.

  3. What changes would you make for a distributed deployment? Replace in-memory rate limiting with Redis, use a database for file storage, and use a shared cache for tokens.

  4. Challenge: Add a middleware that logs all file access attempts for auditing.

FAQ

How do I add pagination to the files endpoint?

Add query parameter validation middleware for page and limit, then slice the results array before responding.

Should I use sessions or JWT for authentication?

JWT is stateless and scales better for APIs. Sessions work better for server-rendered applications.

How do I handle file uploads instead of text content?

Replace express.json() body parsing with multer middleware for multipart/form-data uploads.

How do I add search functionality?

Add a search middleware that parses the query parameter and filters results before the route handler.

How do I deploy this API?

Containerize with Docker, set environment variables, and deploy to your preferred cloud provider behind a reverse proxy.

Project Extension Ideas

  1. Add file sharing between users with permission middleware
  2. Implement file versioning with history middleware
  3. Add Webhook notifications using middleware events
  4. Implement request/response logging to a database
  5. Add API key authentication alongside JWT

What's Next

Congratulations on completing the middleware patterns project! Explore rate limiting patterns in depth. Then learn about caching strategies for backend APIs.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro