Project Structure

Organize a growing app into routes, controllers, models and middleware folders.

Small apps fit in one file, but real projects benefit from a clear layout. A common MVC-style structure separates concerns so each file has one job.

A typical Express project folder structuremy-app/app.js (create app, mount routes)routes/ (URL definitions)controllers/ (request logic)models/ (data / database)middleware/ (reusable functions)views/ (templates)Request flow:route → controllercontroller → modelcontroller → view / json
Routes point to controllers, controllers use models and return views or JSON.

Example

Example · javascript
// app.js
const express = require('express');
const app = express();

app.use(express.json());
app.use('/users', require('./routes/users'));
app.use('/posts', require('./routes/posts'));

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

module.exports = app;

When to use it

  • A growing Express API is reorganised into routes/, controllers/, services/, and middleware/ folders so each layer has a clear responsibility.
  • A team enforces that app.js only contains the Express setup and app.listen() lives in server.js so tests can import the app without starting the server.
  • A developer adds an index.js barrel file to each feature folder so import paths stay short even as the project grows.

More examples

Recommended folder layout

Shows a scalable project layout that separates routing, business logic, and HTTP concerns into dedicated directories.

Example · bash
my-api/
  src/
    routes/        # express.Router() files
    controllers/   # request handlers
    services/      # business logic + DB calls
    middleware/    # custom middleware
    models/        # Mongoose/Sequelize models
  app.js           # Express setup, middleware registration
  server.js        # app.listen() entry point

app.js vs server.js split

Keeps app.js free of listen() so test suites can import the configured app and call supertest without binding a port.

Example · js
// app.js — export app WITHOUT calling listen
const express = require('express');
const app = express();
app.use(express.json());
app.use('/users', require('./routes/users'));
module.exports = app;

// server.js — call listen here only
const app = require('./app');
app.listen(process.env.PORT || 3000);

Route barrel index file

A barrel router in routes/index.js mounts all feature routers, so app.js only needs one app.use() call to register the entire API.

Example · js
// routes/index.js
const express = require('express');
const router = express.Router();

router.use('/users',    require('./users'));
router.use('/products', require('./products'));
router.use('/orders',   require('./orders'));

module.exports = router;

// app.js
app.use('/api/v1', require('./routes'));

Discussion

  • Be the first to comment on this lesson.