Repository: delvedor/find-my-way
Pull request #1 (2017-04-19 09:55:42)

Added Store, refactored internals and updated api
- Added a store object for every node
- Refactored `find` => *recursive* to *iterative* (better results for long paths)
- Updated `lookup` api, now accepts only `request` and `response` objects.
- Updated docs

@mcollina feedbacks?


Patch:
diff --git a/README.md b/README.md
index 96117af..d1565e6 100644
--- a/README.md
+++ b/README.md
@@ -5,22 +5,24 @@
 A crazy fast HTTP router, internally uses an highly performant [Radix Tree](https://en.wikipedia.com/wiki/Radix_tree) (aka compact [Prefix Tree](https://en.wikipedia.com/wiki/Trie)), supports route params, wildcards, and it's framework independent.  
 It is inspired by the [echo](https://github.com/labstack/echo) router, some parts have been extracted from [trekjs](https://github.com/trekjs) router.
 
+<a name="install"></a>
 ## Install
 ```
 npm i find-my-way --save
 ```
 
+<a name="usage"></a>
 ## Usage
 ```js
 const http = require('http')
-const findMyWay = require('./')()
+const router = require('find-my-way')()
 
-findMyWay.on('GET', '/', (req, res, params) => {
+router.on('GET', '/', (req, res, params) => {
   res.end('{"hello":"world"}')
 })
 
 const server = http.createServer((req, res) => {
-  findMyWay.lookup(req.method, req.url, req, res)
+  router.lookup(req, res)
 })
 
 server.listen(3000, err => {
@@ -29,6 +31,60 @@ server.listen(3000, err => {
 })
 ```
 
+<a name="api"></a>
+## API
+<a name="constructor"></a>
+#### FindMyway([options])
+Instance a new router.  
+You can pass a default route with the option `defaultRoute`.
+```js
+const router = require('find-my-way')({
+  defaultRoute: (req, res) => {
+    res.statusCode = 404
+    res.end()
+  }
+})
+```
+
+<a name="on"></a>
+#### on(method, path, handler, [store])
+Register a new route, `store` is an object that you can access later inside the handler function.
+```js
+router.on('GET', '/', (req, res, params) => {
+  // your code
+})
+
+// with store
+router.on('GET', '/store', (req, res, params, store) => {
+  assert.equal(store, { hello: 'world' })
+}, { hello: 'world' })
+```
+
+<a name="lookup"></a>
+#### lookup(request, response)
+Start a new search, `request` and `response` are the server req/res objects.  
+If a route is found it will automatically called the handler, otherwise the default route will be called.  
+The url is sanitized internally.
+```js
+router.lookup(req, res)
+```
+
+<a name="find"></a>
+#### find(method, path)
+Return (if present) the route registered in *method:path*.  
+The path must be sanitized.
+```js
+router.find('GET', '/example')
+// => { handler: Function, params: Object, store: Object}
+// => null
+```
+
+<a name="acknowledgements"></a>
+## Acknowledgements
+
+This project is kindly sponsored by [LetzDoIt](http://www.letzdoitapp.com/).
+
+<a name="license"></a>
 ## License
 **[find-my-way - MIT](https://github.com/delvedor/find-my-way/blob/master/LICENSE)**  
 **[trekjs/router - MIT](https://github.com/trekjs/router/blob/master/LICENSE)**
diff --git a/bench.js b/bench.js
index a13aafc..e3f017d 100644
--- a/bench.js
+++ b/bench.js
@@ -7,14 +7,27 @@ const FindMyWay = require('./')
 
 const findMyWay = new FindMyWay()
 findMyWay.on('GET', '/', () => true)
-findMyWay.on('GET', '/user', () => true)
+findMyWay.on('GET', '/user/:id', () => true)
+findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', () => true)
 
 suite
-  .add('fmw router static route', function () {
-    findMyWay.lookup('GET', '/')
+  .add('lookup static route', function () {
+    findMyWay.lookup({ method: 'GET', url: '/' }, null)
   })
-  .add('fmw router dynamic route', function () {
-    findMyWay.lookup('GET', '/user/tomas')
+  .add('lookup dynamic route', function () {
+    findMyWay.lookup({ method: 'GET', url: '/user/tomas' }, null)
+  })
+  .add('lookup long static route', function () {
+    findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }, null)
+  })
+  .add('find static route', function () {
+    findMyWay.find('GET', '/')
+  })
+  .add('find dynamic route', function () {
+    findMyWay.find('GET', '/user/tomas')
+  })
+  .add('find long static route', function () {
+    findMyWay.find('GET', '/abc/def/ghi/lmn/opq/rst/uvz')
   })
   .on('cycle', function (event) {
     console.log(String(event.target))
diff --git a/example.js b/example.js
index a522a39..f7c4f8d 100644
--- a/example.js
+++ b/example.js
@@ -1,14 +1,15 @@
 'use strict'
 
 const http = require('http')
-const findMyWay = require('./')()
+const router = require('./')()
 
-findMyWay.on('GET', '/', (req, res, params) => {
+router.on('GET', '/test', (req, res, params) => {
   res.end('{"hello":"world"}')
 })
 
 const server = http.createServer((req, res) => {
-  findMyWay.lookup(req.method, req.url, req, res)
+  console.log(req.url)
+  router.lookup(req, res)
 })
 
 server.listen(3000, err => {
diff --git a/index.js b/index.js
index 2543d8b..eb126b5 100644
--- a/index.js
+++ b/index.js
@@ -5,45 +5,44 @@
     static: 0,
     param: 1,
     matchAll: 2,
-
-  Char codes:
-    slash: 47,
-    colon: 58,
-    star: 42
 */
 
+const assert = require('assert')
 const Node = require('./node')
+const httpMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'TRACE', 'CONNECT']
 
 function Router (opts) {
   if (!(this instanceof Router)) {
     return new Router(opts)
   }
   opts = opts || {}
+
   if (opts.defaultRoute) {
+    assert.equal(typeof opts.defaultRoute, 'function', 'The default route must be a function')
     this.defaultRoute = opts.defaultRoute
   }
-  if (opts.async) {
-    this.async = true
-  }
 
   this.tree = new Node()
 }
 
-Router.prototype.on = function (method, path, handler) {
+Router.prototype.on = function (method, path, handler, store) {
+  assert.equal(typeof method, 'string', 'Method should be a string')
+  assert.equal(typeof path, 'string', 'Path should be a string')
+  assert.equal(typeof handler, 'function', 'Handler should be a function')
+  assert.notEqual(httpMethods.indexOf(method), -1, `Method '${method}' is not an http method.`)
+
   const params = []
-  var charCode = 0
   var j = 0
 
   for (var i = 0, len = path.length; i < len; i++) {
     // search for parametric or wildcard routes
-    charCode = path.codePointAt(i)
     // parametric route
-    if (charCode === 58) {
+    if (path[i] === ':') {
       j = i + 1
-      this._insert(method, path.slice(0, i), 0, null, null)
+      this._insert(method, path.slice(0, i), 0, null, null, null)
 
       // isolate the parameter name
-      while (i < len && path.codePointAt(i) !== 47) i++
+      while (i < len && path[i] !== '/') i++
       params.push(path.slice(j, i))
 
       path = path.slice(0, j) + path.slice(i)
@@ -52,22 +51,22 @@ Router.prototype.on = function (method, path, handler) {
 
       // if the path is ended
       if (i === len) {
-        return this._insert(method, path.slice(0, i), 1, params, handler)
+        return this._insert(method, path.slice(0, i), 1, params, handler, store)
       }
-      this._insert(method, path.slice(0, i), 1, params, null)
+      this._insert(method, path.slice(0, i), 1, params, null, null)
 
     // wildcard route
-    } else if (charCode === 42) {
-      this._insert(method, path.slice(0, i), 0, null, null)
+    } else if (path[i] === '*') {
+      this._insert(method, path.slice(0, i), 0, null, null, null)
       params.push('*')
-      return this._insert(method, path.slice(0, len), 2, params, handler)
+      return this._insert(method, path.slice(0, len), 2, params, handler, store)
     }
   }
   // static route
-  this._insert(method, path, 0, params, handler)
+  this._insert(method, path, 0, params, handler, store)
 }
 
-Router.prototype._insert = function (method, path, kind, params, handler) {
+Router.prototype._insert = function (method, path, kind, params, handler, store) {
   var prefix = ''
   var pathLen = 0
   var prefixLen = 0
@@ -84,7 +83,7 @@ Router.prototype._insert = function (method, path, kind, params, handler) {
 
     // search for the longest common prefix
     max = pathLen < prefixLen ? pathLen : prefixLen
-    while (len < max && path.codePointAt(len) === prefix.codePointAt(len)) len++
+    while (len < max && path[len] === prefix[len]) len++
 
     if (len < prefixLen) {
       // split the node in the radix tree and add it to the parent
@@ -92,25 +91,27 @@ Router.prototype._insert = function (method, path, kind, params, handler) {
 
       // reset the parent
       currentNode.children = [node]
+      currentNode.numberOfChildren = 1
       currentNode.prefix = prefix.slice(0, len)
-      currentNode.label = currentNode.prefix.codePointAt(0)
-      currentNode.map = Object.create(null)
+      currentNode.label = currentNode.prefix[0]
+      currentNode.map = {}
       currentNode.kind = 0
 
       if (len === pathLen) {
         // add the handler to the parent node
-        currentNode.addHandler(method, handler, params)
+        assert(!currentNode.findHandler(method), `Method '${method}' already declared for route '${path}'`)
+        currentNode.addHandler(method, handler, params, store)
         currentNode.kind = kind
       } else {
         // create a child node and add an handler to it
         node = new Node(path.slice(len), [], kind)
-        node.addHandler(method, handler, params)
+        node.addHandler(method, handler, params, store)
         // add the child to the parent
         currentNode.add(node)
       }
     } else if (len < pathLen) {
       path = path.slice(len)
-      node = currentNode.findByLabel(path.codePointAt(0))
+      node = currentNode.findByLabel(path[0])
       if (node) {
         // we must go deeper in the tree
         currentNode = node
@@ -118,103 +119,99 @@ Router.prototype._insert = function (method, path, kind, params, handler) {
       }
       // create a new child node
       node = new Node(path, [], kind)
-      node.addHandler(method, handler, params)
+      node.addHandler(method, handler, params, store)
       // add the child to the parent
       currentNode.add(node)
     } else if (handler) {
       // the node already exist
-      currentNode.addHandler(method, handler, params)
+      assert(!currentNode.findHandler(method), `Method '${method}' already declared for route '${path}'`)
+      currentNode.addHandler(method, handler, params, store)
     }
     return
   }
 }
 
-Router.prototype.lookup = function (method, path, req, res) {
-  var handle = this._lookup(method, path, this.tree, { handler: null, params: [] })
-  if (!handle.handler) return this._defaultRoute(req, res)
-  if (this.async) {
-    return setImmediate(handle.handler, req, res, handle.params)
-  }
-  return handle.handler(req, res, handle.params)
+Router.prototype.lookup = function (req, res) {
+  var i = 0
+  var len = req.url.length
+  while (i < len && req.url[i] !== '?' && req.url[i] !== '#') i++
+  var handle = this.find(req.method, req.url.slice(0, i))
+  if (!handle) return this._defaultRoute(req, res)
+  return handle.handler(req, res, handle.params, handle.store)
 }
 
 Router.prototype.find = function (method, path) {
-  return this._lookup(method, path, this.tree, { handler: null, params: [] })
-}
+  var currentNode = this.tree
+  var node = null
+  var pindex = 0
+  var params = []
+  var pathLen = 0
+  var prefix = ''
+  var prefixLen = 0
+  var len = 0
+  var i = 0
+
+  while (true) {
+    pathLen = path.length
+    prefix = currentNode.prefix
+    prefixLen = prefix.length
+    len = 0
 
-Router.prototype._lookup = function (method, path, currentNode, route) {
-  var pathLen = path.length
-  var prefix = currentNode.prefix
+    // found the route
+    if (pathLen === 0 || path === prefix) {
+      var handle = currentNode.findHandler(method)
+      if (!handle) return null
 
-  // found the route
-  if (pathLen === 0 || path === prefix) {
-    var handle = currentNode.findHandler(method)
-    if (!handle || !handle.handler) return route
+      var paramNames = handle.params
+      var paramsObj = {}
 
-    route.handler = handle.handler
-    var paramNames = handle.params
-    var paramValues = route.params
-    route.params = {}
+      for (i = 0; i < paramNames.length; i++) {
+        paramsObj[paramNames[i]] = params[i]
+      }
 
-    for (var i = 0; i < paramNames.length; i++) {
-      route.params[paramNames[i]] = paramValues[i]
+      return {
+        handler: handle.handler,
+        params: paramsObj,
+        store: handle.store
+      }
     }
 
-    return route
-  }
-
-  var swapPath = ''
-  var prefixLen = prefix.length
-  var len = 0
-  // search for the longest common prefix
-  var max = pathLen < prefixLen ? pathLen : prefixLen
-  while (len < max && path.codePointAt(len) === prefix.codePointAt(len)) len++
+    // search for the longest common prefix
+    i = pathLen < prefixLen ? pathLen : prefixLen
+    while (len < i && path[len] === prefix[len]) len++
 
-  if (len === prefixLen) path = path.slice(len)
-  swapPath = path
+    if (len === prefixLen) path = path.slice(len)
 
-  // static route
-  var node = currentNode.find(path.codePointAt(0), 0)
-  if (node) {
-    this._lookup(method, path, node, route)
-    if (route.hanlder) {
-      return route
+    // static route
+    node = currentNode.find(path[0], 0)
+    if (node) {
+      currentNode = node
+      continue
     }
-    path = swapPath
-  }
 
-  // route not found
-  if (len !== prefixLen) {
-    return route
-  }
+    // parametric route
+    node = currentNode.findByKind(1)
+    if (node) {
+      currentNode = node
+      i = 0
+      while (i < pathLen && path[i] !== '/') i++
+      params[pindex++] = path.slice(0, i)
+      path = path.slice(i)
+      continue
+    }
 
-  // parametric route
-  node = currentNode.findByKind(1)
-  if (node) {
-    len = path.length
-    i = 0
-    // find the next '/'
-    while (i < len && path.codePointAt(i) !== 47) i++
-    // save the param value
-    route.params.push(path.slice(0, i))
-    swapPath = path
-    path = path.slice(i)
-    this._lookup(method, path, node, route)
-    if (route.handler) {
-      return route
+    // wildcard route
+    node = currentNode.findByKind(2)
+    if (node) {
+      params[pindex] = path
+      currentNode = node
+      path = ''
+      continue
     }
-    route.params.pop()
-    path = swapPath
-  }
 
-  // wildcard route
-  node = currentNode.findByKind(2)
-  if (node) {
-    route.params.push(path)
-    this._lookup(method, '', node, route)
+    // route not found
+    if (len !== prefixLen) return null
   }
-
-  return route
 }
 
 Router.prototype._defaultRoute = function (req, res) {
diff --git a/node.js b/node.js
index c52c77b..36d5470 100644
--- a/node.js
+++ b/node.js
@@ -9,18 +9,20 @@
 
 function Node (prefix, children, kind, map) {
   this.prefix = prefix || '/'
-  this.label = this.prefix.codePointAt(0)
+  this.label = this.prefix[0]
   this.children = children || []
+  this.numberOfChildren = this.children.length
   this.kind = kind || 0
-  this.map = map || Object.create(null)
+  this.map = map || {}
 }
 
 Node.prototype.add = function (node) {
   this.children.push(node)
+  this.numberOfChildren++
 }
 
 Node.prototype.find = function (label, kind) {
-  for (var i = 0, len = this.children.length; i < len; i++) {
+  for (var i = 0; i < this.numberOfChildren; i++) {
     if (this.children[i].label === label && this.children[i].kind === kind) {
       return this.children[i]
     }
@@ -29,7 +31,7 @@ Node.prototype.find = function (label, kind) {
 }
 
 Node.prototype.findByLabel = function (label) {
-  for (var i = 0, len = this.children.length; i < len; i++) {
+  for (var i = 0; i < this.numberOfChildren; i++) {
     if (this.children[i].label === label) {
       return this.children[i]
     }
@@ -38,7 +40,7 @@ Node.prototype.findByLabel = function (label) {
 }
 
 Node.prototype.findByKind = function (kind) {
-  for (var i = 0, len = this.children.length; i < len; i++) {
+  for (var i = 0; i < this.numberOfChildren; i++) {
     if (this.children[i].kind === kind) {
       return this.children[i]
     }
@@ -46,10 +48,11 @@ Node.prototype.findByKind = function (kind) {
   return null
 }
 
-Node.prototype.addHandler = function (method, handler, params) {
+Node.prototype.addHandler = function (method, handler, params, store) {
   this.map[method] = {
     handler: handler,
-    params: params
+    params: params,
+    store: store || null
   }
 }
 


Test patch:
diff --git a/test/errors.test.js b/test/errors.test.js
new file mode 100644
index 0000000..aa27b86
--- /dev/null
+++ b/test/errors.test.js
@@ -0,0 +1,87 @@
+'use strict'
+
+const t = require('tap')
+const test = t.test
+const FindMyWay = require('../')
+
+test('Method should be a string', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  try {
+    findMyWay.on(0, '/test', () => {})
+  } catch (e) {
+    t.is(e.message, 'Method should be a string')
+  }
+})
+
+test('Path should be a string', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  try {
+    findMyWay.on('GET', 0, () => {})
+  } catch (e) {
+    t.is(e.message, 'Path should be a string')
+  }
+})
+
+test('Handler should be a function', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  try {
+    findMyWay.on('GET', '/test', 0)
+  } catch (e) {
+    t.is(e.message, 'Handler should be a function')
+  }
+})
+
+test('Method is not an http method.', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  try {
+    findMyWay.on('GETT', '/test', () => {})
+  } catch (e) {
+    t.is(e.message, 'Method \'GETT\' is not an http method.')
+  }
+})
+
+test('The default route must be a function', t => {
+  t.plan(1)
+  try {
+    FindMyWay({
+      defaultRoute: '/404'
+    })
+  } catch (e) {
+    t.is(e.message, 'The default route must be a function')
+  }
+})
+
+test('Method already declared', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/test', () => {})
+  try {
+    findMyWay.on('GET', '/test', () => {})
+  } catch (e) {
+    t.is(e.message, `Method 'GET' already declared for route 'test'`)
+  }
+})
+
+test('Method already declared nested route', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/test', () => {})
+  findMyWay.on('GET', '/test/hello', () => {})
+  findMyWay.on('GET', '/test/world', () => {})
+
+  try {
+    findMyWay.on('GET', '/test/hello', () => {})
+  } catch (e) {
+    t.is(e.message, `Method 'GET' already declared for route 'hello'`)
+  }
+})
diff --git a/test/methods.test.js b/test/methods.test.js
index 6480aec..32d15fa 100644
--- a/test/methods.test.js
+++ b/test/methods.test.js
@@ -5,15 +5,16 @@ const test = t.test
 const FindMyWay = require('../')
 
 test('the router is an object with methods', t => {
-  t.plan(2)
+  t.plan(3)
 
   const findMyWay = FindMyWay()
 
   t.is(typeof findMyWay.on, 'function')
   t.is(typeof findMyWay.lookup, 'function')
+  t.is(typeof findMyWay.find, 'function')
 })
 
-test('register a routre', t => {
+test('register a route', t => {
   t.plan(1)
   const findMyWay = FindMyWay()
 
@@ -21,7 +22,7 @@ test('register a routre', t => {
     t.ok('inside the handler')
   })
 
-  findMyWay.lookup('GET', '/test')
+  findMyWay.lookup({ method: 'GET', url: '/test' }, null)
 })
 
 test('default route', t => {
@@ -33,7 +34,7 @@ test('default route', t => {
     }
   })
 
-  findMyWay.lookup('GET', '/test')
+  findMyWay.lookup({ method: 'GET', url: '/test' }, null)
 })
 
 test('async handler', t => {
@@ -47,7 +48,7 @@ test('async handler', t => {
     t.ok('inside async handler')
   })
 
-  findMyWay.lookup('GET', '/test')
+  findMyWay.lookup({ method: 'GET', url: '/test' }, null)
 })
 
 test('parametric route', t => {
@@ -58,7 +59,7 @@ test('parametric route', t => {
     t.is(params.id, 'hello')
   })
 
-  findMyWay.lookup('GET', '/test/hello')
+  findMyWay.lookup({ method: 'GET', url: '/test/hello' }, null)
 })
 
 test('multiple parametric route', t => {
@@ -73,8 +74,8 @@ test('multiple parametric route', t => {
     t.is(params.id, 'world')
   })
 
-  findMyWay.lookup('GET', '/test/hello')
-  findMyWay.lookup('GET', '/other-test/world')
+  findMyWay.lookup({ method: 'GET', url: '/test/hello' }, null)
+  findMyWay.lookup({ method: 'GET', url: '/other-test/world' }, null)
 })
 
 test('multiple parametric route with the same prefix', t => {
@@ -89,8 +90,8 @@ test('multiple parametric route with the same prefix', t => {
     t.is(params.id, 'world')
   })
 
-  findMyWay.lookup('GET', '/test/hello')
-  findMyWay.lookup('GET', '/test/world/world')
+  findMyWay.lookup({ method: 'GET', url: '/test/hello' }, null)
+  findMyWay.lookup({ method: 'GET', url: '/test/world/world' }, null)
 })
 
 test('nested parametric route', t => {
@@ -102,7 +103,87 @@ test('nested parametric route', t => {
     t.is(params.world, 'world')
   })
 
-  findMyWay.lookup('GET', '/test/hello/test/world')
+  findMyWay.lookup({ method: 'GET', url: '/test/hello/test/world' }, null)
+})
+
+test('nested parametric route with same prefix', t => {
+  t.plan(3)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/test', (req, res, params) => {
+    t.ok('inside route')
+  })
+
+  findMyWay.on('GET', '/test/:hello/test/:world', (req, res, params) => {
+    t.is(params.hello, 'hello')
+    t.is(params.world, 'world')
+  })
+
+  findMyWay.lookup({ method: 'GET', url: '/test' }, null)
+  findMyWay.lookup({ method: 'GET', url: '/test/hello/test/world' }, null)
+})
+
+test('long route', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', (req, res, params) => {
+    t.ok('inside long path')
+  })
+
+  findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }, null)
+})
+
+test('long parametric route', t => {
+  t.plan(3)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/abc/:def/ghi/:lmn/opq/:rst/uvz', (req, res, params) => {
+    t.is(params.def, 'def')
+    t.is(params.lmn, 'lmn')
+    t.is(params.rst, 'rst')
+  })
+
+  findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }, null)
+})
+
+test('long parametric route with common prefix', t => {
+  t.plan(9)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/', (req, res, params) => {
+    throw new Error('I shoul not be here')
+  })
+
+  findMyWay.on('GET', '/abc', (req, res, params) => {
+    throw new Error('I shoul not be here')
+  })
+
+  findMyWay.on('GET', '/abc/:def', (req, res, params) => {
+    t.is(params.def, 'def')
+  })
+
+  findMyWay.on('GET', '/abc/:def/ghi/:lmn', (req, res, params) => {
+    t.is(params.def, 'def')
+    t.is(params.lmn, 'lmn')
+  })
+
+  findMyWay.on('GET', '/abc/:def/ghi/:lmn/opq/:rst', (req, res, params) => {
+    t.is(params.def, 'def')
+    t.is(params.lmn, 'lmn')
+    t.is(params.rst, 'rst')
+  })
+
+  findMyWay.on('GET', '/abc/:def/ghi/:lmn/opq/:rst/uvz', (req, res, params) => {
+    t.is(params.def, 'def')
+    t.is(params.lmn, 'lmn')
+    t.is(params.rst, 'rst')
+  })
+
+  findMyWay.lookup({ method: 'GET', url: '/abc/def' }, null)
+  findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn' }, null)
+  findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst' }, null)
+  findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }, null)
 })
 
 test('wildcard', t => {
@@ -113,30 +194,33 @@ test('wildcard', t => {
     t.is(params['*'], 'hello')
   })
 
-  findMyWay.lookup('GET', '/test/hello')
+  findMyWay.lookup({ method: 'GET', url: '/test/hello' }, null)
 })
 
 test('find should return the route', t => {
   t.plan(1)
   const findMyWay = FindMyWay()
+  const fn = () => {}
 
-  findMyWay.on('GET', '/test', { hello: 'world' })
+  findMyWay.on('GET', '/test', fn)
 
-  t.deepEqual(findMyWay.find('GET', '/test'), { handler: { hello: 'world' }, params: {} })
+  t.deepEqual(findMyWay.find('GET', '/test'), { handler: fn, params: {}, store: null })
 })
 
 test('find should return the route with params', t => {
   t.plan(1)
   const findMyWay = FindMyWay()
+  const fn = () => {}
 
-  findMyWay.on('GET', '/test/:id', { hello: 'world' })
+  findMyWay.on('GET', '/test/:id', fn)
 
-  t.deepEqual(findMyWay.find('GET', '/test/hello'), { handler: { hello: 'world' }, params: { id: 'hello' } })
+  t.deepEqual(findMyWay.find('GET', '/test/hello'), { handler: fn, params: { id: 'hello' }, store: null })
 })
 
 test('find should return a null handler if the route does not exist', t => {
   t.plan(1)
   const findMyWay = FindMyWay()
 
-  t.deepEqual(findMyWay.find('GET', '/test'), { handler: null, params: [] })
+  // t.deepEqual(findMyWay.find('GET', '/test'), { handler: null, params: [], store: null })
+  t.deepEqual(findMyWay.find('GET', '/test'), null)
 })
diff --git a/test/querystring.test.js b/test/querystring.test.js
new file mode 100644
index 0000000..f02a3bd
--- /dev/null
+++ b/test/querystring.test.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const t = require('tap')
+const test = t.test
+const FindMyWay = require('../')
+
+test('should sanitize the url - query', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/test', (req, res, params) => {
+    t.ok('inside the handler')
+  })
+
+  findMyWay.lookup({ method: 'GET', url: '/test?hello=world' }, null)
+})
+
+test('should sanitize the url - hash', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/test', (req, res, params) => {
+    t.ok('inside the handler')
+  })
+
+  findMyWay.lookup({ method: 'GET', url: '/test#hello' }, null)
+})
diff --git a/test/server.test.js b/test/server.test.js
index aa5afac..17fbcd8 100644
--- a/test/server.test.js
+++ b/test/server.test.js
@@ -17,7 +17,7 @@ test('basic router with http server', t => {
   })
 
   const server = http.createServer((req, res) => {
-    findMyWay.lookup(req.method, req.url, req, res)
+    findMyWay.lookup(req, res)
   })
 
   server.listen(0, err => {
@@ -46,7 +46,7 @@ test('router with params with http server', t => {
   })
 
   const server = http.createServer((req, res) => {
-    findMyWay.lookup(req.method, req.url, req, res)
+    findMyWay.lookup(req, res)
   })
 
   server.listen(0, err => {
@@ -74,7 +74,7 @@ test('default route', t => {
   })
 
   const server = http.createServer((req, res) => {
-    findMyWay.lookup(req.method, req.url, req, res)
+    findMyWay.lookup(req, res)
   })
 
   server.listen(0, err => {
diff --git a/test/store.test.js b/test/store.test.js
new file mode 100644
index 0000000..2aa027e
--- /dev/null
+++ b/test/store.test.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const t = require('tap')
+const test = t.test
+const FindMyWay = require('../')
+
+test('handler should have the store object', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+
+  findMyWay.on('GET', '/test', (req, res, params, store) => {
+    t.is(store.hello, 'world')
+  }, { hello: 'world' })
+
+  findMyWay.lookup({ method: 'GET', url: '/test' }, null)
+})
+
+test('find a store object', t => {
+  t.plan(1)
+  const findMyWay = FindMyWay()
+  const fn = () => {}
+
+  findMyWay.on('GET', '/test', fn, { hello: 'world' })
+
+  t.deepEqual(findMyWay.find('GET', '/test'), {
+    handler: fn,
+    params: {},
+    store: { hello: 'world' }
+  })
+})