70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
/**
|
|
* @personal-blog/server — EPPP public server application.
|
|
*
|
|
* [E00-S02-T03] minimal serving process: a small HTTP server built on Node's
|
|
* `node:http` (no runtime dependencies yet) that answers the application
|
|
* health endpoint. `GET /health` reports a healthy application — HTTP 200 with
|
|
* `{"status":"ok"}` — so the Compose stack's `app` service stays up and the
|
|
* health endpoint succeeds once the stack is running.
|
|
*
|
|
* The Fastify 5 application shell (and the real HTTP API) lands in a later
|
|
* story; this bootstrap keeps the application health-checkable until then.
|
|
*/
|
|
|
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
|
|
/** Port the server listens on; `PORT` overrides the container default (3000). */
|
|
const PORT = resolvePort(process.env.PORT);
|
|
|
|
/** Health payload — reports a healthy application. */
|
|
const HEALTH_PAYLOAD = JSON.stringify({ status: 'ok' });
|
|
|
|
/** Payload for any route that is not the health endpoint. */
|
|
const NOT_FOUND_PAYLOAD = JSON.stringify({ error: 'not found' });
|
|
|
|
/**
|
|
* Resolves the listen port from `PORT` (default 3000, matching the Dockerfile
|
|
* `EXPOSE 3000` and the compose `:3000` container port). A non-numeric or
|
|
* out-of-range override falls back to the default so a bad `PORT` value cannot
|
|
* crash the process at startup.
|
|
*/
|
|
function resolvePort(raw: string | undefined): number {
|
|
const port = Number(raw ?? 3000);
|
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : 3000;
|
|
}
|
|
|
|
/** Writes a JSON response with an explicit content-length. */
|
|
function sendJson(res: ServerResponse, statusCode: number, body: string): void {
|
|
res.writeHead(statusCode, {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
'Content-Length': Buffer.byteLength(body),
|
|
});
|
|
res.end(body);
|
|
}
|
|
|
|
/**
|
|
* Routes one request. The application only serves the health endpoint at this
|
|
* stage; anything else is a 404 so misconfiguration is loud.
|
|
*/
|
|
function handleRequest(req: IncomingMessage, res: ServerResponse): void {
|
|
if (req.method === 'GET' && (req.url ?? '/') === '/health') {
|
|
sendJson(res, 200, HEALTH_PAYLOAD);
|
|
return;
|
|
}
|
|
sendJson(res, 404, NOT_FOUND_PAYLOAD);
|
|
}
|
|
|
|
const server = createServer(handleRequest);
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`@personal-blog/server listening on http://0.0.0.0:${PORT} (health: GET /health)`);
|
|
});
|
|
|
|
// `docker stop` (Compose down) and Ctrl-C send SIGTERM/SIGINT — close the
|
|
// server and exit cleanly instead of being killed mid-request.
|
|
for (const signal of ['SIGTERM', 'SIGINT'] as const) {
|
|
process.on(signal, () => {
|
|
server.close(() => process.exit(0));
|
|
});
|
|
}
|