Comparing version 1.7.0 to 1.8.0
{ | ||
"name": "anumargak", | ||
"version": "1.7.0", | ||
"version": "1.8.0", | ||
"description": "Amazing fast multipurpose simple to use web/ HTTP router", | ||
@@ -36,4 +36,4 @@ "main": "./src/letsRoute.js", | ||
"find-my-way": "^1.15.1", | ||
"jasmine": "^3.1.0", | ||
"jasmine-core": "^3.1.0", | ||
"jasmine": "^3.2.0", | ||
"jasmine-core": "^3.2.0", | ||
"mock-req": "^0.2.0", | ||
@@ -45,3 +45,9 @@ "nyc": "^11.9.0" | ||
"semver-store": "^0.3.0" | ||
}, | ||
"nyc": { | ||
"exclude": [ | ||
"tests", | ||
"build" | ||
] | ||
} | ||
} |
187
README.md
@@ -19,19 +19,16 @@ # अनुमार्गक (anumargak) | ||
* Fastest node js router (as far as Google & I know) | ||
* Framework independent | ||
* Supports static and dynamic both type of URLs | ||
* Framework independent. | ||
* Supports static and dynamic both type of URLs. | ||
* Supports path parameter with defined type `\this\is\:num([0-9]+)` | ||
* Support multiple path parameters | ||
* Support multiple path parameters. | ||
* Handles enumerated URLs `\login\as\:role(admin|staff|user)` | ||
* Supports wildchar `\this\is\*`, `\this\is\wild*` | ||
* You nee not to register 2 separate routes for trailing slash. `\like\me\` and `\like\me`. | ||
* You need not to register 2 separate routes for trailing slash. `\like\me\` and `\like\me`. | ||
* You may skip leading slash while registering the route, though not recommended. `\like\me` and `like\me`. | ||
* Capture parameters' value for dynamic URLs. | ||
* Warn (by default) or silently overwrites (when `overwriteAllow : true`) same or similar URLs | ||
* `\this\is\static` and `\this\is\static` | ||
* `\this\:param\is\dynamic` and `\this\param\:is\dynamic` | ||
* `\this\is\:uid([a-zA-Z0-9]+)` and `\this\is\:num([0-9]+)` | ||
* Add similar but not same URLs | ||
* `\this\is\:age([0-9]+)` and `\this\is\:name([a-zA-Z]+)` | ||
* Support of shorthand methods | ||
* Add similar but not same URLs : `\this\is\:age([0-9]+)` and `\this\is\:name([a-zA-Z]+)` | ||
* Support shorthand methods | ||
* You can always have a count on registered routes. | ||
* Supports versioned routes | ||
* Supports versioned routes. | ||
* You can name regular expressions to increase code redability. | ||
@@ -41,50 +38,53 @@ ## Usage | ||
```js | ||
const http = require('http') | ||
const router = require('anumargak')({ | ||
defaultRoute : defaultHandler, | ||
defaultRoute : defaultHandler,//it'll be called when no route matches. If it is not set the we'll set statusCode to 404 | ||
ignoreTrailingSlash: true, | ||
ignoreLeadingSlash: true, | ||
}); | ||
}) | ||
anumargak.on("GET", "/this/is/static", handler); | ||
anumargak.on(["POST","PUT"], "/this/is/static", handler);//supports array | ||
anumargak.on("GET", "/this/is/:dynamic", handler); | ||
anumargak.on("GET", "/this/is/:dynamic", handler);//it will overwrite old mapping | ||
anumargak.on("GET", "/this/is/:dynamic/with/:pattern(\\d+)", handler); | ||
//Eg: params = { dynamic : val, pattern: 123} | ||
anumargak.on("GET", "/this/is/:dynamic/with/:two-:params", handler);//use - to separate multiple parameters | ||
anumargak.on("GET", "/this/is/:dynamic/with/:two(\\d+):params", handler); | ||
anumargak.on("GET", "/this/is/:dynamic/with/:two(\\d+)rest", handler); | ||
anumargak.on("GET", "/similar/:string([a-z]{10})", handler); | ||
anumargak.on("GET", "/similar/:number([0-9]{10})", handler);//above route is different from this | ||
router.on('GET', '/', (req, res, params) => { | ||
//process the request response here | ||
}) | ||
const server = http.createServer((req, res) => { | ||
router.lookup(req, res) | ||
}) | ||
anumargak.find("GET","/this/is/static");//will return handler | ||
anumargak.find("GET","/this/is/dynamic/with/123?ignore=me");//ignore query parameters and hashtag part automatically | ||
server.listen(3000); | ||
``` | ||
anumargak.lookup(req,res) ;//will execute handler with req,res and params(for dynamic URLs) as method parameters | ||
## on(method, url [, options] , handler [, store] ) | ||
console.log(anumargak.count); //Print number of unique routes added | ||
To register a route. | ||
```JavaScript | ||
router.on("GET", "/this/is/static", handler); | ||
router.on(["POST","PUT"], "/this/is/static", handler); | ||
``` | ||
Example with server | ||
```js | ||
const http = require('http') | ||
const router = require('anumargak')() | ||
### Dynamic URL | ||
router.on('GET', '/', (req, res, params) => { | ||
//process the request response here | ||
}) | ||
You can register dynamic url with multiple path paramters | ||
const server = http.createServer((req, res) => { | ||
router.lookup(req, res) | ||
}) | ||
```JavaScript | ||
router.on("GET", "/this/is/:dynamic", handler); | ||
router.on("GET", "/this/is/:dynamic", handler);//it will error | ||
router.on("GET", "/this/is/:dynamic/with/:pattern(\\d+)", handler); | ||
//Eg: params = { dynamic : val, pattern: 123} | ||
router.on("GET", "/this/is/:dynamic/with/:two-:params", handler);//use - to separate multiple parameters | ||
router.on("GET", "/this/is/:dynamic/with/:two(\\d+):params", handler);//multiple parameters | ||
router.on("GET", "/this/is/:dynamic/with/:two(\\d+)rest", handler);//single parameter | ||
``` | ||
server.listen(3000, err => { | ||
if (err) throw err | ||
console.log('Server listening on: http://localost:3000') | ||
}) | ||
### Enumerated URL | ||
Anumargak handls enumerated URLs in static way. Because static URLs can be looked up faster than dynamic URLs. | ||
```js | ||
router.on("GET", "/login/as/:role(admin|user|staff)", handler); | ||
``` | ||
**wildcard**: wild cards are helpful when a route handler wants to control all the underlying paths. Eg. a handler registered with `/help*` may take care of all the help pages and static resources under the same path. | ||
### wildcard | ||
wild cards are helpful when a route handler wants to control all the underlying paths. Eg. a handler registered with `/help*` may take care of all the help pages and static resources under the same path. You can check [आलेख (Aalekh)](https://github.com/muneem4node/aalekh) for live example. | ||
@@ -95,3 +95,3 @@ ```js | ||
//this/is/juglee/and/wild/and/unknown | ||
anumargak.on("GET", "/this/is/:dynamic/and/*", handler); | ||
router.on("GET", "/this/is/:dynamic/and/*", handler); | ||
@@ -101,6 +101,6 @@ //this/is/juglee/and/wild | ||
//this/is/juglee/and/wild/and/unknown | ||
anumargak.on("GET", "/this/is/:dynamic/and/wild*", handler); | ||
router.on("GET", "/this/is/:dynamic/and/wild*", handler); | ||
``` | ||
**shorthand methods** | ||
### shorthand methods | ||
@@ -117,4 +117,83 @@ ```js | ||
## Similar but not same URLs | ||
## off(method, url [, version] ) | ||
To remove a registered route. If no route is found no error will be thrown. | ||
```JavaScript | ||
anumargak.off("GET", "/this/is/static"); | ||
anumargak.off("GET", "/this/is/:dynamic"); | ||
anumargak.off("GET", "/this/is/*/really/wild"); | ||
anumargak.off("GET", "/login/as/:role(admin|user|staff)"); //it'll delete all the versions | ||
``` | ||
### Enumerated URLs | ||
an enumerated URL can be deleted multi steps | ||
```JavaScript | ||
anumargak.off("GET", "/login/as/:role(admin|user|staff)"); | ||
//or | ||
anumargak.off("GET", "/login/as/admin"); | ||
anumargak.off("GET", "/login/as/user"); | ||
anumargak.off("GET", "/login/as/staff"); | ||
//or | ||
anumargak.off("GET", "/login/as/:role(user|staff)"); | ||
anumargak.off("GET", "/login/as/admin"); | ||
``` | ||
### Versioned URLs | ||
version can be provided as an additional parmeter. Valid values are: | ||
* 1.2.3 : It'll delete only one URL | ||
* 1.2.x : It'll delete all the URLs with different patche versions : 1.2.0, 1.2.1 ... | ||
* 1.x : It'll delete all the URLs with different minor versions : 1.2.0, 1.2.1, 1.3.5 ... | ||
```JavaScript | ||
anumargak.off("GET", "/this/is/static", version); | ||
``` | ||
Please **note** that, if you delete a route without specifying versions then all the versioned routes will also be deleted. | ||
## find(method, url [, version]) | ||
To find a registered route. It returns; | ||
```JavaScript | ||
{ | ||
handler : function(){}, //registered function | ||
params : {}, //path parameters | ||
store : any // extra data provided at the time of registering the route | ||
} | ||
``` | ||
## quickFind(method, url [, version]) | ||
To find a registered route. It returns; | ||
```JavaScript | ||
{ | ||
handler : function(){}, //registered function | ||
store : any // extra data provided at the time of registering the route | ||
} | ||
``` | ||
`quickFind()` is faster than `find()`. | ||
## lookup(request, response) | ||
This method reads *request* object to fetch url, method, and `accept-version` header to find matching route and then run the handler. | ||
The handler should accept: request, response, and params. params is an object of path parameters. | ||
## count | ||
You can always check how many routes are registered. If you delete some routes count will be decreased. | ||
## Other detail | ||
### Similar but not same URLs | ||
You can register the URLs which look similar but not exactly same. | ||
```js | ||
@@ -127,6 +206,10 @@ const anumargak = require('anumargak')() | ||
anumargak.on("GET", "/this/is/my/:name([a-zA-z]+)", handler); | ||
anumargak.on("GET", "/login/as/:role(admin|user|staff)", handler); | ||
anumargak.on("GET", "/login/as/:role(developer|tester|hacker)", handler); | ||
``` | ||
## Named Expressions | ||
### Named Expressions | ||
Anumargak lets you add named expressions. You can use them at the time of registering the route. | ||
@@ -153,3 +236,3 @@ | ||
## accept-version | ||
### accept-version | ||
@@ -215,1 +298,5 @@ Same routes can be registerd with different versions. Lookup method reads `accept-version` header to read the version or you can pass the version in find method directly. | ||
- <img src="https://avatars3.githubusercontent.com/u/4491530?v=4" width="20" height="20"/> [shuklajay117](https://github.com/shuklajay117) | ||
## Disclaimer | ||
I initially used *find-my-way* npm package for [मुनीम (Muneem)](https://github.com/muneem4node/muneem) framework. But then I realized that lookup for static URLs is comparitively slower. Hence I develop this library. If you notice, I tried to keep the naming convention and syntaxes common wherever possible to reduce the time to switch from one library to another and to keep learning curve smaller. |
var { getFirstMatche, getAllMatches, doesMatch, urlSlice } = require("./util"); | ||
var namedExpressionsStore = require("./namedExpressionsStore"); | ||
var semverStore = require("semver-store"); | ||
var semverStore = require("./semver-store"); | ||
@@ -18,5 +18,6 @@ var httpMethods = ["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS", "PATCH", "TRACE", "CONNECT", "COPY", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND", "VIEW"]; | ||
*/ | ||
Anumargak.prototype.on = function (method, url, options, fn) { | ||
Anumargak.prototype.on = function (method, url, options, fn, extraData) { | ||
if (typeof options === 'function') { | ||
extraData = fn; | ||
fn = options; | ||
@@ -27,6 +28,6 @@ options = {}; | ||
if (typeof method === "string") { | ||
this.normalizeUrl(method, url, options, fn); | ||
this._on(method, url, options, fn, extraData); | ||
} else if (Array.isArray(method)) { | ||
for (var i = 0; i < method.length; i++) { | ||
this.normalizeUrl(method[i], url, options, fn); | ||
this._on(method[i], url, options, fn, extraData); | ||
} | ||
@@ -36,2 +37,4 @@ } else { | ||
} | ||
return this; | ||
} | ||
@@ -44,3 +47,4 @@ | ||
Anumargak.prototype.normalizeUrl = function (method, url, options, fn) { | ||
Anumargak.prototype._on = function (method, url, options, fn, extraData) { | ||
@@ -51,2 +55,8 @@ //validate for correct input | ||
url = this.normalizeUrl(url); | ||
this._addRoute(method, url, options, fn, extraData); | ||
} | ||
Anumargak.prototype.normalizeUrl = function (url) { | ||
//Normalize URL | ||
@@ -66,3 +76,3 @@ if (this.ignoreLeadingSlash) { | ||
this._addRoute(method, url, options, fn); | ||
return url; | ||
} | ||
@@ -73,12 +83,14 @@ | ||
*/ | ||
Anumargak.prototype._addRoute = function (method, url, options, fn, params) { | ||
Anumargak.prototype._addRoute = function (method, url, options, fn, extraData, params) { | ||
var found = this._checkForEnum(method, url, options, fn, params); | ||
if(found) return; | ||
var matches = getAllMatches(url, paramRegexStr); | ||
if (matches.length > 0) {//DYNAMIC | ||
this._addDynamic(method, url, options, fn, params, matches); | ||
} else {//STATIC | ||
this._addStatic(method, url, options, fn, params, this.ignoreTrailingSlash); | ||
var done = this._checkForEnum(method, url, options, fn, extraData, params); | ||
if( done ) { //All the enumerated URLs are registered | ||
return; | ||
}else{ | ||
var matches = getAllMatches(url, paramRegexStr); | ||
if (matches.length > 0) {//DYNAMIC | ||
this._addDynamic(method, url, options, fn, extraData, params, matches); | ||
} else {//STATIC | ||
this._addStatic(method, url, options, fn, extraData, params, this.ignoreTrailingSlash); | ||
} | ||
} | ||
@@ -95,3 +107,3 @@ } | ||
*/ | ||
Anumargak.prototype._checkForEnum = function(method, url, options, fn, params){ | ||
Anumargak.prototype._checkForEnum = function(method, url, options, fn, extraData, params){ | ||
var matches = getFirstMatche(url, enumRegexStr); | ||
@@ -112,3 +124,3 @@ if (matches) { | ||
} | ||
this._addRoute(method, newurl, options, fn, params); | ||
this._addRoute(method, newurl, options, fn, extraData, params); | ||
} | ||
@@ -119,10 +131,11 @@ return true; | ||
Anumargak.prototype._addStatic = function(method, url, options, fn, params, ignoreTrailingSlash){ | ||
Anumargak.prototype._addStatic = function(method, url, options, fn, extraData, params, ignoreTrailingSlash){ | ||
this.checkIfRegistered(this.staticRoutes, method, url, options, fn); | ||
var routeData = this.getRouteData(this.staticRoutes[method][url], method, url, options, fn); | ||
var routeHandlers = this.getRouteHandlers(this.staticRoutes[method][url], method, url, options, fn); | ||
this.staticRoutes[method][url] = { | ||
fn : routeData.handler, | ||
verMap: routeData.verMap, | ||
params: params | ||
fn : routeHandlers.handler, | ||
verMap: routeHandlers.verMap, | ||
params: params, | ||
store: extraData | ||
}; | ||
@@ -138,7 +151,8 @@ | ||
var routeData = this.getRouteData(this.staticRoutes[method][url], method, url, options, fn); | ||
var routeHandlers = this.getRouteHandlers(this.staticRoutes[method][url], method, url, options, fn); | ||
this.staticRoutes[method][url] = { | ||
fn : routeData.handler, | ||
verMap: routeData.verMap, | ||
params: params | ||
fn : routeHandlers.handler, | ||
verMap: routeHandlers.verMap, | ||
params: params, | ||
store: extraData | ||
}; | ||
@@ -150,3 +164,21 @@ | ||
Anumargak.prototype._addDynamic = function(method, url, options, fn, params, matches){ | ||
Anumargak.prototype._addDynamic = function(method, url, options, fn, extraData, params, matches){ | ||
var normalizedUrl = normalizeDynamicUrl(url, matches, this.ignoreTrailingSlash); | ||
url = normalizedUrl.url; | ||
this.checkIfRegistered(this.dynamicRoutes, method, url, options, fn); | ||
var routeHandlers = this.getRouteHandlers(this.dynamicRoutes[method][url], method, url, options, fn); | ||
var regex = new RegExp("^" + url + "$"); | ||
this.dynamicRoutes[method][url] = { | ||
fn: routeHandlers.handler, | ||
regex: regex, | ||
verMap: routeHandlers.verMap, | ||
params: params || {}, | ||
paramsArr: normalizedUrl.paramsArr , | ||
store: extraData | ||
}; | ||
} | ||
var normalizeDynamicUrl = function (url, matches, ignoreTrailingSlash ) { | ||
var paramsArr = []; | ||
@@ -171,3 +203,3 @@ for (var i = 0; i < matches.length; i++) { | ||
if (this.ignoreTrailingSlash) { | ||
if ( ignoreTrailingSlash) { | ||
if (url.endsWith("/")) { | ||
@@ -179,19 +211,10 @@ url = url + "?"; | ||
} | ||
var regex = new RegExp("^" + url + "$"); | ||
this.checkIfRegistered(this.dynamicRoutes, method, url, options, fn); | ||
var routeData = this.getRouteData(this.dynamicRoutes[method][url], method, url, options, fn); | ||
this.dynamicRoutes[method][url] = { | ||
fn: routeData.handler, | ||
regex: regex, | ||
verMap: routeData.verMap, | ||
params: params || {}, | ||
paramsArr: paramsArr | ||
}; | ||
return { | ||
paramsArr : paramsArr, | ||
url : url | ||
}; | ||
} | ||
Anumargak.prototype.getRouteData = function (route, method, url, options, fn) { | ||
Anumargak.prototype.getRouteHandlers = function (route, method, url, options, fn) { | ||
if(options.version){ | ||
@@ -280,38 +303,49 @@ var verMap, handler; | ||
Anumargak.prototype.find = function (method, url, version) { | ||
Anumargak.prototype.quickFind = function (method, url, version) { | ||
url = urlSlice(url); | ||
var result = this.staticRoutes[method][url]; | ||
if (result) return this.getHandler(result, version); | ||
else { | ||
if (result) { | ||
return { | ||
handler: this.getHandler(result, version), | ||
store : result.store | ||
} | ||
}else { | ||
var urlRegex = Object.keys(this.dynamicRoutes[method]); | ||
for (var i = 0; i < urlRegex.length; i++) { | ||
if (this.dynamicRoutes[method][urlRegex[i]].regex.exec(url)) | ||
return this.getHandler( this.dynamicRoutes[method][ urlRegex[i] ], version); | ||
if (this.dynamicRoutes[method][urlRegex[i]].regex.exec(url)){ | ||
var result = this.dynamicRoutes[method][ urlRegex[i] ]; | ||
return { | ||
handler: this.getHandler( result, version), | ||
//params: result.params, | ||
store: result.store | ||
} | ||
} | ||
} | ||
} | ||
return this.defaultFn; | ||
return null; | ||
} | ||
Anumargak.prototype.getHandler = function (route, version) { | ||
if(version){ | ||
return route.verMap.get(version); | ||
}else{ | ||
return route.fn; | ||
} | ||
} | ||
Anumargak.prototype.lookup = function (req, res) { | ||
var method = req.method; | ||
var url = urlSlice(req.url); | ||
var version = req.headers['accept-version']; | ||
var result = this._lookup(url, method, version); | ||
result.fn(req, res, result.params); | ||
var result = this.find(method, req.url, version); | ||
if(result === null){ | ||
this.defaultFn(req, res); | ||
}else{ | ||
result.handler(req, res, result.params); | ||
} | ||
} | ||
Anumargak.prototype._lookup = function (url, method, version) { | ||
Anumargak.prototype.find = function (method, url, version) { | ||
url = urlSlice(url); | ||
var result = this.staticRoutes[method][url]; | ||
if (result) return { fn: this.getHandler(result, version), params: result.params }; | ||
else { | ||
if (result) { | ||
return { | ||
handler: this.getHandler(result, version), | ||
params: result.params, | ||
store: result.store | ||
}; | ||
}else { | ||
var urlRegex = Object.keys(this.dynamicRoutes[method]); | ||
@@ -326,9 +360,87 @@ for (var i = 0; i < urlRegex.length; i++) { | ||
} | ||
return { fn: this.getHandler(this.dynamicRoutes[method][urlRegex[i]], version), params: params }; | ||
var result = this.dynamicRoutes[method][urlRegex[i]]; | ||
return { | ||
handler: this.getHandler(result, version), | ||
params: params, | ||
store: result.store | ||
}; | ||
} | ||
} | ||
} | ||
return { fn: this.defaultFn }; | ||
return null; | ||
} | ||
Anumargak.prototype.getHandler = function (route, version) { | ||
if(version){ | ||
return route.verMap.get(version); | ||
}else{ | ||
return route.fn; | ||
} | ||
} | ||
Anumargak.prototype.off = function (method, url, version) { | ||
url = this.normalizeUrl(url); | ||
var done = this.removeEnum(method, url); | ||
if(done) return; | ||
var matches = getAllMatches(url, paramRegexStr); | ||
var result; | ||
if (matches.length > 0) {//DYNAMIC | ||
url = normalizeDynamicUrl(url, matches, this.ignoreTrailingSlash).url; | ||
result = this.isRegistered(this.dynamicRoutes, method, url); | ||
} else {//STATIC | ||
result = this.isRegistered(this.staticRoutes, method, url); | ||
} | ||
if (result) { | ||
if(version ){ | ||
var route; | ||
if( this.dynamicRoutes[method][result] ){ | ||
route = this.dynamicRoutes[method][result]; | ||
}else { | ||
route = this.staticRoutes[method][result]; | ||
} | ||
if(route.verMap && route.verMap.get( version )){ | ||
var delCount = route.verMap.delete( version ); | ||
this.count -= delCount; | ||
} | ||
}else{ | ||
if( this.dynamicRoutes[method][result] ){ | ||
if( this.dynamicRoutes[method][result].verMap ){ | ||
this.count -= this.dynamicRoutes[method][result].verMap.count(); | ||
} | ||
delete this.dynamicRoutes[method][result]; | ||
this.count--; | ||
}else { | ||
if( this.staticRoutes[method][result].verMap ){ | ||
this.count -= this.staticRoutes[method][result].verMap.count(); | ||
} | ||
delete this.staticRoutes[method][result]; | ||
this.count--; | ||
} | ||
} | ||
} | ||
} | ||
Anumargak.prototype.removeEnum = function(method, url){ | ||
var matches = getFirstMatche(url, enumRegexStr); | ||
if (matches) { | ||
var name = matches[1]; | ||
var pattern = matches[3]; | ||
var arr = pattern.split("\|"); | ||
for (var i = 0; i < arr.length; i++) { | ||
var newurl = url.replace(matches[0], arr[i]); | ||
this.off(method, newurl); | ||
this.count++; | ||
} | ||
this.count--; | ||
return true ; | ||
} | ||
} | ||
/** | ||
@@ -422,2 +534,4 @@ * Adds routes for GET method and URL | ||
this.defaultFn = options.defaultRoute; | ||
}else{ | ||
this.defaultFn = defaultRoute; | ||
} | ||
@@ -430,2 +544,6 @@ this.ignoreTrailingSlash = options.ignoreTrailingSlash || false; | ||
function defaultRoute(req, res) { | ||
res.statusCode = 404 | ||
res.end() | ||
} | ||
module.exports = Anumargak; |
74079
13
710
294