A simple middleware-style router for isomorphic JavaScript web apps
and single-page applications
v3.0.0 (2017-03-25)
import Router from 'universal-router';
const router = new Router([
{ path: '/one', action: () => 'Page One' },
{ path: '/two', action: () => 'Page Two' }
]);
router.resolve({ path: '/one' }).then(result => {
document.body.innerHTML = result || 'Not found';
});
router.resolve({ path, ...context }) → Promise<any>
Traverses the list of routes in the same order they were defined, finds the first route matching the provided URL path string and whose action method returns anything other than null or undefined
..where action is just a regular function that may, or may not, return any arbitrary data — a string, a React component, anything!
Each route may have an optional children: [ ... ] property containing the list of child routes:
const routes = {
path: '/admin'
children: [
{
path: '/', action: () => {/* ... */} // www.example.com/admin
},
{
path: '/users',
children: [
{
path: '/', action: () => {/* ... */} // www.example.com/admin/users
},
{
path: '/:id', action: () => {/* ... */} // www.example.com/admin/users/123
}
]
}
]
};
Named route parameters are captured and added to context.params
const router = new Router({
path: '/hello/:username',
action: (context) => `Welcome, ${context.params.username}!`
});
router.resolve({ path: '/hello/john' })
.then(result => console.log(result));
// => Welcome, john!
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.
In addition to a URL path string, any arbitrary data can be passed to the router's resolve() method, that becomes available inside action methods
const router = new Router({
path: '/hello',
action(context) {
return `Welcome, ${context.user}!`
}
});
router.resolve({ path: '/hello', user: 'admin' })
.then(result => console.log(result));
// => Welcome, admin!
const router = new Router({
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({ path: '/hello/john' })
.then(result => console.log(result));
// => Welcome, John Brown!
The router works great with asynchronous functions out of the box!
Use Babel to transpile your code with async / await to normal JavaScript. Alternatively, stick to ES6 Promises:
{
path: '/hello/:username',
action({ params }) {
return fetch(`/api/users/${params.username}`)
.then(resp => resp.json())
.then(user => user && `Welcome, ${user.displayName}!`);
}
}
Any route action function may act as a middleware by calling context.next()
const router = new Router({
path: '/',
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({ path: '/hello' });
// Prints:
// middleware: start
// route: return a result
// middleware: end
Just create a middleware that's responsible for rendering React components
import Router from 'univeral-router';
const router = new Router({
path: '/',
async action({ next }) {
const component = await next();
return component && <Layout>{component}</Layout>;
},
children: [
{ path: '/', action: () => <HomePage /> },
{ path: '/about', action: () => <AboutPage /> }
]
});
router.resolve({ path: '/hello' }).then(component =>
ReactDOM.render(component, document.getElementById('root'))
);
import express from 'express';
import Router from 'universal-router';
const router = new Router({ // Note: The exact same routes can be used
path: '/', // in both client-side and server-side apps
async action({ next }) {
const component = await next();
return component && <Layout>{component}</Layout>;
},
children: [
{ path: '/', action: () => <HomePage /> },
{ path: '/about', action: () => <AboutPage /> }
]
});
const app = express();
app.use((req, res, next) => {
router.resolve({ path: req.path }).then((component) => {
const body = ReactDOM.renderToString(component);
const html = `<html><head>...</head><body>${body}</body></html>`;
res.send(html);
}).catch(next);
});
app.listen(3000);
import Router from 'universal-router';
const router = new Router({
path: '/products/:id',
action({ params }) {
return {
component: ProductPage, // <- Relay container
params, // <- query parameters
queries: { // <- root queries
viewer: () => Relay.QL`query { viewer }`,
product: () => Relay.QL`query { product(id: $id) }`,
}
};
}
});
router.resolve({ path: '/products/1' }).then(props =>
ReactDOM.render(
React.createElement(RelayApp, props),
document.getElementById('app')
)
);
This routing approach works great with code splitting
const router = new Router([
{
path: '/',
async action() {
const Homepage = await import('./Homepage');
return <Homepage />;
}
},
{
path: '/:username',
async action ({ params }) {
const [ UserProfile, data ] = await Promise.all([
import('./UserProfile'),
fetch(`/api/users/${params.username}`).then(x => x.json()),
]);
if (data) return <UserProfile {...data} />;
}
}
]);
router.resolve({ path: '/john' }).then(/* renderer */);