WeCodeAcademy

Step 4: Create links and redirect

Now the server. POST /links validates, creates a unique id and stores { url, clicks } in a Map. GET /<id> looks the id up and answers 302 Found with a Location header: the browser then goes there by itself. The demo makes four requests: a good create, a visit (not following the redirect, so we can look at it), a bad URL, and an unknown id.

ExampleNode.js
const http = require("http");
const crypto = require("crypto");

const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const links = new Map(); // id -> { url, clicks, created }


function randomId(length = 6) {
  const bytes = crypto.randomBytes(length);
  return [...bytes].map((b) => ALPHABET[b % ALPHABET.length]).join("");
}

function json(res, status, data, headers = {}) {
  res.writeHead(status, { "Content-Type": "application/json", ...headers });
  res.end(JSON.stringify(data));
}

function readJson(req) {
  return new Promise((resolve) => {
    let raw = "";
    req.on("data", (c) => (raw += c));
    req.on("end", () => {
      try { resolve(raw ? JSON.parse(raw) : {}); } catch { resolve(null); }
    });
  });
}

function parseTarget(input) {
  if (typeof input !== "string" || input.length > 2000) return { error: "url must be a string of at most 2000 characters" };
  let url;
  try { url = new URL(input); } catch { return { error: "not a valid URL" }; }
  if (url.protocol !== "http:" && url.protocol !== "https:") return { error: "only http and https links are allowed" };
  return { url: url.toString() };
}

async function handle(req, res) {
  if (req.method === "POST" && req.url === "/links") {
    const body = await readJson(req);
    if (body === null) return json(res, 400, { error: "invalid JSON" });
    const target = parseTarget(body.url);
    if (target.error) return json(res, 400, { error: target.error });
    let id;
    do { id = randomId(); } while (links.has(id));
    links.set(id, { url: target.url, clicks: 0 });
    return json(res, 201, { id, short: `/${id}`, url: target.url });
  }

  const match = req.url.match(/^\/([A-Za-z0-9]{6})$/);
  if (req.method === "GET" && match) {
    const link = links.get(match[1]);
    if (!link) return json(res, 404, { error: "unknown short link" });
    res.writeHead(302, { Location: link.url });   // 302: temporary redirect
    return res.end();
  }
  json(res, 404, { error: "Not found" });
}
const server = http.createServer(handle);
server.listen(0, async () => {
  const base = `http://localhost:${server.address().port}`;
  const created = await fetch(base + "/links", { method: "POST", body: JSON.stringify({ url: "https://example.com/some/long/path?utm=1" }) });
  const data = await created.json();
  console.log("create ->", created.status, data.short.length, data.url);

  // manual: do not follow the redirect, just look at it
  const hop = await fetch(base + data.short, { redirect: "manual" });
  console.log("visit  ->", hop.status, hop.headers.get("location"));

  const bad = await fetch(base + "/links", { method: "POST", body: JSON.stringify({ url: "javascript:alert(1)" }) });
  console.log("bad    ->", bad.status, await bad.text());
  const missing = await fetch(base + "/zzzzzz", { redirect: "manual" });
  console.log("unknown->", missing.status);
  server.close();
});
Output
create -> 201 7 https://example.com/some/long/path?utm=1
visit  -> 302 https://example.com/some/long/path?utm=1
bad    -> 400 {"error":"only http and https links are allowed"}
unknown-> 404

The short link length is 7 (a slash plus 6 characters). A 302 means "temporary", so browsers ask again each time and every visit can be counted; a 301 would let browsers cache it and skip your server.

Quick check

Why use 302 instead of 301?

Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.

Tip: use the left and right arrow keys to move between lessons.