1.0.0 • Published 4 years ago

routeward v1.0.0

Weekly downloads
1
License
MIT
Repository
github
Last release
4 years ago

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 NameTypeDefaultPossible Values
casestringsnakesnake, camel, or title
suffixstringpathany string value
numericPathPrefixstringgoany 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 routes.

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!