node-laravel-router
v2.0.1
Published
A Laravel-inspired router for node.js apps
Downloads
35
Maintainers
Readme
node-laravel-router
A Laravel-inspired router for node.js apps.
(Forked from Express Laravel Router)
Motivation
This router is an alternative to routers such as the one that ships with express.js.
Instead of manually creating instances of express.Router
, for example,
you can define your routes in group closures,
where it becomes easier to create and reason about the shared properties of your routes.
Also, this router allows you to execute custom code for each route definition, which can be useful for many things, e.g. injecting dependencies into each request handler, or automatically creating a swagger/openapi spec from your routes.
There are also some extra features like being able to name and generate urls strings for each route.
To summarize:
- Easily create and organize route groups
- Execute custom code for each route definition
- Generate urls for a given route definition
Installation
npm install node-laravel-router --save
Quickstart
The examples below will create two routes:
- A GET to
/api/v1/users/{userId}
- A POST to
/api/v1/auth
Express app example
const express = require('express');
const createRouter = require('node-laravel-router').createRouter;
const app = express();
const router = createRouter(app);
router.group('/api', (router) => {
router.group('/v1', (router) => {
router.group('/users', (router) => {
router.get('/{userId}', (req, res) => { /* request handler logic */ });
});
router.post('/auth', (req, res) => { /* request handler logic */ });
});
});
To create the above example in pure express.js, it would look something like the following:
const express = require('express');
const app = express();
const apiRouter = express.Router();
const v1Router = express.Router();
const usersRouter = express.Router();
usersRouter.get('/:userId', (req, res) => { /* request handler logic */ });
v1Router.use('/users', usersRouter);
v1Router.get('/auth', (req, res) => { /* request handler logic */ });
apiRouter.use('/v1', v1Router);
app.use('/api', apiRouter);
The pure express.js version is not only visually harder to reason about, but it becomes increasingly more complex as more routes and middleware are added.
Generic Node app example
const createRouter = require('node-laravel-router').createRouter;
const router = createRouter();
router.group('/api', (router) => {
router.group('/v1', (router) => {
router.group('/users', (router) => {
router.get('/{userId}', (req, res) => { /* request handler logic */ });
});
router.post('/auth', (req, res) => { /* request handler logic */ });
});
});
// When we pass an Express (or Express-type) app, like in the Express app example above,
// routing is automatically applied once we call the router's methods.
// With our generic, non-Express app, however, routing is not automatically.
// To apply the routing, we have to call the router's apply method as follows.
// Let's assume we are using an Express app:
router.apply((route) => {
// Do something with route.
// For example, assuming we have a connect-middleware-supported app
// stored in an `app` variable:
const { method, path, handlers } = route;
app[method](path, handlers);
});
Usage
Our quickstart example used strings as the first argument for both the router.group
and router.get
methods. You are
also able to supply an options
object instead, for more powerful functionality. Supplying just a string is actually a
shortcut to setting the prefix
option in the group options
object, and the uri
option in the route options
object.
Group options
The full group options
object with their default values looks like this:
{
"prefix": "/", // url prefix shared by all routes in this group
"middleware": [], // middleware shared by all routes in this group
"namespace": "", // namespace shared by all named routes in this group
"patterns": {}, // regex patterns shared by all route params in this group
"meta": {} // additional meta data to associate to all routes in this group
}
Note that all fields are optional, and any combination of fields can be used.
Also note that the following are all equivalent:
router.group({ prefix: '/api' }, (router) => {
});
// shortcut to the above
router.group('/api', (router) => {
});
For more details, see the wiki page.
Route options
The full route options
object with their default values looks like this:
{
"method": "get", // the HTTP method for this route definition
"uri": "/", // the url fragment for this route definition
"middleware": [], // the middleware specific to this route definition
"name": "", // a name to associate to this route definition
"patterns": {}, // any patterns specific to this route definition
"meta": {} // any additional meta data to associate to this route definition
}
Note that all fields are optional, and any combination of fields can be used.
Also note that the following are all equivalent:
router.route({ method: 'get', uri: '/api' }, (req, res) => {
});
// the default method is "get"
router.route({ uri: '/api' }, (req, res) => {
});
// the default method is "get", and if a string is used instead of an object, that string becomes the uri option.
router.route('/api', (req, res) => {
});
// using router.{method} prefills the options object with the correct method.
router.get({ uri: '/api' }, (req, res) => {
});
// shortcut to the above
router.get('/api', (req, res) => {
});
For more details, see the wiki page.
Full API
Below are all the methods available on a router
.
router.group(options|prefix, closure)
Creates a route group and provides a new router
instance inside the closure
to perform additional routing inside that group.
router.route(options|uri, action)
Creates a route definition and associates an action
with it. The action
is then passed to a mapActionToHandler
function, whose job it is to return the familiar express.js requestHandler function, whose signature is:
(req, res) => {}
The default mapActionToHandler
function assumes that the action
passed to router.route
is already an express.js
requestHandler function. This behavior can be changed by supplying a new mapActionToHandler
function to createRouter
:
const mapActionToHandler = (action, routeDescription, routeOptions) => {
return action.customHandler;
};
const router = createRouter(app, mapActionToHandler);
router.route('/users', { customHandler: (req, res) => {} });
The above example now expects that the action
is an object with a customHandler
property.
router.{method}(options|uri, action)
Instead of supplying a method
in the options of router.route
, you can simply call router.{method}
, which will
set the proper method
field in the options.
router.route({ method: 'post', uri: '/create' }, (req, res) => {
});
// shortcut to the above
router.post('/create', (req, res) => {
});
router.serve(uri, staticMiddleware)
Creates a route that serves static files.
router.serve('/assets', express.static('./public/assets'));
router.url(name, params={}, options={})
Creates and returns a url for the route definition with the given name. If that route contains params, you can pass in
values to fill in the params in the optional params
object. Any extra fields found in the params
object but not found
in the route definition will be considered query params, and will be appended to the url as a query string.
Query strings are generated via the qs module, and so any options that you'd like to
pass to qs.stringify
can be done via the optional options
object.
If there are any patterns on the params, they must be honored by the supplied params, or an error will be thrown.
router.group({ prefix: '/user/{userId}', namespace: 'user.' }, (router) => {
router.get({ uri: '/friend/{friendId}', name: 'getFriend' }, (req, res) => {
});
});
const url = router.url('user.getFriend', { userId: 1, friendId: 2, foo: 'bar' });
// url will equal /user/1/friend/2?foo=bar
router.get({
uri: '/user/{userId}',
name: 'getUser',
patterns: {
userId: /^\d+$/
}
}, (req, res) => {
});
const url = router.url('getUser', { userId: "one"}); // this will throw an error, because userId is expected to be a number.
router.app
Grants access to the express app
object that was passed in to createRouter
.
Extras
In addition to the createRouter
function, this package also exports laravelToExpress
and uriWithParams
functions.
laravelToExpress(uri = '', patterns = {})
Accepts a string uri written in the Laravel way (e.g. /user/{userId}
) and an optional object of regex patterns,
and returns the express.js version (e.g. /user/:userId
).
uriWithParams(uri = '', params = {}, patterns = {}, options = {})
Accepts a string uri with optional params (e.g. /user/{userId}
). If that uri contains params, you can pass in
values to fill in the params in the optional params
object. Any extra fields found in the params
object but not found
in the route definition will be considered query params, and will be appended to the url as a query string.
Query strings are generated via the qs module, and so any options that you'd like to
pass to qs.stringify
can be done via the optional options
object.
If there are any patterns on the params, they must be honored by the supplied params, or an error will be thrown.
paramsFromUri(uri = '')
Accepts a string uri and will return an object that includes and array of required and optional params found in the uri.
const uri = '/users/{userId}/friends/{friendId}/{username?}';
const params = paramsFromUri(uri);
/**
* params looks like:
* { required: ["userId", "friendId"], optional: ["username"] }
*/
Differences to Laravel
Unlike Laravel routes, chaining is discarded in favor of objects containing options. I found this to be a much clearer API.
Additionally, some of Laravel's naming has been updated or repurposed for clarity, e.g. Laravel's "as" has become "name" in route definitions, and "namespace" in route groups.