kwhitley / itty-router

A little router.
MIT License
1.7k stars 77 forks source link

V4.x - itty-router-extras are coming in house! #148

Closed kwhitley closed 1 year ago

kwhitley commented 1 year ago

Proposed/TODO

Types

Let me get this out there... types in itty are HARD. First, it's a Proxy, which are not very type-friendly to begin with. Next, we want ALL the flexibility (to serve all the various audiences), and that's where the problems really begin. We could, for instance, pass generics at a router level, to define Request type, additional args, etc. This works great, and a single set of types can serve an entire router.

But what if you want to override just a particular route? For example, use a UserRequest on a route that has a withUser middleware upstream. This no longer works.

Conversely, you can do the opposite, and allow full, per-route customization. It's more verbose to be sure, but it allows you to define what you want, where you want it - the tradeoff being that you have to define them more often.

Ultimately, this is the path I'm leaning towards. There's nothing more frustrating than fighting types - including boilerplate (as much as I obviously hate that). With that in mind, here are the proposed type changes:

Changes

Usage Example

In the example below, note the lack of manual new Response() or even json(data) creation. By including a single downstream handler at the outermost/root router, we can let routes return Responses, raw data, or even Promises to raw data/Responses (e.g. a request to a database). Downstream, we can wrap everything in a Response if not already formed :)

Update

Rather than include a separate handler (respondWithJSON/respondWithError), I've overloaded the existing function signatures to ignore Requests/Responses. This allows them to be used as global post-route handlers (as well).

import { 
  error,
  json,
  Router,
  withParams,
} from 'itty-router'

// also allows minimalist importing from direct files
// import { Router } from 'itty-router/Router'

// fake data
const todos = [
  { id: '1', message: 'Pet the puppy.' },
  { id: '2', message: 'Pet the kitten.' },
]

const router = Router()

router
   // withParams can now be used globally
  .all('*', withParams)

  // GET list of todos - notice we can just return the data directly
  .get('/todos', () => todos)

  // GET single todo
  .get('/todos/:id', ({ id }) => { 
    const todo = todos.find(t => t.id === id)

    return todo || error(404, 'That todo was not found')
  })

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

export default {
  fetch: (request, env, context) => router
                                      .handle(request, env, context)
                                      .then(json)    // if sent raw data, wrap it in a Response
                                      .catch(error)  // send error Response for all uncaught errors
}
kwhitley commented 1 year ago

Test coverage at the moment... image

cloudflare-pages[bot] commented 1 year ago

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5bc32f6
Status:🚫  Build failed.

View logs

kwhitley commented 1 year ago

So I think by overloading the createResponse() helper (that creates json, error, etc), I can ditch the respondWithJSON and just pass json in at the end. I'm hoping same for responseWithError (in favor of just error).

This would allow the end handler to share the familiar helpers:

// CF ES6 module syntax
export default {
  fetch: (request: IRequest, env: object, context: object) =>
            router
              .handle(request, env, context)
              .then(json)
              .catch(respondWithError) // this could just become .catch(error)
              .then(corsify)
}
hmnd commented 1 year ago

add downstream handler support to existing Response creators to further streamline individual route code

Would be cool if the downstream handler could be provided a copy of the request, so that, for instance, a cleanup function could be run before returning. In my case, I need to do a context.waitUntil() to log out of an API, using an API client that I add on to request.

kwhitley commented 1 year ago

Closed with the release of v4 :)