devnotes.

Separate App and Server

A common Express convention is splitting app.js (the application logic) from server.js (the process that starts listening). It's a small change with a real payoff in testability and clarity.

What app.js is responsible for

app.js builds the Express application: middleware, routes, settings — everything except actually starting a server.

// app.js
const express = require("express");
const app = express();

app.use(express.json());

app.get("/", (req, res) => {
    res.send("Bookstore API");
});

module.exports = app;
  • Middleware like express.json() is registered here.
  • Routes are wired up here.
  • The app is exported, not started, so other files (tests, server.js) can use it.

What server.js is responsible for

server.js imports the app, decides the port, and starts listening.

// server.js
const app = require("./app");
const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
    console.log(`Server is running at http://localhost:${PORT}`);
});
  • Imports the configured app.
  • Reads the port from the environment (falling back to a default).
  • Starts the server and logs a status message.

Why bother splitting them

  1. Separation of concerns — app.js is "what the app does", server.js is "how it runs". Each file has one job.
  2. Cleaner organization — adding a route or middleware means editing app.js only; you never touch server startup code to do it.
  3. Easier debugging — a routing bug and a "server won't start" bug live in different places.
  4. Scales with the project — new features slot into app.js without risk to the listening logic.
  5. Testability — since app.js exports the Express instance without calling listen(), test frameworks (like Supertest) can import it directly, without spinning up a real server or port.
Separate App and Server | DevNotes