Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determine if route exists #95

Open
shennan opened this issue Jul 28, 2020 · 6 comments
Open

Determine if route exists #95

shennan opened this issue Jul 28, 2020 · 6 comments
Labels

Comments

@shennan
Copy link

shennan commented Jul 28, 2020

Is there a way of determining if an exact route exists and running some code if it doesn't. For example:

const Router = require('router')

const route = Router().use('/some/route', (res, req, next) => next())

route({url: '/some/nonexistent/route', method: 'get'}, {}, err => {
  if (err && err instanceof NoRouteError) {
    console.log('No route match')
  }
})

Or perhaps something like:

const Router = require('router')

const route = Router().use('/some/route', (res, req, next) => next())

const routeExists = route.test({url: '/some/nonexistent/route', method: 'get'})

console.log(routeExists ? 'ok' : 'No route match')

I'm currently using the library without any HTTP server, just as a router and middleware engine. So req.send() doesn't exist for me (and shouldn't), I simply call next() until there is no more middleware in the stack.

Any direction on this would be great.

@dougwilson
Copy link
Contributor

Hi @shennan ,

Your first example is almost there: getting to that last callback function with no error is when there is no route, so you should just change your test to !err.

As far as the second example, that is not possible, as any middleware can rewrite the url like req.url = and the routing will continue based on the new URL (or method), similar to the Apache mod_rewrite. This means in order to determine if a given request will match a route, the middlewares need to process on the request in order to apply any possible rewrite rules.

@shennan
Copy link
Author

shennan commented Jul 28, 2020

Thanks for the response @dougwilson.

The problem with the above is that the 'finish' callback is executed always, whether the route exists or not. But by the sound of it (due to possible rewrites of the url) there is no clear way of determining if an exact route exists during runtime?

On a slightly different note...

Is there any way of getting the response object in the 'finish' callback? Reason being that I want to somehow promisify the execution of a route. Something like:

route('some/route').then(responseCallback).catch(errorCallback)

I wrote something like this:

// Promisify a route execution.
function executeRoute (route, url, req = {data: {}}, res = {}) {

  return new Promise((resolve, reject) => {

    route.use((req, res) => {

      resolve({ok: true, ...res})

    })

    route({...req, method: 'GET', url}, res, err => {

      if (err)
        reject(err)

    })
  })
}

module.exports = {executeRoute}

But the problem with that is that it adds a new route each time it's called. It works perfectly; but only once... because I don't call next(). Basically I want to be able to fire a method which sets off a chain of middleware and eventually returns a result. As I don't have req.send, I'm unable to capture my result.

Ideally I want the ability to add some middleware that is only added for that single execution of the route. Such that I can capture results but not effect the make-up of the stack.

Am I trying to square-peg a round hole?

@dougwilson
Copy link
Contributor

Hi @shennan the callback should not be called if you have a route there. Can you share your route code? For example,

const route = Router().use('/some/route', (res, req, next) => next())

Is considered a route that does not exist in this module, as it calls next(), indicating that the route doesn't apply to that given request. This is because route matching is fully programmatic: you can match on anything you want, and next() is there to say "hey, this request doesn't match here, so keep going".

Is there any way of getting the response object in the 'finish' callback?

It is not provided, as the response object is not something produced by the router function, it is just an input. The reference would already be available in your scope when you call router.

As far as your later part of the quest, I think I lost you there, and I'm not sure what exactly you are attempting to do, not sure what an answer is.

@shennan
Copy link
Author

shennan commented Jul 28, 2020

Ok, so the library assumes that a route is only fulfilled when next() is not called? I have something like...

route
  .use('/some/route', someMiddleware)
  .use('/some/route', someOtherMiddleware)

Which allows me to modularise my middleware and separate concerns. But it sounds like, within this setup, I should not call next() in someOtherMiddleware if I consider it the completion (and therefore an existing) route? That kinda makes sense actually.

The latter part of the question is really to do with how my application consumes and 'runs' the route. Because I'm not using a server, I've just been handing empty objects as the response. But I suppose in order to get a response from the executed route, I should send a response object akin to {send: callback} and call res.send('whatever') in my middleware when I consider the route execution fulfilled?

Thanks again for the continued advice. 👍

@dougwilson
Copy link
Contributor

Middlewares are not the same as the route handlers. A middleware is typically expected to call next(), but not a route handler, which is expected to either respond to the request (populate the response object) or call next() to try and see if there is another route handler that will fulfill it.

I guess, what are you trying to do with the module? I thought perhaps you were just trying to write some unit tests around it, but still would be using it to write something that would actually work under the HTTP server, is that not the case?

@shennan
Copy link
Author

shennan commented Jul 28, 2020

Thanks @dougwilson

Perhaps my use case is a strange one. Essentially I wanted to use router without a server such that I could run it in various browser and node distros. We have a core product which needs to have several interfaces. One is a server and more akin to a traditional express application, the other is a devtools extension, an electron interface etc. And there may be others. So I'm using it as a sort of headless router, which will contain all of our business logic.

Best way to describe it is:

// core.js

const route = Router()

route
  .use('/some/route', someMiddleware())
  .use('/some/route', someMoreMiddleware())

return route

// server.js

const core = require('core')
const express = require('express')
const app = express()

app.use((req, res, next) => {
  core({url: req.originalUrl, method: 'GET'}, {
    end: result => res.end(result)
  })
})

app.listen(3000)

// some-browser-app.js

import core from core

window.execute = (path, data) => {
  
  return new Promise((resolve, reject) => {
    core({url: path, method: 'GET'}, {
      end: result => resolve(result)
    }, (err) => {
      reject(err || new Error('No path found')
    })
  })
}

Basically we wanted to leverage the modulability of routes and middleware, and have our main business logic operate outside of a particular interface. Obviously it's more complex than the above but it should go some way at explaining the use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants