Skip to content

Latest commit

 

History

History
340 lines (266 loc) · 9.85 KB

api.md

File metadata and controls

340 lines (266 loc) · 9.85 KB

Universal Router API

const router = new UniversalRouter(routes, options)

Creates an universal router instance which have a single router.resolve() method. UniversalRouter constructor expects a plain javascript object for the first routes argument with any amount of params where only path is required, or array of such objects. Second options argument is optional where you can pass the following:

  • context - The object with any data which you want to pass to resolveRoute function.
    See Context section below for details.
  • baseUrl - The base URL of the app. By default is empty string ''.
    If all the URLs in your app are relative to some other "base" URL, use this option.
  • resolveRoute - The function for any custom route handling logic.
    For example you can define this option to work with routes in declarative manner.
    By default the router calls the action function of matched route.
import UniversalRouter from 'universal-router';

const routes = {
  path: '/page',            // string or regexp or array of them, optionalname: 'page',             // unique string, optional
  parent: null,             // route object or null, automatically filled by the router
  children: [],             // array of route objects or null, optionalaction(context, params) { // function, optional

    // action method should return anything except `null` or `undefined` to be resolved by router
    // otherwise router will throw `Page not found` error if all matched routes returned nothing
    return '<h1>The Page</h1>';
  },
  // ...
};

const options = {
  context: { store: {} },
  baseUrl: '/base',
  resolveRoute(context, params) {
    if (typeof context.route.action === 'function') {
      return context.route.action(context, params);
    }
    return null;
  }
};

const router = new UniversalRouter(routes, options);

router.resolve({ pathname, ...context })Promise<any>

Traverses the list of routes in the order they are defined until it finds the first route that matches provided URL path string and whose action function returns anything other than null or undefined.

const router = new UniversalRouter([
  {
    path: '/one',
    action: () => 'Page One',
  },
  {
    path: '/two',
    action: () => `Page Two`,
  },
]);

router.resolve({ pathname: '/one' })
  .then(result => console.log(result));
  // => Page One

Where action is just a regular function that may, or may not, return any arbitrary data — a string, a React component, anything!

Nested Routes

Each route may have an optional children: [ ... ] property containing the list of child routes:

const router = new UniversalRouter({
  path: '/admin',
  children: [
    {
      path: '',                        // www.example.com/admin
      action: () => 'Admin Page',
    },
    {
      path: '/users',
      children: [
        {
          path: '',                    // www.example.com/admin/users
          action: () => 'User List',
        },
        {
          path: '/:username',          // www.example.com/admin/users/john
          action: () => 'User Profile',
        },
      ],
    },
  ],
});

router.resolve({ pathname: '/admin/users/john' })
  .then(result => console.log(result));
  // => User Profile

URL Parameters

Named route parameters are captured and added to context.params.

const router = new UniversalRouter({
  path: '/hello/:username',
  action: (context) => `Welcome, ${context.params.username}!`,
});

router.resolve({ pathname: '/hello/john' })
  .then(result => console.log(result));
  // => Welcome, john!

Alternatively, captured parameters can be accessed via the second argument to an action method like so:

const router = new UniversalRouter({
  path: '/hello/:username',
  action: (ctx, { username }) => `Welcome, ${username}!`,
});

router.resolve({ pathname: '/hello/john' })
  .then(result => console.log(result));
  // => Welcome, john!

Router preserves the context.params values from the parent router. If the parent and the child have conflicting param names, the child's value take precedence.

This functionality is powered by path-to-regexp npm module and works the same way as the routing solutions in many popular JavaScript frameworks such as Express and Koa. Also check out online router tester.

Context

In addition to a URL path string, any arbitrary data can be passed to the router.resolve() method, that becomes available inside action functions.

const router = new UniversalRouter({
  path: '/hello',
  action(context) {
    return `Welcome, ${context.user}!`;
  },
});

router.resolve({ pathname: '/hello', user: 'admin' })
  .then(result => console.log(result));
  // => Welcome, admin!

Router supports context option in the UniversalRouter constructor to support for specify of custom context properties only once.

const context = {
  store: {},
  user: 'admin',
  // ...
};

const router = new UniversalRouter(route, { context });

Router always adds following parameters to the context object before passing it to the resolveRoute function:

  • router - Current router instance.
  • route - Matched route object.
  • next - Middleware style function which can continue resolving, see Middlewares section below for details.
  • pathname - URL which was transmitted to router.resolve().
  • baseUrl - Base URL path relative to the path of the current route.
  • path - Matched path.
  • params - Matched path params, see URL Parameters section above for details.
  • keys - An array of keys found in the path, see path-to-regexp documentation for details.

Async Routes

The router works great with asynchronous functions out of the box!

const router = new UniversalRouter({
  path: '/hello/:username',
  async action({ params }) {
    const resp = await fetch(`/api/users/${params.username}`);
    const user = await resp.json();
    if (user) return `Welcome, ${user.displayName}!`;
  },
});

router.resolve({ pathname: '/hello/john' })
  .then(result => console.log(result));
  // => Welcome, John Brown!

Use Babel to transpile your code with async / await to normal JavaScript. Alternatively, stick to ES6 Promises:

const route = {
  path: '/hello/:username',
  action({ params }) {
    return fetch(`/api/users/${params.username}`)
      .then(resp => resp.json())
      .then(user => user && `Welcome, ${user.displayName}!`);
  },
};

Middlewares

Any route action function may act as a middleware by calling context.next().

const router = new UniversalRouter({
  path: '', // optional
  async action({ next }) {
    console.log('middleware: start');
    const child = await next();
    console.log('middleware: end');
    return child;
  },
  children: [
    {
      path: '/hello',
      action() {
        console.log('route: return a result');
        return 'Hello, world!';
      },
    },
  ],
});

router.resolve({ pathname: '/hello' });
// Prints:
//   middleware: start
//   route: return a result
//   middleware: end

Remember that context.next() iterates only child routes, use context.next(true) to iterate through the all remaining routes.

URL Generation

In most web applications it's much simpler to just use a string for hyperlinks.

`<a href="/page">Page</a>`
`<a href="/user/${username}">Profile</a>`
`<a href="/search?q=${query}">Search</a>`
`<a href="/faq#question">Question</a>`
// etc.

However for some types of web applications it may be useful to generate URLs dynamically based on route name. That's why this feature is available as an add-on with simple API generateUrls(router, options) ⇒ Function where returned function is used for generating urls url(routeName, params) ⇒ String.

import UniversalRouter from 'universal-router';
import generateUrls from 'universal-router/generateUrls';

const routes = [
  { name: 'users', path: '/users' },
  { name: 'user', path: '/user/:username' },
];

const router = new UniversalRouter(routes, { baseUrl: '/base' });
const url = generateUrls(router);

url('users');                         // => '/base/users'
url('user', { username: 'john' });    // => '/base/user/john'

This approach also works fine for dynamically added routes at runtime.

routes.children.push({ path: '/world', name: 'hello' });

url('hello');                         // => '/base/world'

Use encode option for custom encoding of URI path segments. By default encodeURIComponent is used.

const prettyUrl = generateUrls(router, { encode: x => x });

url('user', { username: ':/' });       // => '/base/user/%3A%2F'
prettyUrl('user', { username: ':/' }); // => '/base/user/:/'

Provide a function to stringifyQueryParams option to generate URL with query string from unknown route params.

const urlWithQueryString = generateUrls(router, {
  stringifyQueryParams(params) {
    return Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
  },
});

const params = { username: 'John', busy: 1 };
url('user', params);                  // => /base/user/John
urlWithQueryString('user', params);   // => /base/user/John?busy=1

Or use external library such as qs, query-string, etc.

import qs from 'qs';
generateUrls(router, { stringifyQueryParams: qs.stringify });

Recipes