GithubHelp home page GithubHelp logo

kwhitley / itty-router Goto Github PK

View Code? Open in Web Editor NEW
1.6K 14.0 71.0 1.79 MB

A little router.

License: MIT License

JavaScript 2.16% TypeScript 97.84%
api cloudflare-workers edge itty-router javascript router serverless service-worker middleware cloudflare

itty-router's Introduction


Itty Router

Version Bundle Size Build Status Coverage Downloads Issues Discord Github Follow @ittydev


An ultra-tiny API microrouter, for use when size matters (e.g. Cloudflare Workers).

Features

  • Tiny. Routers from ~450 bytes to a ~970 bytes batteries-included version (~240-500x smaller than Express.js).
  • TypeScript. Powerfully (and flexibly) typed for any environment.
  • Route-parsing & query parsing.
  • Middleware. Use ours or write your own.
  • 100% Test Coverage. Bulletproof for production peace-of-mind.
  • Designed specifically for serverless (but works anywhere).
  • No assumptions. Return anything; pass in anything.
  • Future-proof. HTTP methods not-yet-invented already work with it.

Example

import { AutoRouter } from 'itty-router' // ~1kB

const router = AutoRouter()

router
  .get('/hello/:name', ({ name }) => `Hello, ${name}!`)
  .get('/json', () => [1,2,3])
  .get('/promises', () => Promise.resolve('foo'))

export default router

// that's it ^-^

Need Help?

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

Join the Discussion!

Have a question? Suggestion? Idea? Complaint? Want to send a gift basket? Join us on Discord!

A Special Thanks ❤️

As the community and contributor list has grown (and thus an individualized list here is no longer easily maintainable), I'd like to thank each and every one of you for making itty far greater than its humble origins. The robustness you see today, the careful consideration of every byte spent on features, the API choices, the code-golfing itself... are all thanks to the efforts and feedback from the community. I'd especially like to thank the core contributors and PR-authors, as well as the fantastic folks on the itty Discord group, for their tireless work refining this little beast and answering community questions.

itty-router's People

Contributors

alexrosenfeld10 avatar arunsathiya avatar bspates avatar cherry avatar chrislalos avatar ddarkr avatar dependabot[bot] avatar drloopfall avatar gimenete avatar hunterloftis avatar isaac-mcfadyen avatar jahands avatar janat08 avatar jeevcat avatar justinnoel avatar kclauson avatar koistya avatar kwhitley avatar leaysgur avatar mvasigh avatar poacher2k avatar rodrigoaddor avatar roojay520 avatar smcstewart avatar subash avatar supremetechnopriest avatar taralx avatar tombyrer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

itty-router's Issues

Request interface overriding already defined interfaces

Hi, I'm not sure if this is an issue with itty-router or just how I'm setting things up. But I'm using itty-router in my typescript project. But I can't get VS Code to recognize all the request properties.

In the example below, VS Code will complain about Property 'query' does not exist on type 'Request'.

import { Router } from 'itty-router'

const router = Router()

router.get('/', (request: Request) => {
  console.log('Cf: ', request.cf)
  console.log('Query: ', request.query)
})


addEventListener('fetch', (event: FetchEvent) => {
  event.respondWith(router.handle(event.request))
})

But if I import the request interface from itty-router. It overrides the already defined request types added to the tsconfig from Cloudflare. The below example will complain that Property 'cf' does not exist on type 'Request'..

import { Router, Request } from 'itty-router'

const router = Router()

router.get('/', (request: Request) => {
  console.log('Cf: ', request.cf)
  console.log('Query: ', request.query)
})


addEventListener('fetch', (event: FetchEvent) => {
  event.respondWith(router.handle(event.request))
})

How can I get VS Code to recognize all properties on the request?

route regex matches path but router params are not parsed correctly

I seem to have encountered a bug with the param parsing logic for routes. This is what I have observed:

route regex:

/users/:user_id([0-9]+)/hello

url path:

/users/12345/hello

expected Request.params value:

{
  "user_id": "12345"
}

actual Request.params value:

