Routing and rewrites
Rewrite an upstream path
Remove a public prefix before proxying and confirm what the backend receives.
10 minute lesson
Public URLs and backend URLs don’t always agree. Clients call /api/users, but the backend was written to serve /users — it doesn’t know it lives behind an /api prefix. A rewrite changes the URI used by later handlers, so the proxy can translate between the two contracts.
Strip the prefix
In the path-routing lesson, handle /api/* forwarded the full path. Swap it for handle_path:
handle_path /api/* {
reverse_proxy 127.0.0.1:4001
}
handle_path behaves exactly like handle with one addition: it strips the matched prefix from the path before running its handlers.
Reload and verify with the lab backend, which echoes the path it receives:
curl http://127.0.0.1:8080/api/users
# {"port":4001,"path":"/users"}
The client asked for /api/users. The backend saw /users. That one-line change is the difference between “backend must know its public prefix” and “backend is prefix-agnostic”.
Compare handle_path with a plain handle by switching back briefly — the backend reports /api/users again. Keeping both behaviors clear in your head prevents a whole class of 404s where the backend receives a path it never defined.
The contract problem
Rewrites are easy to write and easy to get subtly wrong. Make the external and internal contracts explicit so redirects and generated links remain correct.
Here’s the classic failure. The backend at /users issues a redirect to /users/42. The client receives Location: /users/42 — but the public path is /api/users/42. The rewrite happened on the way in, and nothing translated the way out. The client follows the redirect and gets the frontend handler instead of the API.
So test round trips, not just single requests:
curl -i http://127.0.0.1:8080/api/redirect-test
# check the Location: header the client actually receives
If the backend generates absolute paths, either configure it with a base path or keep the public and internal paths identical.
Two more edges worth testing while you’re here: encoded paths and trailing slashes. And be careful that a prefix rewrite never crosses a boundary it shouldn’t. Do not rewrite authentication or tenant boundaries accidentally — a rewrite that strips /tenant-a could let a request land on another tenant’s routes.
Lesson completed