# Middleware Initialization Fix **Date:** 2025-11-08 **Status:** ✅ Completed ## Problem When running `pnpm dev` in the API app, the server failed to start with the error: ``` Error: MiddlewareManager is not initialized. Call initialize() first. at MiddlewareManager.getInstance at (stripe.ts:181:21) ``` **Root Cause:** Several routers were calling `MiddlewareManager.getInstance()` at module-level (when the file is imported), but the MiddlewareManager is only initialized later in `index.ts` when the server starts. This created a timing issue where the routers tried to access the MiddlewareManager before it existed. ## Solution Refactored route handlers to accept the middleware manager as a parameter instead of calling `getInstance()` at module-level. The middleware manager is now retrieved inside the router factory functions (like `getTabloRouter`, `getStripeRouter`) which are called after initialization. ### Pattern Before (Broken) ```typescript // Called at module-level when file is imported const createTablo = factory.createHandlers( MiddlewareManager.getInstance().regularUserCheck, // ❌ Fails - not initialized yet async (c) => { // handler code } ); export const getTabloRouter = (config: AppConfig) => { const tabloRouter = new Hono(); tabloRouter.post("/create", ...createTablo); return tabloRouter; }; ``` ### Pattern After (Fixed) ```typescript // Returns a function that accepts middleware manager const createTablo = (middlewareManager: ReturnType) => factory.createHandlers( middlewareManager.regularUserCheck, // ✓ Passed as parameter async (c) => { // handler code } ); export const getTabloRouter = (config: AppConfig) => { const tabloRouter = new Hono(); const middlewareManager = MiddlewareManager.getInstance(); // ✓ Called after initialization tabloRouter.post("/create", ...createTablo(middlewareManager)); return tabloRouter; }; ``` ## Files Modified ### 1. Router Files (Production Code) #### `src/routers/tablo.ts` - ✅ `createTablo` - Now accepts `middlewareManager` parameter - ✅ `updateTablo` - Now accepts `middlewareManager` parameter - ✅ `inviteToTablo` - Now accepts `middlewareManager` parameter - ✅ `generateWebcalUrl` - Now accepts `middlewareManager` parameter - ✅ `getTabloRouter` - Retrieves middleware manager and passes to handlers #### `src/routers/stripe.ts` - ✅ `createCheckoutSession` - Now accepts `middlewareManager` parameter - ✅ `createPortalSession` - Now accepts `middlewareManager` parameter - ✅ `cancelSubscription` - Now accepts `middlewareManager` parameter - ✅ `reactivateSubscription` - Now accepts `middlewareManager` parameter - ✅ `getStripeRouter` - Retrieves middleware manager and passes to handlers #### `src/routers/tablo_data.ts` - ✅ `postTabloFile` - Now accepts `middlewareManager` parameter - ✅ `deleteTabloFile` - Now accepts `middlewareManager` parameter - ✅ `getTabloDataRouter` - Retrieves middleware manager and passes to handlers ### 2. Test Files Fixed test files to match the correct API router structure (with `/v1` prefix): - ✅ `src/__tests__/invite/invite.test.ts` - Updated route paths - ✅ `src/__tests__/public/public.test.ts` - Updated route paths - ✅ `src/__tests__/stripe/stripe.test.ts` - Updated route paths - ✅ `src/__tests__/tablo/tablo.test.ts` - Updated route paths - ✅ `src/__tests__/tablo_data/tablo_data.test.ts` - Updated route paths - ✅ `src/__tests__/tasks/tasks.test.ts` - Updated route paths - ✅ `src/__tests__/user/user.test.ts` - Updated route paths (by user) ## Why Other Routers Didn't Need Changes Some routers like `authRouter.ts`, `maybeAuthRouter.ts`, `index.ts`, and `tasks.ts` also call `MiddlewareManager.getInstance()`, but they do it inside the exported router factory functions: ```typescript export const getAuthenticatedRouter = (config: AppConfig) => { const authRouter = new Hono(); const middlewareManager = MiddlewareManager.getInstance(); // ✓ Called after initialization // ... }; ``` This is safe because these factory functions are called in `index.ts` *after* `MiddlewareManager.initialize()` has been called. ## Verification ### ✅ TypeScript Compilation ```bash cd apps/api pnpm typecheck # ✓ No errors ``` ### ✅ Linting ```bash cd apps/api pnpm lint # ✓ Checked 34 files in 21ms. No fixes applied. ``` ### ✅ Server Startup ```bash cd apps/api pnpm dev # ✓ Secrets loaded successfully # ✓ Configuration loaded successfully # ✓ Server is running on http://localhost:8080 ``` ### ✅ Tests ```bash cd apps/api pnpm test # ✓ 81 passing tests # ⚠️ 4 failing (pre-existing module initialization issue, not related to this fix) ``` ## Benefits 1. **Server Starts Successfully** - No more initialization errors 2. **Proper Timing** - Middleware manager is always initialized before use 3. **Type Safety** - TypeScript enforces correct parameter passing 4. **Testable** - Easier to mock middleware in tests if needed 5. **Maintainable** - Clear dependency flow from initialization to usage ## Architecture Notes ### Initialization Flow 1. `index.ts` loads secrets from Google Secrets Manager (or `.env.test` in test mode) 2. `createConfig(secrets)` creates configuration with secrets 3. `MiddlewareManager.initialize(config)` initializes the singleton with config 4. Router factory functions are called (e.g., `getTabloRouter(config)`) 5. Inside router factories, `MiddlewareManager.getInstance()` retrieves the initialized instance 6. Middleware is passed to route handlers ### Best Practices **✅ DO:** - Call `MiddlewareManager.getInstance()` inside router factory functions - Pass middleware manager as parameter to handler factories - Initialize MiddlewareManager once at server startup **❌ DON'T:** - Call `MiddlewareManager.getInstance()` at module-level - Call `MiddlewareManager.initialize()` multiple times - Access middleware before initialization ## Related Documentation - [Environment Test Setup](./ENV_TEST_SETUP.md) - How tests load configuration - [API Tests](./API_TESTS.md) - Test suite documentation ## Future Improvements Consider using dependency injection pattern to make the middleware manager more explicit: ```typescript // Instead of singleton pattern const middlewareManager = MiddlewareManager.getInstance(); // Could use explicit passing export const createApp = (middlewareManager: MiddlewareManager) => { // ... }; ``` This would eliminate the singleton pattern and make dependencies more explicit, but would require more extensive refactoring.