{
  "user_id": "1234"
}

Based on my testing, it is always missing the last digit

non-optional format syntax fails to match route

  it('"/:id.:format" will only match with .format', async () => {
    const router = Router()
    const handlerWithFormat = jest.fn(req => req.params)
    const handlerWithoutFormat = jest.fn(req => req.params)

    router
      .get('/:id.:format', handlerWithFormat)
      .get('/:id', handlerWithoutFormat)

    await router.handle(buildRequest({ path: '/foobarbaz' }))
    expect(handlerWithoutFormat).toHaveReturnedWith({ id: 'foobarbaz' })

    await router.handle(buildRequest({ path: '/foobarbaz.jpg' }))
    expect(handlerWithFormat).toHaveReturnedWith({ id: 'foobarbaz', format: 'jpg' })
  })

Fails in first test.

FUNDING.yml isn't working

When I clicked on GitHub's "Sponsor" button (in the top right), I got this error message from GitHub:

Screenshot 2021-05-20 150947

I think the reason is that you left the changed values in FUNDING.yml as comments, rather than as values, e.g.

github: # kwhitley

and:

open_collective: # kevinrwhitley

I think you need to remove the hash symbol / pound symbol, i.e. remove the # in front, so that section of the file will then look like so:

github: kwhitley
patreon: # Replace with a single Patreon username
open_collective: kevinrwhitley

I'm not 100% certain that will fix the problem, but it wouldn't hurt to try!

How to handle OPTIONS request?

i try to use itty-router and deploy it to cloudflare workers. but when there is an OPTIONS request from browser it always return 404 because of router.all('*', () => new Response('404, Not Found!', { status: 404 }));

how can we handle options request to always return 200 response?


thank you for this awesome router 👍

Itty Router hangs when sending "Not Allowed" method with body.

We are using itty-router and have set our own routing up to return 405 response on certain methods. We are sending a POST request with a body to a 3rd party service, however when sending the same request with a not allowed method, the request hangs. If we dont attach a body, we receive the 405 response.

More context on how we have set up our routing is as follows:

router
.post('/api/v1/sign-up', middleware1, middleware2, middleware3)
.post(
'',
() => new Response('Not found', { status: 404, statusText: 'Not found' }),
)
.head(
'
',
() =>
new Response('Method not allowed', {
status: 405,
statusText: 'Method not allowed',
}),
)
.get(
'',
() =>
new Response('Method not allowed', {
status: 405,
statusText: 'Method not allowed',
}),
)
.put(
'
',
() =>
new Response('Method not allowed', {
status: 405,
statusText: 'Method not allowed',
}),
) ...

[Question] How to bundle with ES6 Module Syntax

Hi!

Your router is absolutely amazing, thank you so much for taking your time to create it.

I have built an API and I am trying to change my router to use the ES6 Modules syntax.
I am using esbuild to bundle my api and it works fine when using addEventListener, which makes sense.

But I can't get it working from your ES6 example, am i supposed to use the exported handler in some way?

I setup a super simple repo to demo:
https://github.com/TorbjornHoltmon/itty-es6

Query strings should be modeled as a multimap

As indicated by the Wikipedia page Query string:

While there is no definitive standard, most web frameworks allow multiple values to be associated with a single field (e.g. field1=value1&field1=value2&field2=value3).

And indeed, many web frameworks I have used adopt this approach:

Therefore, I suggest itty-router follow the same convention.

Currently, itty-router models query strings as a Obj:

query?: Obj

where Obj is actually a map from some string to some string:

export type Obj = {
[propName: string]: string
}

I suggest introducing MultiObj as the data model of query strings:

export type MultiObj = {
    [propName: string]: string | Array<string>
}

I'd like to help if you @kwhitley are interested in this :)

Deno compatibility

Hello, I loved using itty-router with Cloudflare workers, I'd like to make it work with Deno/Deno Deploy workers too; there is almost no changes to apply as it already works with it (see below)

// import { Router } from 'https://raw.githubusercontent.com/kwhitley/itty-router/master/src/itty-router.js';

import { Router } from './itty-router.js';

const router = Router();
router.get('/todos', () => new Response('Todos Index!'));
router.get('/todos/:id', ({ params }) => new Response(`Todo #${params.id}`));
router.all('*', () => new Response('Not Found.', { status: 404 }));

const server = Deno.listen({ port: 8080 });

for await (const conn of server) {
  const httpConn = Deno.serveHttp(conn);
  for await (const requestEvent of httpConn) {
    requestEvent.respondWith(router.handle(requestEvent.request));
  }
}

To only probem is about the module export, "module" is not defined in the deno runtime as the module system differs a bit, I removed the module.exports = { Router } (last line) and set the first line as follows :

export const Router = ({ base = '', routes = [] } = {}) => ({

It works like a charm as is.
I don't know what this change would imply for the package but it should be ok as webpack can import such modules.

Jest fails with "SyntaxError: The requested module 'itty-router' does not provide an export named 'Router'"

Loving itty-router, thanks!

This one has me stumped… I'm using https://github.com/mrbbot/miniflare-typescript-esbuild-jest, which is working really well with itty-router in miniflare and when deployed to Cloudflare, but a basic test fails with:

SyntaxError: The requested module 'itty-router' does not provide an export named 'Router'

I went way down the rabbit hole trying to understand why (cos stepping through my miniflare + itty-router project works a treat), and I can see node_modules/itty-router/dist/itty-router.min.js getting evaluated…

I understand that this may be an issue with the ts-jest configuration, but through I'd mentioned it any way (and tag it 'question') in case the solution is self-evident to someone else…

Here's a minimal repro:
ptim/miniflare-typescript-esbuild-jest@371a788

Thanks for your work 🙏

CORS Error on POST request

I am getting CORS error on post route as below

router.post('/login', async (request) => {
  const content = await request.json()
  return new Response(JSON.stringify(content), {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Content-type': 'application/json',
      'Accept': '*/*'
    }
  })
})

But there are no issue if i do GET with same options except content

What exactly is supported path-wise?

I'm trying to interpret some of the regex stuff in the tests, and I don't really understand it. Here's a few cases I think will help me understand better. For each one, should the path match the route or not?

route path
/x|y /y
/x/y|z /z
/x/y|/z /x/z
/xy* /x
/xy* /xyz
/:x.y /a.x.y
/x.y /xay
/xy{2} /xyxy
/xy{2} /xy/xy
/:x.:y /a.b.c
/test.:x /test.a.b

Also, are these just errors, or should they have reasonable meanings?

  • /:x?.y
  • /x/:y*

Response not being sent when body is attached

Following our last discussion we are still having the same problem with the method not allowed response for an invalid method not being sent when a body is attached. We have also noticed the same issue when a post request is made to an invalid endpoint and also when we pass an invalid token (or no token) to our validateToken handler. If there is no body attached in any of this situations then the correct response is returned but if a body is attached, it just hangs.

4E7BB748-AF7E-442D-82A9-8AD3A5DBED75_1_105_c

This is our validateToken handler.

          const ValidateToken = async (request: any) => {
            
            const token: any = request.headers.get('token')
            let SecretKey: string = process.env.KEY || KEY
            if (token != SecretKey) {
              console.log("token not equal to key")
              return new Response('403 Invalid token', {
                status: 403,
                statusText: 'Please check you are using the correct token',
              })
            }
          }

      export default ValidateToken

This is our handler file.

      import { Router } from 'itty-router'
      import ValidateToken from './handlers/validateToken'
      import ValidateLead from './handlers/validateLead'
      import SendLead from './services/sendLead'
      
      const methodError = JSON.stringify({
        message: 'Method Not Allowed',
        body: 'Only POST requests allowed',
      })
      
      const errorHandler = (error: any) =>
        new Response(error.message || 'Server Error', { status: error.status || 500 })
      
      const router = Router() // this is a Proxy, not a class
      
      router
        .post(
          '/api/lead/v1',
          ValidateToken,
          ValidateLead,
          SendLead,
        )
        .post(
          '*',
          () => new Response('Not found', { status: 404, statusText: 'Not found' }),
        )
        .all(
          '*',
          () =>
            new Response(methodError, {
              headers: { 'content-type': 'application/json;charset=UTF-8' },
              status: 405,
              statusText: 'Method not allowed',
            }),
        )
      
      export const handleRequest = (request: any) =>
        router.handle(request).catch(errorHandler)

We have tried using ThrowableRouter from itty-router-extras with the same issue.

Can you confirm that a response should be returned with or without a body attached. If you require any more information please let me know.

Thanks

Ruth

Get wildcard value

Firstly, lovely little router this but one thing I'm not clear on.
Is there a way in the handler, to get the value that a wildcard matched on. For example,

const router = Router()
router.get('/maps/tiles/vector/*', vectorTilesHandler)
router.get('/maps/tiles/static/*', staticTilesHandler)
router.get('/maps/packs/vector/*', vectorPacksHandler)
router.get('/maps/packs/static/*', staticPacksHandler)

I'd like to get the whole wildcard value in the handler. I can get the full url, but then that means I need to remove the host and duplicate the initial piece of path so I can strip that off the string.

Likewise I can't use parameters because you can't have a greedy parameter that takes all segments. The reason I need the wildcard is so i can proxy it as is (in these cases).

ReferenceError: p is not defined

I'm having an issue with importing the router. I'm using a Cloudflare Worker with Durable Objects support. It seems like the variables in the router are not defined. Here is the stack trace:

ReferenceError: p is not defined
    at Proxy.e.r (index.mjs:1:77)

I can fix the issue by editing the script and declaring let p, hs, u, m, h, r.

I think this is due to es6 modules always being in strict mode. Are there plans to make a version that is compliant with strict mode? Especially since the soon to be released durable objects are using es6 modules.

Thanks!

Destructuring request.params partially decodes string

Hi all,

I'm facing this strange behaviour.

If I have a GET endpoint like /entities/:id and I try to pass as :id an encoded string like entities%3A%2Fsource-a_1644431865059, what I get from const { id } = request.params is entities:%2Fsource-a_1644431865059.

Now, why does %3A get decoded into a colon while the rest of the string remains untouched?

Any suggestion anyone?

Thank you!

Invalid character in route '+'

I'm having some difficulty routing a particular URL. I'm wondering if it contains some characters that is breaking the matching?

The url I'm trying to match on is: /v1/osm-v4+cmap-nautical-v3+public-places-latest/*

I've played around a bit and if I take the plus characters out + then it seems to match, otherwise it falls through.

Would someone be kind enough to confirm what the issue is here and whether there is a workaround? This would be a great addition to the docs.

Documentation for module syntax 'context' parameter

In the module syntax portion of the readme (https://github.com/kwhitley/itty-router#cf-es6-module-syntax), it has the following example of how to access the request and env bindings params:

router.get('/', (request, env) => {
  // now have access to the env (where CF bindings like durables, KV, etc now are)
})

I'm assuming there is a third context parameter for passing in the context (see: https://developers.cloudflare.com/workers/runtime-apis/fetch-event#syntax-module-worker), so the example should really be:

router.get('/', (request, env, context) => {
  // now have access to the env (where CF bindings like durables, KV, etc now are)
})

Is this correct?

Use in durable objects??

I saw an interview with you and you mentioned you were using itty router in a durable object class.

How?

I can't seem to get it to work... but I'm also a little brain dead at this point with dealing with all the DO issues. haha... thanks for any insight.

Nestable routers not inheriting base

Perhaps this is a point that needs to be clarified in the documentation, but contrary to what "Nestable" and "API Branching" imply, nestable routers do not inherit the parent's base. To illustrate, an adapted version of the example in the README:

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

// create a parent router at /api
const parentRouter = Router({ base: '/api' })

// 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 /api/todos/* into the child router
parentRouter
  .all('/todos/*', todosRouter.handle) // attach child router
  .all('*', missingHandler) // catch any missed routes

// GET /api/todos --> missingHandler
// GET /api/todos/13 --> missingHandler
// POST /api/todos --> missingHandler (caught eventually by parentRouter)
// GET /api/foo --> missingHandler

To make the above work, one would have to change the base of todosRouter to be /api/todos.

Modify response in Middleware

I'm currently trying to modify all responses for all routes to include the Access-Control-Allow-Origin header. Is there any way to do this with middleware?

If not then consider this a feature request :)

Solving the withParams global middleware issue

Currently a withParams middleware that targets request.params cannot be injected upstream as global middleware, because at this point, params would have not been set (to be spread into the request).

import { Router } from 'itty-router'
import { withParams } from 'itty-router-extras'

const router = Router()

// currently works
router
  .get('/:id', withParams, ({ id }) => new Response(id))
  
// currently fails
router
  .all('*', withParams)
  .get('/:id', ({ id }) => new Response(id))

Proposed Solution

  1. Pass request.proxy || request through to handlers... middleware can inject a proxy (if needed) to trigger this. This costs about ~5 bytes.
  2. Middleware can create a Proxy around the request on request.proxy and effectively watch the request (for loggers and such), or modify how they read things (e.g. withParams intercepts "gets" to the request proxy and find the values in the request.params)

After (Line 12)

image

This allows a middleware upstream to watch any change on the request itself, including (but not limited to) request.params, and fire appropriately. It also allows each middleware to proxy the existing proxy, meaning multiple middleware can watch independent request props without worrying about collisions.

With this, an upstream withParams could be defined as follows, then downstream changes to params would trigger the proxy (assigning params into the request).

image

Logo difficult to read when in dark mode

Wanted to let you know that the black text for "router" is difficult to read when using Github in dark mode.
itty router readme

A common solution is to add a white background with a border radius so that it is legible in dark mode, but appears normally when in light mode.

Running an handler after response is returned

Hi,

In some cases you'd want to run common code after one of the handlers returned a Response but before it was returned to the client.
Some examples:

  • Async send accumulated log-lines to a logger service such as logDNA
  • Async send events to an analytics service
  • Enrich the response with additional headers such as requestId, context, cookies, etc

For all these cases I'd like to be able to add handlers that will run as middleware on the way out.

What do you think?
Is there another pattern to achieve that?

Params vs Defined Paths are combined

I have tried to build rest api with cloudflare workers. I have fallowing routes

router.post('/rooms/image', withUser, async request => {
  const { post } = request

  const room = await rooms.addImage(post)

  return new Response(JSON.stringify(room, null, 2), heads)
})
router.post('/rooms/:id', withUser, async ({ params, post }) => {
  

  const room = await rooms.edit(post)

  return new Response(JSON.stringify(room, null, 2), heads)
})

When i sent request to the rooms/:id it takes care of rooms/image. Router does not get correct route releated to regex.

Export Request and Obj types?

Hi,

We LOVE itty! Would it be possible to export your types from your .d.ts file? We often do handlers in separate handler files and we'd love to be able to do things like this:

import { Request as IttyRequest } from 'itty-router';
...
async buildSomeResponse(req: IttyRequest): Promise<Response> { ... }

VSCode in particular gets confused with the type of the Request arg, and barking about req.query not existing in an example like the above.

Thanks!

COMMUNITY REQUEST: Hybrid export PR or discussion

Hey all! To help with a few issues people have found regarding import into different scenarios (Deno, Jest, etc), I'm proposing a hybrid module export from the build process:

  1. A CommonJS export (currently used for compatibility)
  2. An ES Modules export (e.g. export const Router = ...)

Further investigation into this (and what the underlying issue is) would also be helpful, as I have used itty using esbuild (Cloudflare ES module syntax), as well as traditional CommonJS into webpack builds (or even just using require without w bundler for things like itty's own Jest tests). It always seems to work for me, so I'm not sure what the issue is!

I'll be out of the country (without my computer) for the next few weeks, so any help before I return mid-January would be greatly appreciated!

Cheers and Happy Holidays!

Property 'json' does not exist on type 'Request'

import { Request } from 'itty-router';

const Update = async (request: Request) => {
  const inputData = await request.json();
  console.log(inputData);
};

Typescript will flag out Line 4 , citing that Property 'json' does not exist on type 'Request'

My quick workaround was to // @ts-ignore the line before

How to handle catch all routes

I've defined the following routes

router.post("/api/send-email", handleSendEmail);
  .all("/api/*", handleNotFound);

When I test the route I always get back a 404 even for an exact URL match.

If I remove the .all("/api/*", handleNotFound);, I'll get undefined back

How are you supposed to handle catch all routes? Should I be checking for an undefined response and returning that?

Dots are not escaped

Example '/static/file.json' results in /static/file.json, with the extension dot unescaped. This is somewhat related to #34.

Failed to execute function parameter 1 is not of type response

If you respond with html not using the router then you don't get the error. I still get html in the preview, but the stackstraces are really annoyining in terminal. Everything works but with stack traces in terminal. I believe it will still show underlying errors like reference errors and stuff but you have to scroll more.

import { Router } from 'itty-router'
import {html, HTMLResponse} from '@worker-tools/html'
import {layout} from "./views/layout.js"
import paypal from './paypal.js'
const router = Router()
router.get('/', function handleRequest(event) {
  const page = html`
    abc  `
  return new HTMLResponse (layout(page));
})
router.all('/paypal/*', paypal.fetch)

addEventListener('fetch', event =>
event.respondWith(new HTMLResponse (layout(html`abc  `)))
  // event.respondWith(router.handle(event.request))
)

layoutjs

import {html, HTMLResponse} from '@worker-tools/html'

export function layout (content, title = "Source Domains") {
  return html`
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>${title}</title>
  </head>
  <body>${content}</body>
</html>
`;}

export default function render (content, title = "Source Domains"){
  return new HTMLResponse(layout(content, title))
}

Modify request headers

Hi,

I would like to modify request headers in one of my middleware steps. Example:

  const newHeaders = new Headers(request.headers);
  newHeaders.set("X-Super-header", isSuper);

  const modifiedRequest = new Request(request.url, {
    body: request.body,
    headers: request.headers,
    method: request.method,
    redirect: request.redirect
  })

  request = modifiedRequest

I know if I make a return it has to be Response but when reassigning the var request, it's not being changed.

Getting Request headers

I think I am probably gonna sound so stupid to not figure this out on my own - is there a way to get request headers in this? The TypeScript declaration for Request doesn't have a headers property.

Typescript errors

Hi, the latest changes in 2.4.0 produced the following typescript errors:

../../node_modules/itty-router/dist/itty-router.d.ts:16:6 - error TS2314: Generic type 'RouteHandler<TRequest>' requires 1 type argument(s).

16   2: RouteHandler[]
        ~~~~~~~~~~~~

../../node_modules/itty-router/dist/itty-router.d.ts:25:19 - error TS2314: Generic type 'Promise<T>' requires 1 type argument(s).

25   arrayBuffer?(): Promise
                     ~~~~~~~

../../node_modules/itty-router/dist/itty-router.d.ts:26:12 - error TS2314: Generic type 'Promise<T>' requires 1 type argument(s).

26   blob?(): Promise
              ~~~~~~~

../../node_modules/itty-router/dist/itty-router.d.ts:27:16 - error TS2314: Generic type 'Promise<T>' requires 1 type argument(s).

27   formData?(): Promise
                  ~~~~~~~

../../node_modules/itty-router/dist/itty-router.d.ts:28:12 - error TS2314: Generic type 'Promise<T>' requires 1 type argument(s).

28   json?(): Promise
              ~~~~~~~

../../node_modules/itty-router/dist/itty-router.d.ts:29:12 - error TS2314: Generic type 'Promise<T>' requires 1 type argument(s).

29   text?(): Promise
              ~~~~~~~

tsc --version

Version 4.3.5

Middleware for responses

Similar to how express works there doesn't seem to be an easy way for an itty middleware to trigger after the response. For express there's the https://www.npmjs.com/package/express-mung library to mitigate this, but it's not the most pretty solution.
It would be nice if a middleware could be passed a next function that you could await that could return the response object, so something like this:

async function middleware(request, next) {
  const response = await next();

  console.log(response.status);
}

This would make it possible to do things like error handler middlewares, logging response and keeping track of response times.

Middleware with parameters?

Is it possible to write middleware that accepts parameters like my example below?

router.post('/ping', parseRequest('id123'), async (request, env) => {}

Access to FetchEvent in routes/middleware

There are some middleware use-cases that rely on the FetchEvent. For example, here's a Cloudflare middleware that normalizes the User-Agent, caches based on the normalized UA, and instructs downstream caches to vary on User-Agent:

async function varyOnNormalizedUserAgent(request) {
  const url = new URL(request.url)
  const ua = url.searchParams.get('ua') || UA.normalize(request.headers.get('User-Agent'))
  url.searchParams.set('ua', ua)

  const upstreamRequest = new Request(url, request)
  let originResponse = await next(event) // see #85 
  originResponse = new Response(originResponse.body, originResponse) // mutable copy
  
  const responseForCache = originResponse.clone()
  fetchEvent.waitUntil(caches.default.put(upstreamRequest, responseForCache))

  originResponse.headers.append('Vary', 'User-Agent')
  reutrn originResponse
}

Is the recommended approach to attach the FetchEvent to the Request so upstream handlers have access to it?

HEAD and OPTIONS request support

Just assumed that the router supported head requests, but realised that it doesn't. Is this by design or has it not just been implemented yet? Think head and options support would be nice to have as default, but guess it could be added as a proxy instead? What would be the preferred solution?

Version 2.6.0 generated mjs file does not have named exports

In version 2.6.0, the generated .mjs file exposes only a default export of an object containing the Router, the syntax import {Router} from 'itty-router' does not work.

The generated .mjs file contains the following code, and is picked up by Rollup and Webpack by default, causes build to fail. Reverting back to version 2.5.3 works.

export default{Router:function({base:t="",routes:n=[]}={}){return{__proto__:new Proxy({},{get:(e,a,o)=>(e,...r)=>n.push([a.toUpperCase(),RegExp(`^${(t+e).replace(/(\/?)\*/g,"($1.*)?").replace(/\/$/,"").replace(/:(\w+)(\?)?(\.)?/g,"$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/,"\\.").replace(/\)\.\?\(([^\[]+)\[\^/g,"?)\\.?($1(?<=\\.)[^\\.")}/*$`),r])&&o}),routes:n,async handle(e,...r){let a,o,t=new URL(e.url);e.query=Object.fromEntries(t.searchParams);for(var[p,s,u]of n)if((p===e.method||"ALL"===p)&&(o=t.pathname.match(s))){e.params=o.groups;for(var c of u)if(void 0!==(a=await c(e.proxy||e,...r)))return a}}}}};

Typescript Examples

Are there any typescript examples laying around? I looked at the .d.ts and its not very informative. The Router type seems to be incomplete. It doesn't expose the methods as functions (GET, POST, DELETE, ect). Could you please explain how the generic TRequest is supposed to be used?

Thanks!

Optional format params feature is broken in recent versions since 2.3.10

Example code on replit: https://replit.com/@dbradleyfl/itty-optional-format-parsing#index.js

const { Router } = require('itty-router')

const router = Router()

// if you remove the ? below you get different results and this is a regression
router.get('/pages/:id.:format?', (req, res) => {
  return `Params: ${JSON.stringify(req.params)}`
})

router.handle({
  method: 'GET',
  url: 'https://example.com/pages/1',
}).then(console.log)

router.handle({
  method: 'GET',
  url: 'https://example.com/pages/1.html',
}).then(console.log)

// this will log the following
// Params: {"id":"1"}
// Params: {"id":"1.html"}

// if you remove the ? from the route to indicate the format is no longer optional you get the following
// undefined
// Params: {"id":"1","format":"html"}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.