DocsHub
Back-EndSetup

Server Setup

Splitting the Express app into app.js and server.js — the proper structure for a production-ready backend.

Server Setup

In the previous file we used a single server.js just to verify the setup worked. Now we split it properly — app.js holds the Express application and all its configuration, server.js is a thin entry point that connects to the database before starting the server.

This separation matters for testing — you can import app.js into a test file without actually starting a server.


Why Split app.js and server.js

yes no server.js starts Connect to MongoDB Connection successful? Import app.js Start listening on PORT Log error and exit

app.js — defines the Express app, middleware, and routes. No listen() call here. server.js — connects to MongoDB first, and only starts the server if the connection succeeds.

This way the server never starts accepting requests before the database is ready.


app.js — Express Application

// src/app.js
import express from "express";
import cors from "cors";

const app = express();

// Step 1 — built-in middleware to parse JSON request bodies
app.use(express.json());

// Step 2 — parse URL-encoded form data
app.use(express.urlencoded({ extended: true }));

// Step 3 — enable CORS so the frontend can make requests to this API
app.use(
  cors({
    origin: process.env.CLIENT_URL || "http://localhost:5173",
    credentials: true, // allow cookies to be sent cross-origin
  })
);

// Step 4 — health check route — useful for deployment monitoring
app.get("/api/health", (req, res) => {
  res.status(200).json({ status: "ok", message: "Server is healthy" });
});

// Step 5 — routes will be mounted here as we build them
// Example (added in later files):
// import authRoutes from "./routes/auth.routes.js";
// app.use("/api/auth", authRoutes);

// Step 6 — 404 handler — runs if no route matched
app.use((req, res) => {
  res.status(404).json({ message: "Route not found" });
});

export default app;

server.js — Entry Point

// server.js
import dotenv from "dotenv";
dotenv.config(); // load .env variables before anything else uses them

import app from "./src/app.js";
import connectDB from "./src/config/db.js";

const PORT = process.env.PORT || 5000;

// Step 1 — connect to MongoDB first
connectDB()
  .then(() => {
    // Step 2 — only start listening once the database connection succeeds
    app.listen(PORT, () => {
      console.log(`Server running on port ${PORT}`);
    });
  })
  .catch((error) => {
    // Step 3 — if connection fails, log the error and exit the process
    console.error("Failed to connect to database:", error.message);
    process.exit(1);
  });

dotenv.config() must run before importing any file that reads process.env values — like db.js, which reads MONGO_URI. Importing dotenv and calling config() at the very top of server.js guarantees this order.


Placeholder — db.js

We build the real db.js in the next file. For now, a placeholder so server.js runs without errors:

// src/config/db.js
// Full implementation in the next file — mongodb-connection.mdx

const connectDB = async () => {
  console.log("Database connection placeholder — see next file");
};

export default connectDB;

Run It

npm run dev
# expected output
Database connection placeholder see next file
Server running on port 5000

Visit http://localhost:5000/api/health — you should see:

{ "status": "ok", "message": "Server is healthy" }

Summary

  • app.js holds the Express app, middleware, and routes — never calls listen()
  • server.js is the entry point — loads env variables, connects to the database, then starts the server
  • This split makes the app testable and ensures the server never starts before the database is ready
  • cors is configured with credentials: true to support cookie-based auth later
  • A /api/health route is useful for uptime monitoring once deployed
  • Next file — MongoDB Connection — builds out the real db.js

On this page