Introduction
A single-file Express app works fine for small demos, but as an application grows to dozens of routes and features, keeping everything in one file becomes unmanageable. A well-structured Express project separates concerns into distinct folders: routes (URL-to-handler mapping), controllers (business logic), middleware (cross-cutting request processing), models (data access), and config (environment and app setup). This structure improves readability, testability, and team collaboration.
Cricket analogy: A single net-practice session works for a beginner, but a national team splits duties across batting coach, bowling coach, fielding coach, and analyst rather than one person doing everything.
Syntax
project/
src/
app.js // Express app configuration
server.js // Entry point, calls app.listen()
routes/
userRoutes.js
orderRoutes.js
controllers/
userController.js
orderController.js
middleware/
errorHandler.js
authMiddleware.js
models/
userModel.js
config/
db.js
package.jsonExplanation
app.js typically creates the Express instance, registers global middleware (like express.json() and logging), mounts routers with app.use('/api/users', userRoutes), and registers the final error-handling middleware. server.js imports the configured app and calls app.listen(), keeping startup logic separate from app configuration — this separation makes the app easier to test, since test frameworks can import app.js directly without starting a real server. Routers (built with express.Router()) map URLs to controller functions. Controllers contain the actual request-handling logic, often delegating data access to model files. This layered approach follows separation of concerns: routes decide 'what URL maps to what', controllers decide 'what to do', and models decide 'how to access data'.
Cricket analogy: app.js is like the team's official team sheet naming the playing eleven and formation, while server.js is the umpire's coin toss that actually starts the match using that pre-set lineup.
Example
// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.listUsers);
router.post('/', userController.createUser);
module.exports = router;
// app.js
const express = require('express');
const userRoutes = require('./routes/userRoutes');
const errorHandler = require('./middleware/errorHandler');
const app = express();
app.use(express.json());
app.use('/api/users', userRoutes);
app.use(errorHandler);
module.exports = app;
// server.js
const app = require('./app');
app.listen(3000, () => console.log('Server started on port 3000'));Output
Running node server.js starts the server and logs 'Server started on port 3000'. A GET request to /api/users is routed through userRoutes.js to userController.listUsers, keeping the app.js file focused purely on wiring rather than business logic.
Cricket analogy: It's like the umpire calling 'play' (server starts, logs ready) and then a specific delivery to a specific batsman being routed through the bowler's run-up to the exact shot outcome, keeping the umpire's role separate from execution.
Key Takeaways
- Separate app configuration (app.js) from server startup (server.js) for easier testing.
- Use express.Router() to split routes into feature-specific files (e.g. userRoutes.js).
- Keep business logic in controllers, not inline in route definitions.
- Centralize error-handling and auth middleware in a dedicated middleware folder.
- This layered structure scales better and is easier for teams to navigate than a single monolithic file.
Practice what you learned
1. Why is it recommended to separate app.js from server.js in a larger Express project?
2. In a typical layered Express structure, where should business logic primarily live?
3. How do you mount a feature-specific router at a URL prefix?
4. What is a key benefit of organizing an Express app into routes, controllers, and models?
Was this page helpful?
You May Also Like
Introduction to Express
Learn what Express is, why it exists on top of Node's http module, and how to create your first Express server.
Middleware in Express
Master the Express middleware chain, the next() function, and the special error-handling middleware signature.
Environment Variables and Configuration
Learn how to manage configuration and secrets in Node.js apps using environment variables and dotenv.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics