routeward v1.0.0
routeward
Routeward provides an easy, relatively literative, declarative way to generate helper methods that return the URL paths your app relies on.
It is inspired by the Phoenix framework's own helper methods for generating routes already specified in the app. By using the helper functions returned from routeward, you can limit the number of places in your app that have hard-coded paths.
Usage
import { routeward } from 'routeward';
const { scope, route, resources } = routeward();
// Define your routes by their top-level scope.
const routes = scope(
'/',
route('/about'),
route('/pages/:pageslug'),
route('/authors/:authorId/posts/:postId'),
scope('/api', resources('/posts', { only: ['index', 'show'] })),
scope('/authenticated', { as: 'auth' }, route('/dashboard')),
);
// You get an object with methods that correspond to the routes
// and resources you specified. Use this anywhere in your app
// that you need to reference a path.
routes.about_path(); // '/about'
routes.pages_path(); // '/pages'
routes.pages_path('show', { pageslug: '123-hello' }); // '/pages/123-hello'
routes.authors_pages_path({ authorId: 'smith', postId: 999 }); // '/authors/smith/posts/999'
routes.api_posts_path(); // '/api/posts'
routes.api_posts_path('show', { id: 456 }); // '/api/posts/456'
routes.auth_dashboard(); // '/authenticated/dashboard'
Contents
Installation
npm install routeward --save
yarn add routeward
Configuration
Configuration options can be passed when you call routeward
in your app. For example,
to change the function name suffix from path
to route
and to use the camelCase
naming convention, you would do:
const { scope, route } = routeward({
case: 'camel',
suffix: 'route',
numericPathPrefix: 'error',
});
const routes = scope('/', route('/apples'), route('/404'));
routes.applesRoute(); // '/apples'
routes.error404Route(); // '/404'
Option Name | Type | Default | Possible Values |
---|---|---|---|
case | string | snake | snake , camel , or title |
suffix | string | path | any string value |
numericPathPrefix | string | go | any string value |
Scopes
Scopes are how you define hierarchy in your routes. All routes generated by routeward must
start with a scope. Most of the time, this will probably be /
to reference the root of your
site or app, but it can be whatever you need.
// The /apples route is included in the '/' scope.
// Calling routes.apples_path() will return '/apples'.
const routes = scope('/', route('/apples'));
// Here, the scope is '/admin'. All routes included in it are scoped to '/admin'.
// Calling routes.admin_dashboard_path() will return '/admin/dashboard'.
const routes = scope('/admin', route('/dashboard'));
The leading slash for scopes and routes is optional.
Alias scopes with as
By default, the scope itself is used when generating the helper function name.
If your scope is /admin
, the dashboard
route within it will get a helper function named
admin_dashbaord_path
.
Sometimes, though, you may want to use a different string in place of the scope itself.
Imagine you have an api with a v1
endpoint that you want to reference as legacy
in your
route helpers. Use the as
scope option:
const routes = scope('/api', { as: 'legacy' }, route('/users'));
routes.legacy_users_path(); // '/api/users'
Routes
The route
function lets you specify a single route. It can be a single or multiple levels,
include parameters, or be a static route. The static (non-parameter) parts of your route are used to
build the helper function returned by routeward.
const routes = scope(
'/',
route('/contact'),
route('/about/history'),
route('/posts/:postid'),
route('/posts/:postid/comments/:commentid'),
);
Object.keys(routes); // ['contact_path', 'about_history_path', 'posts_path', 'posts_comments_path']
about_history_path(); // '/about/history'
Parameters
To build a dynamic route that relies on a value you provide, include a parameter in the route
by prefixing the parameter name with a colon :
. The posts_path
and posts_comments_path
routes
above are dynamic routes. Whatever parameter name you use is expected to be a key on the object
you pass to the route's helper function when you call it.
const routes = scope('/', route('/posts/:postid'));
routes.posts_path({ id: 123 }); // '/posts/123'
Multiple parameters can be specified, but all are required by the route.
Currently, if you don't provide a parameter that exists in the
route, the returned path will include the :parameter
that you specified.
const routes = scope('/', route('/posts/:id/comments/:commentid'));
routes.posts_comments_path({ id: 10, commentid: 3 }); // '/posts/10/comments/3'
routes.posts_comments_path({ id: 10 }); // '/posts/10/comments/:commentid'
Alias routes with as
Just like scopes, individual routes can be aliased with the as
option:
const routes = scope('/', route('/about-us', { as: 'about' }));
routes.about_path(); // '/about-us'
Numeric routes
Because Javascript function names cannot begin with a number, if you define a
route whose first character (after any slash) is numeric, such a /404
, routeward will transform that into a route that begins with go
, by default.
const routes = scope('/', route('/404'));
routes.go_404_path(); // '/404'
You can override the go
default by setting the numericPathPrefix
option
to your preferred prefix when calling routeward:
const { scope, route } = routeward({ numericPathPrefix: 'error' });
Resources
It's not unusual to require the same sorts of routes but for different content entitities.
The resources
function helps you craft those. You can think of it as a kind of macro for
writing route
s.
By default, resources
provides index
, show
, edit
, and delete
routes. id
is
the expected parameter for show
, edit
, and delete
routes.
const routes = scope('/', resources('posts'));
routes.posts_path(); // '/posts'
routes.posts_path('show', { id: 123 }); // /posts/123
routes.posts_path('edit', { id: 123 }); // /posts/123/edit
routes.posts_path('delete', { id: 123 }); // /posts/123/delete
index
is the default path returned. The other types are specified when you call the
route helper function.
Restricting resources with only
and except
From the default set, you can use the only
option to quickly limit the resources
returned.
const routes = scope('/', resources('posts', { only: ['index', 'show'] }));
routes.posts_path(); // '/posts'
routes.posts_path('show', { id: 123 }); // /posts/123
routes.posts_path('edit', { id: 123 }); // Error!
routes.posts_path('delete', { id: 123 }); // Error!
Similarly, you can use except
, too:
const routes = scope('/', resources('posts', { except: ['delete'] }));
routes.posts_path(); // '/posts'
routes.posts_path('show', { id: 123 }); // '/posts/123'
routes.posts_path('edit', { id: 123 }); // '/posts/123/edit'
routes.posts_path('delete', { id: 123 }); // Error!
Use custom resource paths
Not everyone uses /edit
, /delete
resource paths. Specify your own by passing them as the second argument to resources
instead of the only/except restrictions object:
const routes = scope('/', resources('/apples', ['view', 'export']));
routes.apples_path('view', { id: 123 }); // '/apples/123/view'
routes.apples_path('export', { id: 123 }); // '/apples/123/export'
// 'index' and 'show' paths are automatically included
routes.apples_path(); // '/apples'
routes.apples_path('show', { id: 123 }); // '/apples/123'
The index
and show
resource paths are automatically included in any custom paths
you provide. If you wish to exclude them, you can still do so by providing the same
only/exclude restrictions object as the last argument to resources
:
const routes = scope(
'/',
resources('/apples', ['view', 'export'], { exclude: ['show'] }),
);
routes.apples_path('show', { id: 123 }); // Error!
4 years ago