Express 24,430 imposes no structure, so you impose one. This is the layout the chapter uses, conventional enough that a new developer finds their way around in minutes.
bookshelf/src/
app.js createApp(): builds the app, returns it, never listens
server.js reads config, calls app.listen()
config.js environment variables parsed and validated once
routes/ one router per resource: books.js, authors.js
controllers/ request in, response out; no business rules
services/ business rules; knows nothing about req or res
data/ the store interface and its in-memory implementation
middleware/ auth.js, validate.js, error-handler.jsThe rule that matters is the split between app.js and server.js. createApp() assembles middleware and routers and returns the app without binding a port; server.js is the only file that calls listen. That lets the test suite build a fresh app per file for Supertest 14,405 (HTTP Testing with Supertest) without racing for port 3000, and lets a serverless platform import the app directly.
The layers under it are a gradient, not a law. routes/ maps URLs to handlers, controllers/ translates between HTTP and plain arguments, services/ holds the rules you would keep if you replaced HTTP with a message queue, and data/ alone knows how records are stored. A small project can fold controllers into routes, but not data/: MongoDB replaces it wholesale. Make config.js throw on startup when a required variable is missing, too — a server that refuses to boot without JWT_SECRET beats one that signs every token with undefined.