Skip to main content

Routing and REST Design

intermediate20 min readLesson 118 of 143

URLs name things; methods say what happens. A resource-oriented API that clients can predict.

Resources, not verbs

REST models your API as nouns with methods:

GET    /api/tasks          list (supports ?status=open&limit=20)
POST   /api/tasks          create โ€” 201 + Location: /api/tasks/42
GET    /api/tasks/42       read one โ€” 404 if absent
PUT    /api/tasks/42       replace โ€” full object
PATCH  /api/tasks/42       partial update
DELETE /api/tasks/42       remove โ€” 200 or 204

Compare with the anti-pattern: /api/getTasks, /api/deleteTask?id=42. Verbs-in-URLs duplicate what the method already says and grow unboundedly. Exceptions exist (auth: POST /api/login is fine โ€” login is a process, not a resource).

Path parameters and queries

  • Path identifies the resource: /api/tasks/42.
  • Query shapes the response without changing identity: ?status=open&sort=due&limit=20.

Conventions clients will assume: limit/offset (or cursor) for pagination, sort=-createdAt (minus = descending), filtering by field names.

Design review checklist

  • Plural nouns (/tasks not /task)
  • No verbs in paths (methods carry the action)
  • Status codes truthful (201 on create, 404 on missing, 400 on invalid body)
  • Consistent error shape: { "error": { "message": "...", "field": "due" } } โ€” clients parse one shape
  • Versioning strategy when you must break the contract: /api/v2/... (avoid until forced)

A tiny router, by hand

const routes = [];
function route(method, pattern, handler) {
  // "/tasks/:id" -> regex with a named group
  const keys = [];
  const rx = new RegExp(
    "^" +
      pattern.replace(/:(\w+)/g, (_, k) => {
        keys.push(k);
        return "([^/]+)";
      }) +
      "$",
  );
  routes.push({ method, rx, keys, handler });
}

function match(method, url) {
  for (const r of routes) {
    const m = r.rx.exec(url);
    if (r.method === method && m) {
      const params = Object.fromEntries(r.keys.map((k, i) => [k, decodeURIComponent(m[i + 1])]));
      return { handler: r.handler, params };
    }
  }
  return null;
}

Thirty lines and routing is demystified: patterns become regexes, matches become parameter objects. (Framework routers do exactly this plus optimizations.)

Now practice

REST Resource Design โ€” PracticeJudge API designs in code: name resources, score RESTfulness, and paginate a collection deterministically.3 challenges ยท ยท ~18 min