Understanding `app.use()` in Express.js: The Backbone of Middleware
DEV Community

Understanding app.use() in Express.js: The Backbone of Middleware

If you've started learning Node.js and Express.js, you've probably seen app.use() in almost every project:

const express = require("express");
const app = express();
app.use(express.json());

At first glance, it may seem like just another Express function. But in reality, app.use() is one of the most important concepts in Express. It is responsible for building the request-processing pipeline by registering middleware. In this article, we'll explore what app.use() is, why it's important, how it works internally, and common real-world use cases.

What is app.use()?

app.use() is a method provided by Express that registers middleware. Middleware is simply a function that executes between receiving an HTTP request and sending the HTTP response. Every incoming request passes through one or more middleware functions before it reaches the final route handler.

The syntax is:

app.use([path], middleware);
  • path (optional): URL path where the middleware should run.
  • middleware: Function executed for matching requests.

A middleware function looks like this:

function middleware(req, res, next) {
  console.log("Middleware executed");
  next();
}

The next() function tells Express to continue processing the next middleware or route handler.

Why Do We Need Middleware?

Imagine an Express application without middleware:

app.get("/users", (req, res) => {
  res.send("Users");
});

The request directly reaches the route handler. But real-world applications need much more:

  • Validate incoming requests
  • Parse JSON
  • Authenticate users
  • Log requests
  • Handle errors
  • Enable CORS
  • Serve static files

Instead of repeating this logic in every route, Express lets us write reusable middleware and register it using app.use().

How Express Processes Requests

Suppose we have:

app.use((req, res, next) => {
  console.log("Middleware 1");
  next();
});

app.use((req, res, next) => {
  console.log("Middleware 2");
  next();
});

app.get("/", (req, res) => {
  console.log("Route Handler");
  res.send("Hello");
});

When a request comes to /, Express executes the following pipeline:

Incoming Request
โ”‚
โ–ผ
Middleware 1
โ”‚
โ–ผ
Middleware 2
โ”‚
โ–ผ
Route Handler
โ”‚
โ–ผ
Response Sent

Console Output:

Middleware 1
Middleware 2
Route Handler

Express simply walks through its middleware stack in the order the middleware was registered.

The Importance of next()

Every middleware receives three parameters: (req, res, next)

Example:

app.use((req, res, next) => {
  console.log("Request received");
  next();
});

next() tells Express: "I'm done. Move to the next middleware."

If you forget to call next(), Express stops processing. Example:

app.use((req, res, next) => {
  console.log("Stopped here");
});

Now every request hangs because Express never moves to the next middleware.

Global Middleware

Without specifying a path, middleware runs for every request.

app.use((req, res, next) => {
  console.log(req.method, req.url);
  next();
});

Requests:

  • GET /users
  • POST /login
  • DELETE /products/5

All three execute this middleware. This is why logging middleware is usually global.

Path-Specific Middleware

Middleware can also be attached to a specific path.

app.use("/api", (req, res, next) => {
  console.log("API Middleware");
  next();
});

This middleware executes for:

  • GET /api/users
  • POST /api/login
  • PUT /api/orders

But not for:

  • GET /
  • GET /about

Express matches the URL prefix before executing the middleware.

app.use() Works for All HTTP Methods

Unlike app.get() or app.post(), app.use() doesn't care about the HTTP method.

app.use("/users", middleware);

Runs for:

  • GET /users
  • POST /users
  • PUT /users
  • DELETE /users
  • PATCH /users

This makes it perfect for middleware like authentication.

Real-World Use Cases

1. Parsing JSON

app.use(express.json());

Without this:

console.log(req.body);
// Output: undefined

With express.json():

{ "name": "John" }

becomes req.body = { name: "John" };

2. Logging Requests

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

Output:

GET /users
POST /login

3. Authentication

app.use((req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).send("Unauthorized");
  }
  next();
});

Every request must pass authentication before reaching the routes.

4. CORS

const cors = require("cors");
app.use(cors());

This allows browsers to make requests from different origins.

5. Serving Static Files

app.use(express.static("public"));

Now public/logo.png is available as: http://localhost:3000/logo.png

6. Mounting Routers

Suppose we have:

// users.js
router.get("/", getUsers);
router.post("/", createUser);

Register it:

app.use("/users", userRouter);

Routes become:

  • GET /users
  • POST /users

This keeps applications modular and easier to maintain.

Execution Order Matters

Consider:

app.use(authMiddleware);
app.get("/dashboard", dashboard);

Every request to /dashboard is authenticated.

Now reverse them:

app.get("/dashboard", dashboard);
app.use(authMiddleware);

Now authentication never runs because the response is already sent. Middleware executes strictly in the order it is registered.

Error-Handling Middleware

Express recognizes error middleware by four parameters.

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ message: err.message });
});

Whenever next(error) is called, Express skips normal middleware and jumps directly to this error handler.

app.use() vs app.get()

Feature app.use() app.get()
Purpose Register middleware Register a GET route
HTTP Methods All methods Only GET
Path Required Optional Required
Calls next() Yes Usually sends response
Used For Logging, auth, parsing, routers Handling GET requests

Example:

app.use("/users", middleware);

Handles: GET /users, POST /users, PUT /users, DELETE /users

Whereas:

app.get("/users", handler);

Handles only: GET /users

Best Practices

  • Register middleware in the correct order.
  • Always call next() unless you're sending a response.
  • Keep middleware focused on a single responsibility.
  • Use path-specific middleware when appropriate.
  • Place error-handling middleware at the end of your middleware stack.

Conclusion

app.use() is the foundation of an Express application. It lets you build a request-processing pipeline where each middleware performs a specific task before handing control to the next one. Whether you're parsing request bodies, authenticating users, logging requests, enabling CORS, serving static files, or organizing routes, app.use() is the mechanism that ties everything together.

Understanding how app.use() works-and especially how middleware order and next() affect request flow-is essential for writing clean, scalable, and maintainable Express applications. If you're learning Express, mastering app.use() is one of the biggest steps toward understanding how Express handles requests under the hood.

Thanks for reading! ๐Ÿš€ If you found this article helpful, consider sharing it with fellow developers who are learning Express.js.

Comments

No comments yet. Start the discussion.