Files
VibeFinance/backend/src/server.js
T

113 lines
5.4 KiB
JavaScript
Raw Normal View History

import express from "express";
import cors from "cors";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import cookieParser from "cookie-parser";
import pinoHttp from "pino-http";
import { existsSync } from "fs";
import { join } from "path";
import { config } from "./config/index.js";
import { logger } from "./logger.js";
import { initDb } from "./db.js";
import authRouter from "./routes/auth.js";
import dataRouter from "./routes/data.js";
import usersRouter from "./routes/users.js";
import healthRouter from "./routes/health.js";
import updateRouter from "./routes/update.js";
import { errorHandler } from "./middleware/errorHandler.js";
export { logger };
const app = express();
// ── Security ──────────────────────────────────────────────────────────────────
// De app serveert nu ook de frontend, dus het CSP-beleid moet passen bij wat
// die pagina laadt: Google Fonts, inline <style> van React en base64-avatars.
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
imgSrc: ["'self'", "data:", "blob:"],
connectSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
// Uit: intern draait de app op http achter een reverse proxy.
upgradeInsecureRequests: null,
},
},
}));
app.use(cors({ origin: config.CORS_ORIGIN, credentials: true }));
app.set("trust proxy", 1);
// ── Rate limiting (alleen op de API — niet op statische bestanden) ────────────
app.use("/api/auth", rateLimit({ windowMs: 15 * 60 * 1000, max: 20, standardHeaders: true, legacyHeaders: false }));
app.use("/api", rateLimit({ windowMs: 15 * 60 * 1000, max: 200, standardHeaders: true, legacyHeaders: false }));
// ── Parsing ───────────────────────────────────────────────────────────────────
app.use(express.json({ limit: "2mb" }));
app.use(cookieParser());
// ── Logging ───────────────────────────────────────────────────────────────────
if (config.NODE_ENV !== "test") {
app.use(pinoHttp({ logger }));
}
// ── API-routes ────────────────────────────────────────────────────────────────
app.use("/api/health", healthRouter);
app.use("/api/update-check", updateRouter);
app.use("/api/auth", authRouter);
app.use("/api/data", dataRouter);
app.use("/api/users", usersRouter);
// ── 404 voor overige /api/* ───────────────────────────────────────────────────
// Zonder pad-patroon met wildcard: Express 5 vereist benoemde wildcards
// (/api/*splat), en een prefix-mount doet hier precies hetzelfde.
app.use("/api", (_req, res) => res.status(404).json({ error: "Endpoint niet gevonden." }));
// ── Frontend (Vite build) ─────────────────────────────────────────────────────
const indexHtml = join(config.STATIC_DIR, "index.html");
export const servesFrontend = existsSync(indexHtml);
if (servesFrontend) {
// Assets hebben een hash in de naam en mogen lang gecached worden;
// index.html juist nooit, anders blijft een oude build hangen.
app.use(express.static(config.STATIC_DIR, {
index: false,
setHeaders(res, path) {
res.setHeader("Cache-Control", path.endsWith(".html")
? "no-store, no-cache, must-revalidate"
: "public, max-age=31536000, immutable");
},
}));
// SPA-fallback: alle overige GET-routes naar index.html. Pathloze middleware
// in plaats van app.get("*") — dat patroon is in Express 5 ongeldig.
app.use((req, res, next) => {
if (req.method !== "GET" && req.method !== "HEAD") return next();
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
res.sendFile(indexHtml);
});
} else {
logger.warn({ dir: config.STATIC_DIR }, "Geen frontend-build gevonden — alleen de API is beschikbaar");
}
// ── Error handler ─────────────────────────────────────────────────────────────
app.use(errorHandler);
// ── Start ─────────────────────────────────────────────────────────────────────
if (config.NODE_ENV !== "test") {
await initDb();
app.listen(config.PORT, () => {
logger.info(
`VibeFinance gestart op http://localhost:${config.PORT} [${config.NODE_ENV}]` +
(servesFrontend ? " — API + frontend" : " — alleen API")
);
});
}
export default app;