# Express.js API Architecture
Express is minimal by design, which means architecture decisions are entirely up to the developer. Here's a structure that scales beyond a single-file API.
## Middleware Pipeline
Every Express request flows through a chain of middleware functions before reaching a route handler:
```javascript
app.use(express.json());
app.use(cors());
app.use(authMiddleware);
```
Order matters — middleware runs top to bottom, so authentication checks must come before protected routes.
## Router-Based Structure
Instead of defining every route in one file, split them by resource using `express.Router()`:
```javascript
// routes/courses.js
const router = require("express").Router();
router.get("/", getCourses);
router.post("/", createCourse);
module.exports = router;
// app.js
app.use("/courses", require("./routes/courses"));
```
## Controller Layer
Route files should only wire up paths to handler functions. Actual logic belongs in controllers, keeping routing and business logic separate:
```javascript
exports.getCourses = async (req, res) => {
const courses = await CourseService.findAll();
res.json(courses);
};
```
## Centralized Error Handling
Express supports a special error-handling middleware signature with four parameters, placed at the end of the middleware chain:
```javascript
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});
```
This avoids repeating try/catch error formatting in every route.
## Conclusion
Express doesn't enforce structure, so discipline matters — separating middleware, routes, controllers, and error handling keeps an API maintainable as it grows past a handful of endpoints.
Back to Blogs
Express.js API Architecture
How to structure a real Express.js application — middleware, routers, controllers, and error handling that scales past a single file.
04 Aug 2026
6 min read