brackt/server.ts
Chris Parsons 35fe84a1dd
Update claude.md to push more tests. (#84)
* Update claude.md to push more tests.

* Archiving old plans.

* fix: run DB migrations programmatically on server startup

Replace unreliable drizzle-kit CLI migration with drizzle-orm's built-in
migrator running in the server process before accepting connections. This
ensures migrations are always applied on deploy and fails fast if they error.

- Add runMigrations() to server.ts using drizzle-orm/postgres-js/migrator
- Skip migrations in development (handled manually via db:migrate)
- Add DATABASE_URL guard with a clear error message
- Remove start:production script (now identical to start)
- Update Dockerfile CMD to use npm run start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 22:31:04 -08:00

107 lines
3.3 KiB
TypeScript

import compression from "compression";
import express from "express";
import type { Express, Request, Response, NextFunction } from "express";
import morgan from "morgan";
import { createServer } from "http";
import type { ViteDevServer } from "vite";
import path from "path";
import { fileURLToPath } from "url";
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
// ESM module resolution helpers
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Configuration
// Resolve paths relative to project root (one level up from dist/ in production, or current dir in dev)
const PROJECT_ROOT = path.resolve(__dirname, "..");
const BUILD_PATH = path.join(PROJECT_ROOT, "build/server/index.js");
const CLIENT_BUILD_PATH = path.join(PROJECT_ROOT, "build/client");
const CLIENT_ASSETS_PATH = path.join(PROJECT_ROOT, "build/client/assets");
const DEVELOPMENT = process.env.NODE_ENV === "development";
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
async function runMigrations(): Promise<void> {
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL is required");
}
const client = postgres(process.env.DATABASE_URL);
try {
const db = drizzle(client);
await migrate(db, { migrationsFolder: path.join(PROJECT_ROOT, "drizzle") });
console.log("Migrations complete");
} finally {
await client.end();
}
}
async function createAppServer(): Promise<void> {
const app: Express = express();
app.use(compression());
app.disable("x-powered-by");
if (DEVELOPMENT) {
console.log("Starting development server");
// Dynamic import Vite only in development
const { createServer: createViteServer } = await import("vite");
const viteDevServer: ViteDevServer = await createViteServer({
server: { middlewareMode: true },
});
app.use(viteDevServer.middlewares);
app.use(async (req: Request, res: Response, next: NextFunction) => {
try {
const source = await viteDevServer.ssrLoadModule("./server/app.ts");
return await source.app(req, res, next);
} catch (error) {
if (error instanceof Error) {
viteDevServer.ssrFixStacktrace(error);
}
next(error);
}
});
} else {
console.log("Starting production server");
app.use(
"/assets",
express.static(CLIENT_ASSETS_PATH, {
immutable: true,
maxAge: "1y"
})
);
app.use(morgan("tiny"));
app.use(express.static(CLIENT_BUILD_PATH, { maxAge: "1h" }));
// Import the built React Router app
const { app: productionApp } = await import(BUILD_PATH);
app.use(productionApp);
}
// Create HTTP server
const httpServer = createServer(app);
// Initialize Socket.IO
const { initializeSocketIO } = await import("./server/socket");
initializeSocketIO(httpServer);
// Start server
httpServer.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
}
// Start the server with error handling
(DEVELOPMENT ? Promise.resolve() : runMigrations())
.then(createAppServer)
.catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});