Where the hell is a server in an express app!?

If you are a go to javascript person then most probably at some point you have created a server using our oh so popular ExpressJS framework. If yes, and you have ever wondered how an ExpressJS work internally!?… Then welcome to this blog where we will be looking into how ExpressJS (a nodeJS framework) actually creates a server, handles and resolve routes.

Let’s start with a basic NodeJS server to understand it from ground up!!

import { createServer } from "http";

const server = createServer((req, res) => {
  console.log("Received a request!");

  if (req.url === "/") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Welcome to the Home Page!");
  } else if (req.url === "/about") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("This is about page");
  }
});

server.listen(3000, () => {
  console.log("Server is running on port 3000...");
});

🧐 What’s Happening Here?

  1. createServer() creates an HTTP server that listens for incoming requests.

  2. Inside createServer(), we define a request handler (req, res), which processes HTTP requests and sends responses.

  3. listen(3000) starts the server and makes it listen for requests on port 3000.

Think of it Like a Restaurant

  • Defining routes (server.createServer()) is like setting up tables and a menu.

  • Calling listen(PORT) is like opening the restaurant doors so customers can enter.

  • The requests (incoming customers) only start coming once the doors are open.

What Happens When a Request Comes In?

When a request (e.g., GET /about) arrives:

  1. Node.js starts the event loop and waits for incoming requests.

  2. The request reaches http.createServer() where we check req.url.

  3. If there's a match (/about), we send a response (res.end()).

  4. If there's no match, we return 404 Page Not Found.

Let’s look into the expressJS counterpart!!

import express from "express";
const app = express();

app.get("/", (req, res) => res.send("Welcome to Home!"));
app.get("/about", (req, res) => res.send("About Page"));

app.listen(3000, () => {
  console.log("Server running on port 3000...");
});

Now just for a second think where is the actual server here??

If your answer is “app“ is our server then you should keep reading my friend….

`const app = express()` here is just an “Express application“ that contains the logic to resolve and handle the incoming requests.

Where is the actual server thing then?

What’s Happening Here?

  1. express() creates an Express app but does NOT start a server.

  2. app.get("/") defines a route handler inside the app.

  3. app.listen(3000) internally creates an HTTP server using Node.js and starts listening.

Now let’s understand this “app.listen“ thing written within an express server through how it actually looks and work behind the scene through codes.

Under the Hood:

  • Express still uses http.createServer() internally.

  • app.listen(3000) is just a wrapper around server.listen(3000).

import { createServer } from "http";
import express from "express";

const app = express();
const server = createServer(app); // express app becomes the request handler!

server.listen(3000, () => {
  console.log("Server running on port 3000...");
});
  • Express collects routes but doesn’t start listening immediately.

  • createServer(app) converts Express into a request handler for a real Node.js server.

  • The server only starts handling requests when listen(3000) is called.

Now let’s see how we can build something like an express application on our own using NodeJS.

Now that we understand Express is just an abstraction over Node.js's HTTP module, let’s build a mini Express-like framework from scratch.

import { createServer } from "http";

// creating a class with all the necessary methods and variables
class SimpleExpress {
  private routes: Record<string, Function> = {};

  get(path: string, handler: Function) {
    this.routes[path] = handler;
  }

  // leveraging the "createServer" method from 'http' module of nodejs 
  // to start the server and handle the requests.
  listen(port: number, callback: Function) {
    const server = createServer((req, res) => {
      const handler = this.routes[req.url || ""] || (() => {
        res.writeHead(404);
        res.end("Not Found");
      });
      handler(req, res);
    });

    server.listen(port, callback);
  }
}

// creating an instance of the SimpleExpress class
const app = new SimpleExpress();

// calling the methods within the SimpleExpress class
app.get("/", (req: any, res: any) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello from our mini Express!");
});

app.listen(3000, () => {
  console.log("Custom Express server running on port 3000...");
});

How Does This Work??

  • We created a SimpleExpress class that stores routes in an object.

  • get() registers a route in the routes object.

  • listen() creates an HTTP server that matches requests

TL;DR – How Express and Node.js Work Together

  • Node.js provides the http module to create raw servers.

  • Express is NOT a server—it’s a framework that uses Node.js’s HTTP module.

  • Routes must be defined before app.listen(), because Express registers them before starting the server.

  • You can build a basic Express-like framework in pure Node.js!

1
Subscribe to my newsletter

Read articles from Manish Kumar Gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Manish Kumar Gupta
Manish Kumar Gupta

{ "about" : "Just an engineer ⚙️", "stack" : "T3 Stack || MERN || AWS || Docker" }