itty-router

A ~460 byte router, designed to make beautiful APIs anywhere.

README

itty-router


Itty is arguably the smallest (~460 bytes) feature-rich JavaScript router available, while enabling dead-simple API code.

Designed originally for Cloudflare Workers, itty can be used in browsers, service workers, edge functions, or runtimes like Node, Bun, etc.!

Complete API documentation is available at itty.dev/itty-router, or join our Discord channel to chat with community members for quick help!

Installation


  1. ```
  2. npm install itty-router
  3. ```

Example


  1. ```js
  2. import {
  3.   error,      // creates error responses
  4.   json,       // creates JSON responses
  5.   Router,     // the ~440 byte router itself
  6.   withParams, // middleware: puts params directly on the Request
  7. } from 'itty-router'
  8. import { todos } from './external/todos'

  9. // create a new Router
  10. const router = Router()

  11. router
  12.   // add some middleware upstream on all routes
  13.   .all('*', withParams)

  14.   // GET list of todos
  15.   .get('/todos', () => todos)

  16.   // GET single todo, by ID
  17.   .get(
  18.     '/todos/:id',
  19.     ({ id }) => todos.getById(id) || error(404, 'That todo was not found')
  20.   )

  21.   // 404 for everything else
  22.   .all('*', () => error(404))

  23. // Example: Cloudflare Worker module syntax
  24. export default {
  25.   fetch: (request, ...args) =>
  26.     router
  27.       .handle(request, ...args)
  28.       .then(json)     // send as JSON
  29.       .catch(error),  // catch errors
  30. }
  31. ```

# What's different about itty?
Itty does a few things very differently from other routers.  This allows itty route code to be shorter and more intuitive than most!

1. Simpler handler/middleware flow.

In itty, you simply return (anything) to exit the flow.  If any handler ever returns a thing, that's what the router.handle returns.  If it doesn't, it's considered middleware, and the next handler is called.

That's it!

  1. ```ts
  2. // not middleware: any handler that returns (anything at all)
  3. (request) => [1, 4, 5, 1]

  4. // middleware: simply doesn't return
  5. const withUser = (request) => {
  6.   request.user = 'Halsey'
  7. }

  8. // a middleware that *might* return
  9. const onlyHalsey = (request) => {
  10.   if (request.user !== 'Halsey') {
  11.     return error(403, 'Only Halsey is allowed to see this!')
  12.   }
  13. }

  14. // uses middleware, then returns something
  15. route.get('/secure', withUser, onlyHalsey,
  16.   ({ user }) => `Hey, ${user} - welcome back!`
  17. )
  18. ```

2. You don't have to build a response in each route handler.

We've been stuck in this pattern for over a decade.  Almost every router still expects you to build and return a Response... in every single route.

We think you should be able to do that once, at the end. In most modern APIs for instance, we're serving JSON in the majority of our routes.  So why handle that more than once?
  1. ```ts
  2. router
  3.   // we can still do it the manual way
  4.   .get('/traditional', (request) => json([1, 2, 3]))

  5.   // or defer to later
  6.   .get('/easy-mode', (request) => [1, 2, 3])

  7. // later, when handling a request
  8. router
  9.   .handle(request)
  10.   .then(json) // we can turn any non-Response into valid JSON.
  11. ```

3. It's all Promises.

itty awaits every handler, looking for a return value.  If it gets one, it breaks the flow and returns the value.  If it doesn't, it continues processing handlers/routes until it does.  This means that every handler can either be synchronous or async - it's all the same.

When paired with the fact that we can simply return raw data and transform it later, this is AWESOME for working with async APIs, database layers, etc.  We don't need to transform anything at the route, we can simply return the Promise (to data) itself!

Check this out:
  1. ```ts
  2. import { myDatabase } from './somewhere'

  3. router
  4.   // assumes getItems() returns a Promise to some data
  5.   .get('/items', () => myDatabase.getItems())

  6. // later, when handling a request
  7. router
  8.   .handle(request)
  9.   .then(json) // we can turn any non-Response into valid JSON.
  10. ```

4. Only one required argument.  The rest is up to you.

itty only requires one argument - a Request-like object with the following shape: { url, method } (usually a native Request).  Because itty is not opinionated about Response creation, there is not "response" argument built in.  Every other argument you pass toroute.handle is given to each handler, in the same order.  

This makes itty one of the most platform-agnostic routers, period, as it's able to match up to any platform's signature.


Here's an example using Cloudflare Worker arguments:
  1. ```ts
  2. router
  3.   .get('/my-route', (request, environment, context) => {
  4.     // we can access anything here that was passed to `router.handle`.
  5.   })

  6. // Cloudflare gives us 3 arguments: request, environment, and context.
  7. // Passing them to `route.handle` gives every route handler (above) access to each.  
  8. export default {
  9.   fetch: (request, env, ctx) => router
  10.                                   .handle(request, env, ctx)
  11.                                   .then(json)
  12.                                   .catch(error)
  13. }
  14. ```

Join the Discussion!


Have a question? Suggestion? Complaint? Want to send a gift basket?

Join us on Discord!

Testing and Contributing


1. Fork repo
1. Install dev dependencies via yarn
1. Start test runner/dev mode yarn dev
1. Add your code and tests if needed - do NOT remove/alter existing tests
1. Commit files
1. Submit PR (and fill out the template)
1. I'll add you to the credits! :)

Special Thanks: 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


- @mvasigh - proxy hack wizard behind itty, coding partner in crime, maker of the entire doc site, etc, etc.
- @hunterloftis - router.handle() method now accepts extra arguments and passed them to route functions
- @SupremeTechnopriest - improved TypeScript support and documentation! :D

Code Golfing


- @taralx - router internal code-golfing refactor for performance and character savings
- @DrLoopFall - v4.x re-minification

Fixes & Build


- @taralx - QOL fixes for contributing (dev dep fix and test file consistency) <3
- @technoyes - three kind-of-a-big-deal errors fixed. Imagine the look on my face... thanks man!! :)
- @roojay520 - TS interface fixes
- @jahands - v4.x TS fixes

Documentation


  @ddarkr,