DEV Community

Cover image for Understanding `app.use()` in Express.js: The Backbone of Middleware
Srishti Prasad
Srishti Prasad

Posted on

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());
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
  • 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();
}
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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

Incoming Request
       │
       ▼
Middleware 1
       │
       ▼
Middleware 2
       │
       ▼
Route Handler
       │
       ▼
Response Sent
Enter fullscreen mode Exit fullscreen mode

Console Output:

Middleware 1
Middleware 2
Route Handler
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Example:

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

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");
});
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

Requests:

GET /users
POST /login
DELETE /products/5
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

This middleware executes for:

GET /api/users
POST /api/login
PUT /api/orders
Enter fullscreen mode Exit fullscreen mode

But not for:

GET /
GET /about
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Runs for:

GET /users
POST /users
PUT /users
DELETE /users
PATCH /users
Enter fullscreen mode Exit fullscreen mode

This makes it perfect for middleware like authentication.


Real-World Use Cases

1. Parsing JSON

app.use(express.json());
Enter fullscreen mode Exit fullscreen mode

Without this:

console.log(req.body);
Enter fullscreen mode Exit fullscreen mode

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

With express.json():

{
  "name": "John"
}
Enter fullscreen mode Exit fullscreen mode

becomes

req.body = {
  name: "John"
};
Enter fullscreen mode Exit fullscreen mode

2. Logging Requests

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

Output:

GET /users
POST /login
Enter fullscreen mode Exit fullscreen mode

3. Authentication

app.use((req, res, next) => {

    if (!req.headers.authorization) {
        return res.status(401).send("Unauthorized");
    }

    next();

});
Enter fullscreen mode Exit fullscreen mode

Every request must pass authentication before reaching the routes.


4. CORS

const cors = require("cors");

app.use(cors());
Enter fullscreen mode Exit fullscreen mode

This allows browsers to make requests from different origins.


5. Serving Static Files

app.use(express.static("public"));
Enter fullscreen mode Exit fullscreen mode

Now:

public/logo.png
Enter fullscreen mode Exit fullscreen mode

is available as:

http://localhost:3000/logo.png
Enter fullscreen mode Exit fullscreen mode

6. Mounting Routers

Suppose we have:

// users.js

router.get("/", getUsers);

router.post("/", createUser);
Enter fullscreen mode Exit fullscreen mode

Register it:

app.use("/users", userRouter);
Enter fullscreen mode Exit fullscreen mode

Routes become:

GET /users
POST /users
Enter fullscreen mode Exit fullscreen mode

This keeps applications modular and easier to maintain.


Execution Order Matters

Consider:

app.use(authMiddleware);

app.get("/dashboard", dashboard);
Enter fullscreen mode Exit fullscreen mode

Every request to /dashboard is authenticated.

Now reverse them:

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

app.use(authMiddleware);
Enter fullscreen mode Exit fullscreen mode

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
    });

});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Handles:

GET /users
POST /users
PUT /users
DELETE /users
Enter fullscreen mode Exit fullscreen mode

Whereas:

app.get("/users", handler);
Enter fullscreen mode Exit fullscreen mode

Handles only:

GET /users
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
frank_signorini profile image
Frank

How does app.use() handle middleware functions with multiple arguments, I've seen some examples but still unsure. Would love to hear your thoughts on this.