Routing by hand
A route is a rule: "for this method and this address, respond like this". Frameworks describe routes in one line, but with plain node:http you choose them yourself: look at req.method and the path from req.url and decide which code to run.
Parsing the address
req.url holds the path together with parameters: /posts?page=2. The built-in URL class splits it into parts. It needs a base address, so any host is passed as the second argument:
const url = new URL('/posts?page=2', 'http://localhost');
console.log(url.pathname); // /posts
console.log(url.searchParams.get('page')); // 2In the server, req.url is passed instead of the string.
First routes
Rewrite src/server.js: a home page, the post list and a 404 response for everything else. The posts come from the src/posts.js module from the JSON storage lesson:
// src/server.js
import http from 'node:http';
import { loadPosts } from './posts.js';
function sendJson(res, status, data) {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(data));
}
const server = http.createServer(async (req, res) => {
const { pathname } = new URL(req.url, 'http://localhost');
if (req.method === 'GET' && pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Notes by Anna');
} else if (req.method === 'GET' && pathname === '/posts') {
sendJson(res, 200, await loadPosts());
} else {
sendJson(res, 404, { error: 'Not found' });
}
});
server.listen(3000);http://localhost:3000/posts shows the array of posts as JSON, and http://localhost:3000/abc shows an error object with code 404. The handler is declared async so that await works inside.
A parameter in the address
For a post address like /posts/2, the number is extracted with a regular expression. Add a branch before the else block:
// src/server.js
const match = pathname.match(/^\/posts\/(\d+)$/);
if (req.method === 'GET' && match) {
const post = await findPost(Number(match[1]));
if (post) sendJson(res, 200, post);
else sendJson(res, 404, { error: 'Post not found' });
}Remember to import findPost next to loadPosts. The (\d+) group captures the digits; they land in match[1] as a string, so they are turned into a number.
Parameters after the question mark
The page number for the list is taken from searchParams with a default value:
const url = new URL('/posts?page=3', 'http://localhost');
const page = Number(url.searchParams.get('page') ?? 1);
const perPage = 5;
console.log(`Posts ${(page - 1) * perPage + 1} to ${page * perPage}`);It prints "Posts 11 to 15". The needed part of the array is then cut out with slice().
Common mistake
Comparing req.url with an address directly is unreliable: /posts?page=2 and /posts/ are not equal to '/posts', and the visitor gets a 404:
console.log('/posts?page=2' === '/posts'); // falseAlways compare the pathname obtained with new URL(), and read parameters from searchParams.