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.fetch
replacesrouter.handle
to enable cleaner exports. - breaking
createCors()
has been replaced with the improvedcors()
. - breaking
RouteHandler
(type) has been replaced withRequestHandler
. - change previous
Router
is now preserved asIttyRouter
. - added new
Router
(backwards-compatible) adds support for stages. - added new batteries-included
AutoRouter
adds 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.
export default {
fetch: router.handle, // previously
fetch: router.fetch,
}
// or simply
export default router
2. 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. 🔥🔥🔥
import { Router, json, error,
createCors,
cors,
} from 'itty-router'
const router = Router()
const { preflight, corsify } = createCors()
const { preflight, corsify } = cors()
router.get('/', () => 'Success!')
export default {
fetch: (request, ...args) =>
router
.fetch(request, ...args)
.then(json)
.catch(error)
.then(corsify)
.then((req, res) => corsify(req, res))
}
import { AutoRouter, cors } from 'itty-router'
const { preflight, corsify } = cors()
const router = AutoRouter({
before: [preflight], // add preflight to upstream middleware
finally: [corsify], // and corsify to response handlers
})
router.get('/', () => 'Success!')
export default router
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:
before
stage - an array of handlers that processes before route-matching - Added:
catch
- a handler that will catch any thrown error. Errors thrown during thefinally
stage will naturally not be subject to thefinally
handlers (preventing it from throwing infinitely). - Added:
finally
stage - an array of handlers that processes after route-matching, and after any error thrown during thebefore
stage 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 Bun
Added AutoRouter
This is a thin wrapper around Router
, with a few default behaviors included, and a couple additional options for fine-tuned control.
withParams
is included by default before thebefore
stage, allowing direct access of route params from the request itself.json
response formatter is included by default before thefinally
stage, 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 themissing
option (below). - Added
missing
option to replace the default 404. Example{ missing: error(404, 'Are you sure about that?') }
. To prevent any 404, include a() => {}
no-op here. - Added
format
option 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 Bun
Bigger 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 3001
4. 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'
}