Before Express existed, developers built HTTP servers with Node.js’s built-in http module. Express is a framework built directly on top of it. Understanding how the raw http module works โ how requests arrive, how you read the URL and method, how you write a response โ gives you a complete picture of what Express is actually doing on your behalf. In this lesson you will build a working HTTP server from scratch using only Node.js built-ins, see exactly why raw Node.js is cumbersome for an API, and appreciate the specific problems that Express solves.
What the http Module Provides
| Capability | http Module | Express equivalent |
|---|---|---|
| Create a server | http.createServer(callback) |
express() |
| Read request URL | req.url (raw string, e.g. /api/posts?page=2) |
req.path, req.query.page |
| Read HTTP method | req.method ('GET', 'POST'โฆ) |
app.get(), app.post()โฆ |
| Read request body | Must manually collect chunks from a stream | express.json() parses automatically |
| Send response | res.writeHead(200); res.end(JSON.stringify(data)) |
res.status(200).json(data) |
| Route matching | Manual if/else on req.url and req.method |
app.get('/api/posts', handler) |
| Middleware | No concept โ must implement manually | app.use(middleware) |
app.listen(5000) in Express, Express calls http.createServer(app).listen(5000) internally. The Express app object itself is a valid request handler function โ it is designed to be passed directly to http.createServer(). This means Express is purely a JavaScript layer โ there is no C++ magic, no separate process โ just organised JavaScript on top of Node’s http module.'end' event you will never receive the body. Express’s express.json() middleware handles all of this for you โ another reason raw Node.js is not practical for REST APIs.Building a Raw HTTP Server
// server-raw.js โ a complete HTTP server using only built-in Node.js modules
const http = require('http');
const url = require('url');
// In-memory data store (no MongoDB yet)
let posts = [
{ id: 1, title: 'First Post', body: 'Hello MERN!' },
{ id: 2, title: 'Second Post', body: 'Node.js is great' },
];
const server = http.createServer(async (req, res) => {
// Parse the URL to separate the path from query string
const parsed = url.parse(req.url, true);
const pathname = parsed.pathname;
const method = req.method;
// Set CORS and content-type headers on every response
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json');
// โโ Helper: read request body โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function readBody() {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', () => resolve(body));
req.on('error', err => reject(err));
});
}
// โโ Route: GET /api/health โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if (pathname === '/api/health' && method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify({ status: 'ok' }));
return;
}
// โโ Route: GET /api/posts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if (pathname === '/api/posts' && method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify({ success: true, data: posts }));
return;
}
// โโ Route: POST /api/posts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if (pathname === '/api/posts' && method === 'POST') {
const raw = await readBody();
const body = JSON.parse(raw);
const post = { id: posts.length + 1, ...body };
posts.push(post);
res.writeHead(201);
res.end(JSON.stringify({ success: true, data: post }));
return;
}
// โโ 404 fallthrough โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
res.writeHead(404);
res.end(JSON.stringify({ success: false, message: 'Route not found' }));
});
server.listen(5000, () => console.log('Raw HTTP server on http://localhost:5000'));
The Same API in Express โ Side by Side
// server-express.js โ the same API with Express
const express = require('express');
const cors = require('cors');
const app = express();
let posts = [
{ id: 1, title: 'First Post', body: 'Hello MERN!' },
{ id: 2, title: 'Second Post', body: 'Node.js is great' },
];
app.use(cors()); // CORS handled in one line
app.use(express.json()); // body parsing handled in one line
// Routes are clean, declarative, and easy to read
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
app.get('/api/posts', (req, res) => {
res.json({ success: true, data: posts });
});
app.post('/api/posts', (req, res) => {
const post = { id: posts.length + 1, ...req.body }; // body already parsed!
posts.push(post);
res.status(201).json({ success: true, data: post });
});
// 404 fallthrough
app.use((req, res) => res.status(404).json({ message: 'Route not found' }));
app.listen(5000, () => console.log('Express server on http://localhost:5000'));
What Express Removes
| Raw Node.js http | Lines of code | Express equivalent | Lines of code |
|---|---|---|---|
| Manual URL parsing | 3โ4 | Built into route definition | 0 |
| Manual body collection | 7โ10 | app.use(express.json()) |
1 |
| Manual CORS headers | 3โ5 | app.use(cors()) |
1 |
| if/else route matching | 4 per route | app.get('/path', handler) |
1 |
res.writeHead + res.end |
2 per response | res.json(data) |
1 |
Common Mistakes
Mistake 1 โ Not ending the response
โ Wrong โ forgetting to call res.end() or res.json() hangs the request forever:
// Raw http
if (pathname === '/api/posts') {
res.writeHead(200);
// forgot res.end() โ client waits forever, then times out
}
// Express
app.get('/api/posts', (req, res) => {
const posts = getPosts();
// forgot res.json(posts) โ same problem
});
โ Correct โ every code path through a route handler must call a response method.
Mistake 2 โ Not parsing the body in raw http
โ Wrong โ reading req.body in raw Node.js http (it does not exist):
const server = http.createServer((req, res) => {
const data = req.body; // undefined โ body is a stream, not a property
const post = JSON.parse(data); // TypeError: cannot parse undefined
});
โ Correct โ collect the stream manually (or just use Express which does this for you).
Mistake 3 โ Confusing http.createServer with app.listen
โ Wrong assumption โ thinking app.listen() is entirely separate from Node’s http module:
// Common misconception: Express.listen() is something special
// Reality:
app.listen(5000) === http.createServer(app).listen(5000)
// Express's app object IS the request handler passed to http.createServer()
โ Understanding this matters when you add Socket.io โ you need to pass the http server instance to Socket.io, not the Express app, which requires creating the http server explicitly.
Quick Reference
| Task | Raw http | Express |
|---|---|---|
| Create server | http.createServer(fn) |
express() |
| Read URL | url.parse(req.url) |
req.path, req.query |
| Read method | req.method |
app.get/post/put/delete |
| Send JSON | res.writeHead(200); res.end(JSON.stringify(d)) |
res.json(d) |
| Send status | res.writeHead(404) |
res.status(404) |
| Start listening | server.listen(PORT) |
app.listen(PORT) |