Proxy foundations
Run two backend services
Start two tiny HTTP servers on different loopback ports and make their identity visible in every response.
10 minute lesson
A reverse proxy needs an upstream application. Something has to sit behind the proxy and answer requests. In this course we use two small backends, because two is the minimum number that makes routing and load balancing visible without a framework.
The important design decision is identity. Every lesson that follows will ask a version of the same question: which backend handled this request? So each backend must say who it is in every response.
Write one backend, run it twice
Create backend.mjs:
import http from 'node:http'
const port = Number(process.argv[2])
http.createServer((request, response) => {
response.setHeader('Content-Type', 'application/json')
response.end(JSON.stringify({ port, path: request.url }))
}).listen(port, '127.0.0.1')
// node backend.mjs 4001
// node backend.mjs 4002
The port comes from the command line, so one file gives you as many instances as you want. Each response carries the port that produced it and the path the backend actually received. Those two fields are the evidence you’ll rely on for the whole course.
Start both instances in two terminals:
node backend.mjs 4001
node backend.mjs 4002
Verify direct access
Request each backend directly and record its port:
curl http://127.0.0.1:4001/hello
# {"port":4001,"path":"/hello"}
curl http://127.0.0.1:4002/hello
# {"port":4002,"path":"/hello"}
Direct loopback access is the baseline before adding the proxy. Later, when a request travels through Caddy, you’ll compare what the client sent with what the backend reports here.
If curl hangs or refuses the connection, the usual cause is a typo in the port argument. node backend.mjs without a port makes port become NaN, and the server never binds. Check the terminal running the backend for an error.
One deliberate choice in the code: the servers listen on 127.0.0.1, not on all interfaces. Do not bind the practice backends publicly. The proxy should be their intended entry point, and a later lesson checks that nothing can reach them any other way.
Lesson completed