named-urls
Simple named url patterns in JavaScript.


Implementing a static route config and named routes on top of (react-router) at this point is like a 20 line ordeal.
– Ryan Florence
Motivation
Named routes are essential to keep route config DRY and prevent silly errors
due to typos. This feature was removed from react-router
in 1.0 and I missed
it since then as many
others.
There're other libs dealing with named routes, some of them provide custom
Link
, Route
routes, some of them have more features to integrate with
express
. Here's incomplete list of libs I considered before writing these
20 lines of code (and 200+ lines of other files to publish this package):
NOTE: v2 introduces breaking changes. Please check out the migration guide before upgrading.
Installation
$ npm install named-urls
or
$ yarn add named-urls
Quickstart
Create file with all routes in your application (e.g. routes.js
). Use
named-urls/include
to create namespaced group of routes with common prefix:
import { include } from 'named-urls'
export default {
profile: '/profile',
article: '/article/:articleId',
messages: '/messages/:messageId?',
auth: include('/auth', {
login: '/login/',
passwordReset: 'password/reset/',
passwordVerify: 'password/verify/',
}),
messages: include('/messages', {
all: '',
unread: 'unread/',
detail: include(':messageId/', {
show: '',
edit: 'edit/',
comments: 'comments/',
})
})
}
Use routes in Route
component from react-router-dom
:
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import routes from './routes'
import * as scenes from './scenes'
function App() {
return (
<Switch>
<Route path={routes.profile} component={scenes.Profile} />
<Route path={routes.auth.login} component={scenes.auth.Login} />
// ...
<Route path={routes.messages.unread} component={scenes.messages.Unread} />
<Route path={routes.messages.detail.show} component={scenes.messages.Detail} />
</Switch>
)
}
Routes with parameters can be formatted using reverse
function:
import React from 'react'
import { Link } from 'react-router'
import { reverse } from 'named-urls'
function Navigation({ messages }) {
return (
<ul>
<li><Link to={`${routes.profile}`}>Profile</Link></li>
// ...
// Use reverse to replace params in route pattern with values
{messages.map(message =>
<li key={message.id}>
<Link to={reverse(`${routes.messages.detail.show}`, { messageId: message.id })}>
Profile
</Link>
</li>
)}
</ul>
)
}
Ending slash
Patterns ending with slash are always reversed to URL with ending slash and vice
versa: Paterns without ending slash are always reserved to URL without endlish
slash:
reverse('pattern/:optional?', { optional: 42 })
reverse('pattern/:optional?')
reverse('pattern/:optional?/', { optional: 42 })
reverse('pattern/:optional?/')
Migrating from v1.x.x to v2.x.x
For better compatibility with React Router, v2 uses path-to-regexp to resolve URLs. This means some of your routes may break when you upgrade.
reverse
-reverse('pattern/page:param?', {})
+reverse('pattern/(page:param)?', {})
reverseForce
-reverseForce('pattern/page:param?', {})
+reverseForce('pattern/(page:param)?', {})
To get a full overview of all accepted patterns, consult the path-to-regexp documentation.
Some tricks
Using an "include" route
If you define a route as an include, calling it directly will return you a function. To by-pass that, you have a solution to create an empty route inside, let's call it self
:
import { include } from 'named-urls'
export default {
messages: include('/messages', {
self: '',
detail: include(':messageId/', {
show: '',
edit: 'edit/',
comments: 'comments/',
})
})
}
so you'll be able to do:
<Route path={routes.messages.self} component={Messages} />
A way to not define a useless route is to use the string way of a route like that:
import { include } from 'named-urls'
export default {
messages: include('/messages', {
detail: include(':messageId/', {
edit: 'edit/',
comments: 'comments/',
})
})
}
<Route path={`${routes.messages}`} component={Messages} />
<Route path={String(routes.messages)} component={Messages} />
License
MIT