It's an itty bitty router, designed for Express.js-like routing within Cloudflare Workers (or anywhere else). Like... it's super tiny (~450 bytes), with zero dependencies. For reals.
Installation
npm install itty-router
Simple Example
import { Router } from 'itty-router'
const router = Router()
router
.get('/todos', () => new Response('Todos Index!'))
.get('/todos/:id', request => new Response(`Todo #${request.params.id}`))
.post('/todos', async request => {
const content = await request.json()
return new Response('Creating a new Todo with following payload: ' + JSON.stringify(content))
})
.all('*', () => new Response('Not Found.', { status: 404 }))
addEventListener('fetch', event =>
event.respondWith(router.handle(event.request))
)
Features
Options API
Router(options = {})
Name | Type(s) | Description | Examples |
---|
base | string | prefixes all routes with this string | Router({ base: '/api' }) |
Usage
1. Create a Router
import { Router } from 'itty-router'
const router = Router()
2. Register Route(s)
.{methodName}(route:string, handler1:function, handler2:function, ...)
router.get('/todos/:user/:item?', (req) => {
const { params, query, url } = req
const { user, item } = params
console.log({ user, item, query })
})
3. Handle Incoming Request(s)
.handle(request: Request)
Requests should have both a method and full url. The handle
method will then return the first matching route handler that returns something.
router.handle({
method: 'GET',
url: 'https://example.com/todos/jane/13',
})
Examples
Nested Routers with 404 handling
const missingHandler = new Response('That resource was not found.', { status: 404 })
const parentRouter = Router()
const todosRouter = Router({ base: '/todos' })
todosRouter
.get('/', () => new Response('Todos Index'))
.get('/:id', ({ params }) => new Response(`Todo #${params.id}`))
parentRouter
.all('/todos/*', todosRouter.handle)
.all('*', missingHandler)
Middleware
Any handler that does not return will effectively be considered "middleware", continuing to execute future functions/routes until one returns, closing the response.
const withUser = request => {
request.user = { name: 'Mittens', age: 3 }
}
const requireUser = request => {
if (!request.user) {
return new Response('Not Authenticated', { status: 401 })
}
}
const showUser = request => new Response(JSON.stringify(request.user))
router
.get('/pass/user', withUser, requireUser, showUser)
.get('/fail/user', requireUser, showUser)
router.handle({ url: 'https://example.com/pass/user' })
router.handle({ url: 'https://example.com/fail/user' })
Multi-route (Upstream) Middleware
const withUser = request => {
request.user = { name: 'Mittens', age: 3 }
}
router
.get('*', withUser)
.get('/user', request => new Response(`Hello, ${user.name}!`))
router.handle({ url: 'https://example.com/user' })
File format support
router.get('/todos/:id.:format?', request => {
const { id, format = 'csv' } = request.params
return new Response(`Getting todo #${id} in ${format} format.`)
})
Testing & Contributing
- Fork repo
- Run tests (add your own if needed)
yarn dev
- Add your code (tests will re-run in the background)
- Verify tests run once minified
yarn verify
- Commit files (do not manually modify version numbers)
- Submit PR with a detailed description of what you're doing
- I'll add you to the credits! :)
The Entire Code (for more legibility, see src on GitHub)
const Router = (o = {}) =>
new Proxy(o, {
get: (t, k, c) => k === 'handle'
? async (r, ...a) => {
for (let [p, hs] of t.r.filter(i => i[2] === r.method || i[2] === 'ALL')) {
let m, s, u
if (m = (u = new URL(r.url)).pathname.match(p)) {
r.params = m.groups
r.query = Object.fromEntries(u.searchParams.entries())
for (let h of hs) {
if ((s = await h(r, ...a)) !== undefined) return s
}
}
}
}
: (p, ...hs) =>
(t.r = t.r || []).push([
`^${(t.base || '')+p
.replace(/(\/?)\*/g, '($1.*)?')
.replace(/\/$/, '')
.replace(/:(\w+)(\?)?(\.)?/g, '$2(?<$1>[^/$3]+)$2$3')
}\/*$`,
hs,
k.toUpperCase(),
]) && c
})
Special Thanks
This repo goes out to my past and present colleagues at Arundo - who have brought me such inspiration, fun,
and drive over the last couple years. In particular, the absurd brevity of this code is thanks to a
clever [abuse] of Proxy
, courtesy of the brilliant @mvasigh.
This trick allows methods (e.g. "get", "post") to by defined dynamically by the router as they are requested,
drastically reducing boilerplate.
Contributors
These folks are the real heroes, making open source the powerhouse that it is! Help out and get your name added to this list! <3
Core, Concepts, and Codebase
- @technoyes - three kind-of-a-big-deal errors caught at once. Imagine the look on my face... thanks man!! :)
- @hunterloftis - router.handle() method now accepts extra arguments and passed them to route functions
- @roojay520 - TS interface fixes
- @mvasigh - proxy hacks courtesy of this chap
Documentation Fixes