Bugfix: NestJS ThemesController route ordering: PUT /themes/active w...

Documentation for Sessions

🐛 Bugfix: NestJS ThemesController route ordering: PUT /themes/active w...

Date: 2026-02-25
Time: 20:18:37 UTC
Session: gspace1-141.10
Agent: backend
Confidence: 97%
Status: ✅ Documented


📋 Decision Context

Question:

NestJS ThemesController route ordering: PUT /themes/active was shadowed by PUT /themes/:themeId — how should routes be ordered?

Task: Fix NestJS route ordering for theme active endpoint


🎯 Decision Made

Chosen:

Move @Put('active') before @Put(':themeId') in the controller class

Options Considered:

  1. Move @Put('active') before @Put(':themeId') in the controller class
  2. Use a dedicated /themes-settings/active endpoint to avoid parametric collision
  3. Add a regex constraint to :themeId to exclude 'active'
  4. Keep current order and use middleware to reroute

💭 Reasoning

ROOT CAUSE: NestJS matches routes in DECLARATION ORDER within a controller class. @Put(':themeId') was declared at line 172, @Put('active') at line 181. When the frontend called PUT /themes/active with body { themeId: 'theme-default-dark' }, NestJS matched :themeId first, binding themeId='active'. This called upsertTheme({ id: 'active' }) which created a bogus document with _id='active' in the themes collection instead of setting the active theme ID in theme_settings. The fix reorders so static routes (@Put('active'), @Delete()) are declared BEFORE parametric routes (@Put(':themeId'), @Delete(':themeId')). Same principle applied to DELETE routes. WHY SIMPLEST FIX IS BEST: NestJS documentation explicitly states that specific routes must precede parametric routes. Adding regex constraints or separate endpoints adds unnecessary complexity. This is a well-known NestJS convention. WHAT WOULD MAKE THIS WRONG: Only if NestJS changed its routing algorithm to match specificity over declaration order (unlikely, as Express/Fastify routers are order-based). EARLY WARNING: Any NestJS controller with both @Method('static-path') and @Method(':param') at the same depth is a candidate for this bug.


📁 Files Involved


🔗 Related


Auto-generated by MCP Smart Consolidation