From b5b5f238394f6e6c4f0bc3ac09d01e7af1784a80 Mon Sep 17 00:00:00 2001 From: rxmox Date: Thu, 19 Mar 2026 16:39:40 -0600 Subject: [PATCH 1/2] Fix MongoDB connection timeout on Vercel serverless Move DB connection middleware before route handlers in app.ts to ensure the connection is verified before any route handler fires. Extract shared connection logic into utils/db.ts with ping-based stale connection detection to handle Vercel freeze/thaw cycles that leave dead TCP sockets despite readyState showing connected. Tune connection options for serverless (bufferCommands: false, smaller pool, heartbeat). --- shatter-backend/api/index.ts | 70 +++------------------------ shatter-backend/src/app.ts | 4 ++ shatter-backend/src/server.ts | 4 +- shatter-backend/src/utils/db.ts | 84 +++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 66 deletions(-) create mode 100644 shatter-backend/src/utils/db.ts diff --git a/shatter-backend/api/index.ts b/shatter-backend/api/index.ts index 655999c..745313b 100644 --- a/shatter-backend/api/index.ts +++ b/shatter-backend/api/index.ts @@ -1,71 +1,13 @@ import 'dotenv/config'; -import mongoose from 'mongoose'; +import { connectDB } from '../src/utils/db'; import app from '../src/app'; const MONGODB_URI = process.env.MONGO_URI; - -let connectionPromise: Promise | null = null; - -async function connectDB() { - if (mongoose.connection.readyState === 1) { - return; - } - - if (mongoose.connection.readyState !== 0) { - await mongoose.disconnect(); - connectionPromise = null; - } - - if (connectionPromise) { - return connectionPromise; - } - - if (!MONGODB_URI) { - throw new Error('MONGO_URI is not set in environment variables'); - } - - connectionPromise = (async () => { - try { - await mongoose.connect(MONGODB_URI, { - maxPoolSize: 10, - minPoolSize: 2, - maxIdleTimeMS: 30000, - serverSelectionTimeoutMS: 5000, - socketTimeoutMS: 45000, - }); - - mongoose.connection.on('error', (err) => { - console.error('MongoDB connection error:', err); - connectionPromise = null; - }); - - mongoose.connection.on('disconnected', () => { - console.log('MongoDB disconnected'); - connectionPromise = null; - }); - - } catch (error) { - console.error('Failed to connect to MongoDB:', error); - connectionPromise = null; - throw error; - } - })(); - - return connectionPromise; +if (!MONGODB_URI) { + throw new Error('MONGO_URI is not set in environment variables'); } -connectDB().catch(console.error); - -app.use(async (req, res, next) => { - try { - await connectDB(); - next(); - } catch (error: any) { - res.status(500).json({ - error: 'Database connection failed', - message: error.message - }); - } -}); +// Eagerly start connection at module load (Vercel cold start) +connectDB(MONGODB_URI).catch(console.error); -export default app; \ No newline at end of file +export default app; diff --git a/shatter-backend/src/app.ts b/shatter-backend/src/app.ts index 7f5f7ea..3c0df13 100644 --- a/shatter-backend/src/app.ts +++ b/shatter-backend/src/app.ts @@ -1,6 +1,7 @@ import express from "express"; import cors from "cors"; +import { ensureConnection } from "./utils/db"; import userRoutes from './routes/user_route'; import authRoutes from './routes/auth_routes'; import eventRoutes from './routes/event_routes'; @@ -40,6 +41,9 @@ app.get("/", (_req, res) => { res.send("Hello"); }); +// Ensure DB connection is alive before handling any API request +app.use("/api", ensureConnection); + app.use('/api/users', userRoutes); app.use('/api/auth', authRoutes); app.use('/api/events', eventRoutes); diff --git a/shatter-backend/src/server.ts b/shatter-backend/src/server.ts index e209977..2c568d5 100644 --- a/shatter-backend/src/server.ts +++ b/shatter-backend/src/server.ts @@ -1,5 +1,5 @@ import "dotenv/config"; -import mongoose from "mongoose"; +import { connectDB } from "./utils/db"; import app from "./app"; const PORT = process.env.PORT ? Number(process.env.PORT) : 4000; @@ -25,7 +25,7 @@ async function start() { if (!MONGODB_URI) { throw new Error("MONGO_URI is not set"); } - await mongoose.connect(MONGODB_URI); + await connectDB(MONGODB_URI); console.log("Successfully connected to MongoDB"); app.listen(PORT, () => { diff --git a/shatter-backend/src/utils/db.ts b/shatter-backend/src/utils/db.ts new file mode 100644 index 0000000..2e5f65e --- /dev/null +++ b/shatter-backend/src/utils/db.ts @@ -0,0 +1,84 @@ +import mongoose from "mongoose"; +import { Request, Response, NextFunction } from "express"; + +let connectionPromise: Promise | null = null; +let listenersRegistered = false; + +function registerListeners(): void { + if (listenersRegistered) return; + listenersRegistered = true; + + mongoose.connection.on("connected", () => console.log("MongoDB: connected")); + mongoose.connection.on("disconnected", () => { + console.log("MongoDB: disconnected"); + connectionPromise = null; + }); + mongoose.connection.on("reconnected", () => console.log("MongoDB: reconnected")); + mongoose.connection.on("error", (err) => { + console.error("MongoDB: error:", err); + connectionPromise = null; + }); +} + +export async function connectDB(uri: string): Promise { + registerListeners(); + + // If connected, verify the connection is actually alive (not stale from serverless freeze) + if (mongoose.connection.readyState === 1) { + try { + await mongoose.connection.db!.admin().ping(); + return; // genuinely alive + } catch { + console.log("MongoDB connection stale, reconnecting..."); + await mongoose.disconnect(); + connectionPromise = null; + } + } + + // If in a transitional state (connecting/disconnecting), reset + if (mongoose.connection.readyState !== 0) { + await mongoose.disconnect(); + connectionPromise = null; + } + + // Reuse in-flight connection attempt + if (connectionPromise) { + return connectionPromise; + } + + connectionPromise = (async () => { + try { + await mongoose.connect(uri, { + bufferCommands: false, + maxPoolSize: 5, + serverSelectionTimeoutMS: 5000, + socketTimeoutMS: 30000, + heartbeatFrequencyMS: 10000, + }); + console.log("MongoDB connected"); + } catch (error) { + console.error("MongoDB connection failed:", error); + connectionPromise = null; + throw error; + } + })(); + + return connectionPromise; +} + +export function ensureConnection(req: Request, res: Response, next: NextFunction): void { + const uri = process.env.MONGO_URI; + if (!uri) { + res.status(500).json({ error: "MONGO_URI is not configured" }); + return; + } + + connectDB(uri) + .then(() => next()) + .catch((error: any) => { + res.status(500).json({ + error: "Database connection failed", + message: error.message, + }); + }); +} From a8a54f4ab786a95ff730ca98d1e00688480d9ac2 Mon Sep 17 00:00:00 2001 From: rxmox Date: Thu, 19 Mar 2026 17:23:49 -0600 Subject: [PATCH 2/2] Fix TypeScript error for req.params.eventId type mismatch Cast req.params.eventId to string in event controller to fix TS2345 where string | string[] was passed to functions expecting string. --- shatter-backend/src/controllers/event_controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shatter-backend/src/controllers/event_controller.ts b/shatter-backend/src/controllers/event_controller.ts index 1d9cf9c..223a6be 100644 --- a/shatter-backend/src/controllers/event_controller.ts +++ b/shatter-backend/src/controllers/event_controller.ts @@ -170,7 +170,7 @@ export async function getEventByJoinCode(req: Request, res: Response) { export async function joinEventAsUser(req: Request, res: Response) { try { const { name, userId } = req.body; - const { eventId } = req.params; + const eventId = req.params.eventId as string; if (!userId || !name || !eventId) return res.status(400).json({ @@ -279,7 +279,7 @@ export async function joinEventAsGuest(req: Request, res: Response) { organization?: string; title?: string; }; - const { eventId } = req.params; + const eventId = req.params.eventId as string; if (!name || !eventId) { return res.status(400).json({ @@ -404,7 +404,7 @@ export async function joinEventAsGuest(req: Request, res: Response) { */ export async function getEventById(req: Request, res: Response) { try { - const { eventId } = req.params; + const eventId = req.params.eventId as string; if (!eventId) { return res @@ -455,7 +455,7 @@ export async function getEventById(req: Request, res: Response) { */ export async function updateEventStatus(req: Request, res: Response) { try { - const { eventId } = req.params; + const eventId = req.params.eventId as string; const { status } = req.body; const validStatuses = ['In Progress', 'Completed'];