Skip to content

[fix] Router middleware not applied when prefix contains path parameters (v14.0.0 regression) #202

@posgarou

Description

@posgarou

Describe the bug

Node.js version: v22.14.0

OS version: macOS Sequoia 15.5

Description:

Router middleware applied via .use() fails to execute on any routes when the router prefix contains path parameters. This worked correctly in v13.1.1 but is broken in v14.0.0.

Actual behavior

Router middleware executes for static path prefixes (/api/apps), but not for prefixes that include a path parameter (/api/apps/:appId).

Expected behavior

Router middleware applied via .use() should run for all routes in a router, regardless of whether the router prefix includes a path parameter.

Code to reproduce

import Router from "@koa/router";

function authorize() {
  return async (ctx, next) => {
    console.log(`Middleware executed for ${ctx.method} ${ctx.path}`);
    ctx.state.authorized = "AUTHORIZED";
    await next();
  };
}

// This router works (prefix has no parameters)
export const appsRouter = new Router({
  prefix: "/api/apps",
})
  .use(authorize())
  .get("/", async (ctx) => {
    // Middleware runs
    ctx.body = { authorized: ctx.state.authorized };
  })
  .get("/:id", async (ctx) => {
    // Middleware runs
    ctx.body = { authorized: ctx.state.authorized };
  });

// This router fails (prefix contains path parameter)
export const appSettingsRouter = new Router({
  prefix: "/api/apps/:appId/settings",
})
  .use(authorize())
  .get("/", async (ctx) => {
    // Middleware does not run, so `ctx.state.authorized` is undefined
    ctx.body = { authorized: ctx.state.authorized };
  });

Failing Test

  it('executes middleware for routes when prefix contains path parameters', async () => {
    const app = new Koa();
    
    // Test middleware execution with path parameter in prefix
    const appSettingsRouter = new Router({
      prefix: '/api/apps/:appId/settings'
    });
    
    // Track middleware execution
    let middlewareExecuted = false;
    
    appSettingsRouter
      .use((ctx, next) => {
        middlewareExecuted = true;
        ctx.state.authorized = 'AUTHORIZED';
        return next();
      })
      .get('/', (ctx) => {
        ctx.body = { 
          authorized: ctx.state.authorized,
          middlewareRan: middlewareExecuted,
          appId: ctx.params.appId 
        };
      })
      .get('/:settingId', (ctx) => {
        ctx.body = { 
          authorized: ctx.state.authorized,
          middlewareRan: middlewareExecuted,
          appId: ctx.params.appId,
          settingId: ctx.params.settingId
        };
      });
    
    app.use(appSettingsRouter.routes());
    
    // Test first route
    middlewareExecuted = false;
    const res1 = await request(http.createServer(app.callback()))
      .get('/api/apps/123/settings')
      .expect(200);
    
    assert.strictEqual(res1.body.authorized, 'AUTHORIZED', 'Middleware should set authorized state');
    assert.strictEqual(res1.body.middlewareRan, true, 'Middleware should have executed');
    assert.strictEqual(res1.body.appId, '123', 'Should capture appId param');
    
    // Test second route with additional parameter
    middlewareExecuted = false;
    const res2 = await request(http.createServer(app.callback()))
      .get('/api/apps/456/settings/theme')
      .expect(200);
    
    assert.strictEqual(res2.body.authorized, 'AUTHORIZED', 'Middleware should set authorized state');
    assert.strictEqual(res2.body.middlewareRan, true, 'Middleware should have executed');
    assert.strictEqual(res2.body.appId, '456', 'Should capture appId param');
    assert.strictEqual(res2.body.settingId, 'theme', 'Should capture settingId param');
  });

Checklist

  • I have searched through GitHub issues for similar issues.
  • I have completely read through the README and documentation.
  • I have tested my code with the latest version of Node.js and this package and confirmed it is still not working.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions