Migrations
Migration Guides > v4 to v5 - itty-router
v5 is a major step forward in terms of robustness, power, and slimness. Here's a quick rundown of all changes:
Changes in v5
- breaking
router.fetchreplacesrouter.handleto enable cleaner exports. - breaking
createCors()has been replaced with the improvedcors(). - breaking
RouteHandler(type) has been replaced withRequestHandler. - change previous
Routeris now preserved asIttyRouter. - added new
Router(backwards-compatible) adds support for stages. - added new batteries-included
AutoRouteradds default settings toRouter. - change TypeScript support has been improved in all of the routers, allowing router-level generics AND route-level generic overrides in the same router.
1. router.handle is deprecated in favor of router.fetch
At some point in v4, we began to support (at the cost of extra bytes) boththe original router.handle and the newer router.fetch, which matches the default export signature of Cloudflare Workers and Bun. Using router.fetch allows you to simply export the router, allowing its internal .fetch method to match up to what the runtime expects.
To save bytes, we're dropping support for router.handle, preferring router.fetch in all cases.
// PREVIOUSLY
export default {
fetch: router.handle,
}
// NOW
export default {
fetch: router.fetch
}
// or simply
export default router2. CORS: cors() replaces previous createCors()
This was a big one. As some folks have found, the previous CORS solution included a nasty race condition that could affect high-throughput APIs using CORS. The solution required a breaking change anyway, so we ripped off the bandage and rewrote the entire thing. By changing the name as well, we're intentionally drawing attention that things have changed. While this is a non-trivial migration compared to the others, it comes with a series of advantages:
- It now supports the complete options that express.js does, including support for various formats (e.g. string, array of string, RegExp, etc). We have embraced the industry-standard naming (this is a change).
- It correctly handles the previously-identified race condition
- It correctly handles some other edge-cases not previously addressed
- It handles far-more-complex origin cases & reflection than the previous version
- We added all this extra power while shaving off 100 bytes. 🔥🔥🔥
3. The Three Routers
v5 preserves the original Router as IttyRouter, and introduces two advanced routers, Router and AutoRouter. Router is a 100% backwards-compatible swap, with perfect feature-parity with the previous Router.
Here are the key changes:
Added functionality to standard Router
- Added:
beforestage - an array of handlers that processes before route-matching - Added:
catch- a handler that will catch any thrown error. Errors thrown during thefinallystage will naturally not be subject to thefinallyhandlers (preventing it from throwing infinitely). - Added:
finallystage - an array of handlers that processes after route-matching, and after any error thrown during thebeforestage or route-matching.
Example
import { Router, error, json, withParams } from 'itty-router'
const router = Router({
before: [withParams],
catch: error,
finally: [json],
})
router
.get('/params/:id', ({ id }) => `Your id is ${id}.`) // withParams already included
.get('/json', () => [1,2,3]) // auto-formatted as JSON
.get('/throw', (a) => a.b.c) // safely returns a 500
export default router // CF Workers or BunAdded AutoRouter
This is a thin wrapper around Router, with a few default behaviors included, and a couple additional options for fine-tuned control.
withParamsis included by default before thebeforestage, allowing direct access of route params from the request itself.jsonresponse formatter is included by default before thefinallystage, formatting any unformatted responses as JSON.- A default 404 is included for missed routes. This is equivalent to
router.all('*', () => error(404), and may be changed using themissingoption (below). - Added
missingoption to replace the default 404. Example{ missing: error(404, 'Are you sure about that?') }. To prevent any 404, include a() => {}no-op here. - Added
formatoption to replace the default formatter ofjson. To prevent all formatting, include a() => {}no-op here.
Example
import { AutoRouter } from 'itty-router'
const router = AutoRouter()
router
.get('/params/:id', ({ id }) => `Your id is ${id}.`) // withParams already included
.get('/json', () => [1,2,3]) // auto-formatted as JSON
.get('/throw', (a) => a.b.c) // safely returns a 500
export default router // CF Workers or BunBigger Example
import { AutoRouter, error } from 'itty-router'
// upstream middleware to embed a start time
const withBenchmarking = (request) => {
request.start = Date.now()
}
// downstream handler to log it all out
const logger = (res, req) => {
console.log(res.status, req.url, Date.now() - req.start, 'ms')
}
// now let's create the router
const router = AutoRouter({
port: 3001,
before: [withBenchmarking],
missing: () => error(404, 'Custom 404 message.'),
finally: [logger],
})
router.get('/', () => 'Success!')
export default router // Bun server on port 30014. TypeScript Changes
We've kept things mostly in place from the last round on this, with some improvements, and one minor (technically breaking) change.
Improved router/route generics
We've added the ability to include router-level generics AND route-level generics in the same router. Before, this was not possible, forcing users to pick one or the other.
import { IRequestStrict, Router } from 'itty-router'
type FooRequest = {
foo: string
} & IRequestStrict
type BarRequest = {
bar: string
} & IRequestStrict
const router = Router<FooRequest>() // router-level generic
router
.get('/', (request) => {
request.foo // foo is valid
request.bar // but bar is not
})
// use a route-level generic to override the router-level one
.get<BarRequest>('/', (request) => { // route-level override
request.foo // now foo is not valid
request.bar // but bar is
})Previous RouteHandler becomes RequestHandler breaking
Technically, no one should be affected by this, as RouteHandler was not a documented/advertised type (but rather used internally). Nevertheless, as it was an exported type, we're drawing attention to the change.
To make things more explicit, we've renamed RouteHandler to the more correct RequestHandler. While originally only used in route-definitions, this type is actually the foundation of all handlers/middleware, and thus needed a more appropriate name before people started using it externally.
import { RequestHandler } from 'itty-router'
export const withUser: RequestHandler = (request) => {
request.user = 'Kevin'
}