Project structure
Express does not create folders for you: everything can live in one file. But once there are more than a dozen routes, such a file is hard to read. So a project is split into folders, and each part has its own role.
The blog structure
This is how the project will look by the middle of the course:
blog/
├── package.json
├── .env settings, not committed to Git
├── src/
│ ├── app.js creates the application and wires everything up
│ ├── server.js starts the server
│ ├── routes/
│ │ └── posts.js addresses of the posts section
│ ├── controllers/
│ │ └── postsController.js handler functions
│ ├── models/
│ │ └── post.js working with data
│ └── middleware/
│ └── requireAuth.js
├── views/ HTML page templates
│ └── posts/
│ └── index.ejs
├── public/ CSS, images, scripts for the browser
│ └── css/style.css
└── test/ automated testsThe views/ and public/ folders are in the root: by default Express looks for templates in views/ relative to the folder the server is started from.
Folder roles
| Folder or file | Contents |
|---|---|
src/app.js | Application setup: middleware, routes, error handling |
src/server.js | Only starting: app.listen() |
src/routes/ | Which address is handled by which function |
src/controllers/ | What to do with a request: get data, respond |
src/models/ | Reading and writing data |
views/ | Page templates |
public/ | Files the browser receives as they are |
Splitting app.js and server.js
Create the file src/app.js, move the application into it and export it:
// src/app.js
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello from the blog!');
});
export default app;And src/server.js now only imports the application and starts it:
// src/server.js
import app from './app.js';
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server started: http://localhost:${PORT}`);
});npm run dev works as before: http://localhost:3000 shows the same greeting. But now the application from app.js can be imported in tests without starting a real server — this will come in handy in the testing lesson.
How the files connect
A request travels from top to bottom: server.js starts the application from app.js, app.js passes the request to the right file in routes/, the route calls a function from controllers/, the controller takes data from models/ and returns a page built from a template in views/. Each link of the chain gets its own lesson.
Common mistake
In ES modules the path to your own file is written in full, with the extension: import app from './app.js'. Writing './app' without .js works in some bundlers, but Node.js stops with the error ERR_MODULE_NOT_FOUND.