Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

itty-router

Package Overview
Dependencies
Maintainers
2
Versions
265
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

itty-router

Tiny, zero-dependency router with route param and query parsing.

  • 2.1.3
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
52K
decreased by-16.4%
Maintainers
2
Weekly downloads
 
Created
Source

Logo

npm package minified + gzipped size Build Status Coverage Status Open Issues

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'

// create a router
const router = Router() // this is a Proxy, not a class

// register some routes
router
  .get('/todos', () => new Response('Todos Index!'))                         // GET index
  .get('/todos/:id', request => new Response(`Todo #${request.params.id}`))  // GET item
  .post('/todos', async request => {                                         // POST new item
    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 }))               // 404 otherwise

// attach the router "handle" to the event handler
addEventListener('fetch', event =>
  event.respondWith(router.handle(event.request))
)

Features

  • Tiny (~450 bytes) with zero dependencies
  • Full sync/async support. Use it when you need it!
  • Route params, with optional param support (e.g. /api/:collection/:id?)
  • Format support (e.g. /api/items.:format) for .csv, .json, etc. within same route
  • Query parsing (e.g. ?page=3&foo=bar will add a request.query object with keys page and foo)
  • Wildcard support for nesting, global middleware, etc. (e.g. /api/*)
  • Middleware support. Any number of sync/async handlers may be passed to a route.
  • Nestable. Supports nesting routers for API branching.
  • Supports any method type (e.g. router.puppy('/:name', handler) would work)
  • Route match to multiple methods using the "all" channel
  • Define base path per router to prefix all routes (useful for nested routers)
  • Extendable. Use itty as the tiny, zero-dependency internal router to more feature-rich/elaborate routers.
  • Chainable route declarations (why not?)
  • Readable internal code (yeah right...)

Options API

Router(options = {})
NameType(s)DescriptionExamples
basestringprefixes all routes with this stringRouter({ base: '/api' })

Usage

1. Create a Router

import { Router } from 'itty-router'

const router = Router() // no "new", as this is not a real ES6 class/constructor!

2. Register Route(s)

.{methodName}(route:string, handler1:function, handler2:function, ...)
// register a route on the "GET" method
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',                              // optional, default = 'GET'
  url: 'https://example.com/todos/jane/13',   // required
})

// Example outputs (using route handler from step #2 above):
// GET /todos/jane/13             --> { user: 'jane', item: '13', query: {} }
// GET /todos/jane                --> { user: 'jane', query: {} }
// GET /todos/jane?limit=2&page=1 --> { user: 'jane', query: { limit: '2', page: '2' } }

Examples

Nested Routers with 404 handling

  // lets save a missing handler
  const missingHandler = new Response('That resource was not found.', { status: 404 })

  // create a parent router
  const parentRouter = Router()

  // and a child router
  const todosRouter = Router({ base: '/todos' })

  // with some routes on it...
  todosRouter
    .get('/', () => new Response('Todos Index'))
    .get('/:id', ({ params }) => new Response(`Todo #${params.id}`))

  // then divert ALL requests to /todos/* into the child router
  parentRouter
    .all('/todos/*', todosRouter.handle) // attach child router
    .all('*', missingHandler) // catch any missed routes

  // GET /todos --> Todos Index
  // GET /todos/13 --> Todo #13
  // POST /todos --> missingHandler (caught eventually by parentRouter)
  // GET /foo --> 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.

// withUser modifies original request, but returns nothing
const withUser = request => {
  request.user = { name: 'Mittens', age: 3 }
}

// requireUser optionally returns (early) if user not found on request
const requireUser = request => {
  if (!request.user) {
    return new Response('Not Authenticated', { status: 401 })
  }
}

// showUser returns a response with the user (assumed to exist)
const showUser = request => new Response(JSON.stringify(request.user))

// now let's add some routes
router
  .get('/pass/user', withUser, requireUser, showUser)
  .get('/fail/user', requireUser, showUser)

router.handle({ url: 'https://example.com/pass/user' })
// withUser injects user, allowing requireUser to not return/continue
// STATUS 200: { name: 'Mittens', age: 3 }

router.handle({ url: 'https://example.com/fail/user' })
// requireUser returns early because req.user doesn't exist
// STATUS 401: Not Authenticated

Multi-route (Upstream) Middleware

// middleware that injects a user, but doesn't return
const withUser = request => {
  request.user = { name: 'Mittens', age: 3 }
}

router
  .get('*', withUser) // embeds user before all other matching routes
  .get('/user', request => new Response(`Hello, ${user.name}!`))

router.handle({ url: 'https://example.com/user' })
// STATUS 200: Hello, Mittens!

File format support

// GET item with (optional) format
router.get('/todos/:id.:format?', request => {
  const { id, format = 'csv' } = request.params

  return new Response(`Getting todo #${id} in ${format} format.`)
})

Testing & Contributing

  1. Fork repo
  2. Run tests (add your own if needed) yarn dev
  3. Add your code (tests will re-run in the background)
  4. Verify tests run once minified yarn verify
  5. Commit files (do not manually modify version numbers)
  6. Submit PR with a detailed description of what you're doing
  7. 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

Keywords

FAQs

Package last updated on 03 Mar 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc