hello-svc/src/app.ts
nholtware 43f10810d3
All checks were successful
/ checks (push) Successful in 35s
/ deploy (push) Successful in 10s
feat: hot-reloaded runtime config (src/lib/runtime-config.ts), greeting on GET /, GET /admin/runtime-config
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:37:09 -06:00

74 lines
2.1 KiB
TypeScript

import express, { type ErrorRequestHandler } from "express";
import { randomUUID } from "node:crypto";
import { pinoHttp } from "pino-http";
import type { Logger } from "pino";
import type { Config } from "./lib/config.js";
import type { Db } from "./lib/db.js";
import type { Flags } from "./lib/flags.js";
import type { Cache } from "./lib/redis.js";
import {
staticRuntimeConfig,
type RuntimeConfig,
} from "./lib/runtime-config.js";
import { adminRouter } from "./routes/admin.js";
import { healthRouter } from "./routes/health.js";
import { logDemoRouter } from "./routes/log-demo.js";
import { rootRouter } from "./routes/root.js";
export interface Deps {
config: Config;
logger: Logger;
db?: Db;
cache?: Cache;
flags?: Flags;
runtimeConfig?: RuntimeConfig;
}
export function createApp({
config,
logger,
db,
cache,
flags,
runtimeConfig = staticRuntimeConfig(),
}: Deps) {
const app = express();
app.disable("x-powered-by");
app.use(
pinoHttp({
logger,
genReqId: (req, res) => {
const incoming = req.headers["x-request-id"];
const id =
(Array.isArray(incoming) ? incoming[0] : incoming) ?? randomUUID();
res.setHeader("X-Request-Id", id);
return id;
},
// Keep the logging contract flat and small: req.id/method/url,
// res.statusCode, responseTime.
serializers: {
req: (req) => ({ id: req.id, method: req.method, url: req.url }),
res: (res) => ({ statusCode: res.statusCode }),
},
autoLogging: {
ignore: (req) => req.url === "/healthz" || req.url === "/readyz",
},
}),
);
app.use(healthRouter({ db, cache }));
app.use(rootRouter({ config, db, cache, flags, runtimeConfig }));
app.use(adminRouter({ runtimeConfig }));
app.use(logDemoRouter());
// Express identifies error handlers by arity; keep all four args.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onError: ErrorRequestHandler = (err, req, res, _next) => {
req.log.error({ err }, "unhandled error");
res.status(500).json({ error: "internal error" });
};
app.use(onError);
return app;
}