Skip to content

Migrate lib from koa-router to @koa/router for Koa 3 support - #17

Open
ch-asimakopoulos wants to merge 1 commit into
masterfrom
chore/migrate-koa-router-to-scoped-koa-router
Open

Migrate lib from koa-router to @koa/router for Koa 3 support#17
ch-asimakopoulos wants to merge 1 commit into
masterfrom
chore/migrate-koa-router-to-scoped-koa-router

Conversation

@ch-asimakopoulos

@ch-asimakopoulos ch-asimakopoulos commented Aug 11, 2026

Copy link
Copy Markdown

Why

koa-router has carried a deprecation notice since v9 — "Please use @koa/router instead" — and its 7.x line predates Koa 3 entirely, declaring no support for it. @koa/router@15.7.0 declares koa: ^2.0.0 || ^3.0.0.

This blocks consumers from upgrading Koa. Concretely: ravasaki is upgrading @workablehr/orka from 4.4.0 to 5.2.2, which moves Koa 2 → 3 transitively. Every /api/v1 route in ravasaki is mounted through apicco, so nothing in that upgrade can land until apicco runs on a Koa-3-compatible router.

What changed

File Change
lib/build.js require('koa-router')require('@koa/router'), plus the notImplemented fix below
lib/package.json koa-router@^7.4.0@koa/router@^15.7.0; engines.node ~8.11.4>=20; added koa peer range
lib/build.test.js Same import swap (backs the toBeInstanceOf assertion)
lib/__snapshots__/build.test.js.snap Regenerated

All six router APIs apicco uses are unchanged between the two packages: new Router(), router.use(), .post(), .get(), .routes(), and .allowedMethods({ throw, notImplemented, methodNotAllowed }). The only production change is the require.

Two extras worth calling out explicitly:

  • engines.node was ~8.11.4 — wrong on its own terms today, and below @koa/router 15's >= 20 floor.
  • A koa peer range is now declared. apicco previously declared no koa peer at all, so npm had no way to warn a consumer that the library only supported Koa 2. That is how this incompatibility stayed invisible until someone tried the upgrade.

Bug fixed along the way

allowedMethods.notImplemented was written as:

notImplemented: () => {
  Boom.notImplemented();   // result discarded — callback returns undefined
},

@koa/router types this as () => Error, so with throw: true it threw undefined. Dropping the braces fixes it. Measured against a live Koa 3 server with a Boom-aware error handler in front:

Request Before After
POST a known action 200 200
GET a POST-only action 405 405
PROPFIND (unimplemented method) 500 501
Accept: text/plain 406 406

This bug is pre-existing and reproduces on koa@2 + koa-router@7 too — it is not fallout from the router swap.

Verification

Beyond the unit suite, I exercised the parts most likely to break on this jump against a real Koa 3 server:

  • Dotted action paths still route. This was the main risk, since @koa/router 15 brings path-to-regexp@8 with its overhauled path syntax. apicco registers only literal paths (/api/v1/conversations.send, /api/v1/discovery) with no :params or wildcards, and dots are literal in v8. POST /api/v1/conversations.send → 200, unknown action → 404.
  • ctx.router and ctx._matchedRoute are still set — which is why three of the four passing behavioural tests needed no edits.
  • ctx.throw(406, 'json only') in the accepts guard still works under Koa 3's tightened ctx.throw(status, error, properties) signature.
  • middleware.router = router still attaches.

npm test in lib/: 9/9 passing. Before regenerating the snapshot it was 8/9, the only failure being the snapshot itself.

Reviewer notes

  • The snapshot diff is unavoidable. lib/build.test.js:55 snapshots the router's private structure, so the class name (RouterRouterImplementation) and every Layer field reshaped by path-to-regexp 8 show up. Worth considering separately: that test guarantees a failure on every future router bump while asserting nothing apicco actually promises — narrowing it to registered paths and methods would make the next upgrade a no-op.
  • lib/package-lock.json inflates this diff. It was migrated from lockfileVersion 1 → 3, since it had not been rebuilt with a modern npm. Happy to drop it from this PR if you would rather regenerate it on your own npm version — note scripts/install.sh uses npm install (not npm ci) and the publish workflow installs nothing, so it is not load-bearing either way.
  • No version bump here — the publish workflow runs npm version itself on master. Suggest minor (→ 1.5.0) when publishing, since this swaps a dependency major and raises the engines floor even though apicco's own API is untouched.

🤖 Generated with Claude Code

koa-router has been deprecated since v9 ("Please use @koa/router instead")
and its 7.x line predates Koa 3, declaring no support for it. @koa/router
15.7.0 supports koa ^2.0.0 || ^3.0.0, so this unblocks consumers upgrading
to Koa 3 while remaining compatible with Koa 2.

All six router APIs apicco uses are unchanged between the two packages:
new Router(), router.use(), .post(), .get(), .routes() and
.allowedMethods({ throw, notImplemented, methodNotAllowed }). The only
production change is the require in build.js.

Also in this commit:

- engines.node was "~8.11.4", which is wrong on its own terms and below
  @koa/router 15's ">= 20" floor. Set to ">=20".
- Added a koa peer range ("^2.0.0 || ^3.0.0"). apicco declared no koa peer
  at all, so npm had no way to warn a consumer that the library only
  supported Koa 2 — which is how this incompatibility stayed invisible.
- Fixed notImplemented: it called Boom.notImplemented() and discarded the
  result, so the callback returned undefined and allowedMethods({throw:true})
  threw undefined instead of a 501. Verified: an unimplemented method now
  returns 501 rather than an opaque 500.

Verified against a live Koa 3 server: dotted action paths
(/api/v1/conversations.send) still route correctly under path-to-regexp 8,
ctx.router and ctx._matchedRoute are still set, and ctx.throw(406, ...) in
the accepts guard still works. Method-mismatch behaviour is unchanged (405
via a Boom-aware error handler).

The build.test.js snapshot captures the router's private structure, so it
was regenerated: the class is now RouterImplementation and the Layer fields
reshaped under path-to-regexp 8. Test suite: 9/9 passing.

Note on diff size: lib/package-lock.json was regenerated and migrated from
lockfileVersion 1 to 3, since it had not been rebuilt with a modern npm.
Happy to drop it from this PR if you would rather regenerate it yourselves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants