Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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')); // 2

In 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'); // false

Always compare the pathname obtained with new URL(), and read parameters from searchParams.

Thanks for reading!

Donate to the site — we work hard for you.