Application Structure
By Syncfusion®, Inc. · Last updated
Splitting app.js and server.js, a project folder layout, a central config module, registration order, startup errors and graceful shutdown, testing with node:test and Supertest, Express Generator, and keeping code healthy.
What you will learn
- Split app.js and server.js
- Organise routes, handlers, middleware and config
- Register middleware in the right order
- Handle startup errors and SIGTERM
- Test routes with node:test and Supertest
Application Structure Introduction
Small demos fit in one file. Real apps do not.
- Application structure = how you split an app into files and folders.
- Good structure makes code easy to find, change and test.
- Express does not force a layout. You choose one and keep it consistent.
- This lesson shows a common, simple layout for the canteen API.
- It also shows the order to register everything, and how to test it.
Simple lesson: Put each kind of code in its own place, and register it in a fixed order.
A Real-World Example
Think about a well-run school kitchen.
- Vegetables, spices and plates each have their own shelf.
- Every cook knows where to find things, even on their first day.
- The work happens in a fixed order: wash, cut, cook, serve.
- A food inspector can check each station on its own.
Shelves are folders. The fixed order is the registration order. The inspector is your tests.
In this lesson, you will learn:
- Why to split
app.jsandserver.js. - A folder layout for a real Express project.
- A central configuration module.
- The order to register settings, middleware, routes and errors.
- How to start and stop the server safely.
- How to test routes with Supertest.
Project Layout
canteen-api/
├── src/
│ ├── app.js builds and exports the Express app
│ ├── server.js starts the HTTP server
│ ├── config/index.js reads and checks settings
│ ├── routes/ one router per resource
│ ├── handlers/ the functions routes call
│ ├── middleware/ logger, auth, validation, errors
│ ├── utils/ small shared helpers
│ └── views/ templates, if the app renders pages
├── public/ static files
├── test/ automated tests
├── .env .env.example .gitignore
└── package.json
- src/ = all the code that runs, kept apart from settings and tests.
- routes/ = which URL goes where. handlers/ = what happens there.
- middleware/ = shared steps, such as checks and logging.
- config/ = the only place that reads
process.env. - utils/ = small helpers, such as
formatPrice. - public/ and views/ = files for browsers. See Views and Static Files.
Simple lesson: Routes, handlers, middleware and config each get their own folder.
app.js and server.js
- app.js creates the app, registers everything and exports it. It never calls
listen. - server.js imports the app, reads the port and calls
listen. - Tests import
app.jsand call it directly, with no real port. - You can start the same app in different ways, such as with HTTPS or in a cloud function.
- This split was introduced in Introduction to Express.js. Here it carries a whole project.
- Open the explorer to see the whole tree.
- Each file is short, and its job is clear from its folder and name.
Simple lesson: app.js wires everything up. server.js only starts it.
Central Configuration
- One config module reads
process.envand checks it. - Every other file imports
config, neverprocess.envdirectly. - Convert types once:
Number(process.env.PORT ?? 3000). - Fail at startup when a required value is missing.
- Changing a setting name later means changing one file.
Simple lesson: One file reads the environment. Everyone else reads config.
Registration Order
The order of app.use and app.set calls decides how every request flows.
- Settings:
trust proxy,x-powered-by, view engine. - Security:
helmet(),cors(), rate limits. - Logging: request logger or Morgan.
- Parsers:
express.json(),express.urlencoded(), then cookies and sessions. - Static files:
express.static(). - Authentication: middleware that sets
req.user. - Routers: your routes.
- Not found: the 404 handler.
- Errors: the four-input error handler, always last.
Easy way to remember: settings, shields, logs, parsers, files, users, routes, 404, errors.
- Compare Headers:
/menuhas no security headers,/laterhas them. - Move
app.use(helmet())to the top, and both routes are protected.
Order bugs give no error message. The app works, just less safely. Review the registration order in every code review.
Simple lesson: Middleware only helps what comes after it, so order is everything.
Starting and Stopping the Server
- Read the port from
config. - Handle startup errors: in Express 5, the
listencallback gets the error, such as a busy port. - Log one clear line when the server is ready.
- Graceful shutdown = finish current requests before stopping.
- Hosts send SIGTERM before stopping a server. Listen for it, call
server.close(), then exit.
const server = app.listen(config.port, () => console.log("Ready"));
process.on("SIGTERM", () => {
console.log("SIGTERM received, closing the server");
server.close(() => {
// Close database connections here.
process.exit(0);
});
});
In this lab, the Stop button ends the server at once, like Ctrl + C. On a real host, the SIGTERM code above lets requests in progress finish first.
- Instead of a long stack trace, the terminal shows one helpful line.
process.exit(1)tells the host the start failed, so it can alert you or retry.
Simple lesson: Start with clear errors, and stop gently so no request is cut off.
Testing Routes with Supertest
Automated test = code that checks your code, run with one command.
- node:test = Node.js's built-in test runner. Run it with
node --test. - node:assert/strict = checks such as
assert.equal(a, b). - Supertest = sends requests straight to your
app, with no real port. Install:npm install --save-dev supertest. await request(app).get("/api/menu").expect(200)checks one route.- Test files usually end in
.test.jsand live intest/.
- Press ▶ Run. The terminal runs
node --testand shows each test. - Change
"Idli"in the test to"Dosa"and run again. One test fails and says why. - Tests import
app.js, which is why it must not calllisten.
- The test fails with
expected 201 "Created", got 200 "OK". - Change
status(200)tostatus(201)inapp.js, and the test passes.
Simple lesson: Export the app, test it with Supertest, and run node --test before every change.
Express Generator
express-generator = an official tool that creates a starter project.
npx express-generator --view=ejs canteen-site
- It creates
app.js,bin/www(the server),routes/,views/andpublic/. --viewpicks the template engine, such asejsorpug.- It uses older CommonJS style (
require), notimport. - It is quick for server-rendered sites, but you will change most files.
- Building the structure by hand, as in this lesson, teaches you every part.
Simple lesson: The generator gives a fast start. A hand-made layout gives you full control.
Keeping the Code Healthy
- Small route files: if a file passes about 200 lines, split it.
- Reuse handlers and middleware instead of copying code.
- Consistent names:
routes/menu.js,handlers/menu.js,middleware/auth.js. - One-way imports: routes import handlers, handlers import utils. Never the other way.
- Circular imports = two files that import each other. Values may be
undefinedat startup. Move shared code to a third file.
Simple lesson: Small files, clear names and one-way imports keep a project easy to grow.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
app.listen inside app.js | One file is simpler | app.js exports, server.js listens | Tests start real servers and clash |
process.env in every file | It is always available | Read it once in config | Typos and missing checks everywhere |
| 404 handler before routes | Order does not matter | Routes, then 404, then errors | Every request gets 404 |
| No SIGTERM handling | Stopping is instant anyway | Close the server gently | Users see cut-off requests during deploys |
| Business logic in route files | Routes can do everything | Routes point, handlers work | Huge files that are hard to test |
| Two modules importing each other | Imports always work | Avoid cycles | undefined values at startup |
| No tests | Manual checks are enough | Automate with node --test | Old features break silently |
Check Yourself
Exercises
What to Remember
- app.js builds and exports the app. server.js starts it.
- Folders: routes, handlers, middleware, config, utils, views, public, test.
- One config module reads and checks
process.env. - Register in order: settings, security, logging, parsers, static, auth, routes, 404, errors.
- Handle startup errors, and shut down gently on SIGTERM.
- Test routes with Supertest and
node --test.
Next: Express with TypeScript. You will add types to your Express app and catch mistakes early.