The http Module: A Server With No Framework
Objectives
By the end of this lesson, you should be able to:
- Build a working HTTP server with Node’s built-in
httpmodule - Route requests manually based on
req.urlandreq.method - Send a response with a status code, headers, and a body
💡 Why this matters: Express, starting next module, is a layer on top of exactly this module. Building a server manually first, routing, status codes, headers, response bodies, makes it clear what problem Express actually solves, and why it’s worth using.
⚠️ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests.
A Minimal Server
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Welcome to the homepage');
});
server.listen(3939, () => {
console.log('server listening on port 3939');
});
server listening on port 3939
http.createServer(callback) creates a server, the callback runs once for every incoming request, receiving a request object (req) and a response object (res). server.listen(port, callback) starts the server actually listening for connections on that port. This callback pattern (Module 3’s event loop, and this module’s events) is core to how Node handles I/O, createServer’s callback fires whenever the underlying 'request' event happens.
Routing Manually
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Welcome to the homepage');
} else if (req.url === '/about' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ page: 'about', author: 'Erin' }));
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3939);
Testing this live with real requests:
curl -i http://localhost:3939/
HTTP/1.1 200 OK
Content-Type: text/plain
Welcome to the homepage
curl -i http://localhost:3939/about
HTTP/1.1 200 OK
Content-Type: application/json
{"page":"about","author":"Erin"}
curl -i http://localhost:3939/nowhere
HTTP/1.1 404 Not Found
Content-Type: text/plain
Not Found
req.url is the path requested, req.method is the HTTP method (GET, POST, and so on). There’s no built-in routing, every route is a manual if/else if check against req.url and req.method, and anything not explicitly matched falls through to a 404. res.writeHead(statusCode, headers) sets the status code and headers, res.end(body) sends the body and finishes the response, nothing sends until .end() is called.
Why a Framework Helps
Even this small example shows the pain points: routing is a manual if/else if chain that gets unwieldy fast, every response needs its headers and status code set by hand, and there’s no built-in way to parse a request body, read URL query parameters cleanly, or share logic across routes. Express, starting next module, solves exactly these problems, but everything it does still ultimately runs on top of this same http module.
Try It
- Build a server with three routes,
/,/contact, and/products, each returning different plain text. - Add a route that only responds to
POSTrequests to/submit, returning a different message than aGETto the same path. - Test your server with
curl -i, confirming both the status code and the response body match what you expect for each route, including an unmatched path. - Explain, in your own words, what
res.writeHead()andres.end()each do, and what happens if.end()is never called.
Recap
http.createServer(callback)creates a server, the callback fires on every request,server.listen(port)starts it.- Routing with the raw
httpmodule is manual, checkingreq.urlandreq.methoddirectly. res.writeHead(status, headers)sets the response’s status and headers,res.end(body)sends the body and completes the response.
Next lesson: url, parsing and working with URLs and query parameters.