rex-router
v1.3.0
Published
Universal expressive routing for Node.js (Express) and Browser environments. Laravel-inspired syntax with nested layouts, middleware, and SWR support.
Maintainers
Readme
🚀 Re-Router
Universal Frontend & Backend Advanced Routing System
Re-Router is a powerful, expressive routing solution designed to work seamlessly across both Node.js (Express) and Browser environments. It brings Laravel-inspired expressive syntax, nested layouts, and advanced middleware capabilities to the JavaScript ecosystem.
Key Features
- Universal: Same routing logic for Server (Express) and Client (SPA).
- Nested Layouts: Define component chains for complex UI hierarchies.
- Middleware & Guards: Granular control over route access and lifecycle.
- Resourceful Routes: Clean, standard CRUD route definitions in one line.
- Named Routes: Easy URI generation and navigation by name.
- Lazy Loading: Automatic code splitting support for handlers.
- Data Loaders & SWR: Fetch data before activation with stale-while-revalidate support.
- i18n Ready: Native support for localized route patterns.
- API Bridge: Transparent backend-to-frontend data synchronization.
- Error Boundaries: Graceful error handling per route.
Installation
npm install re-routerQuick Start
Basic Server-side Setup (Node.js)
const { Router } = require('re-router/server');
const router = new Router();
router.get('/', () => 'Welcome Home').as('home');
router.get('/users/:id', (ctx) => {
return `Viewing User ${ctx.params.id}`;
}).as('users.show');
// Start your Express server and integrate...Deep Dive
Route Groups & Prefixing
Organize Your routes with prefixing and middleware application.
router.group((r) => {
r.get('/dashboard', () => 'Admin Dashboard');
r.get('/settings', () => 'Settings Page');
}).prefix('admin').middleware([authMiddleware]);Nested Layouts
Define a chain of components that wrap your page content.
router.group((r) => {
r.group((sub) => {
sub.get('/stats', () => 'Stats Component').as('dashboard.stats');
}).prefix('dashboard').layout('DashboardLayout');
}).layout('RootLayout');
// Matches '/dashboard/stats' -> Chain: [RootLayout, DashboardLayout, 'Stats Component']Resourceful Routes
Generate standard CRUD routes for a controller.
const UserController = {
index: (ctx) => 'User List',
show: (ctx) => `User ${ctx.params.id}`,
store: (ctx) => 'User Created'
};
router.resource('users', UserController);Middleware, Guards & Authorization
Protect your routes with ease using middleware, guards, or granular authorization.
router.get('/profile/:id', () => 'Profile')
.beforeEnter((ctx) => {
if (!isLoggedIn) return '/login'; // Redirect
return true; // Proceed
})
.can('update-profile') // Authorization check
.middleware([loggerMiddleware]);Parameter Constraints
Enforce specific formats for your route parameters using regex or built-in helpers.
router.get('/users/:id', () => 'User Profile')
.whereNumber('id') // Only matches if 'id' is a number
router.get('/posts/:slug', () => 'Post')
.where('slug', /^[a-z0-9-]+$/); // Custom regexRequest Validation
Validate incoming data for params, query, or body.
router.post('/register', () => 'User Registered')
.validate({
body: {
email: 'required|email',
password: 'required|min:8'
}
});Parameter Binding
Bind parameters to specific resolvers for automatic data fetching.
router.bind('user', async (id) => {
return await Database.users.find(id);
});
router.get('/users/:user', (ctx) => {
// ctx.params.user is now the full user object!
return `Hello, ${ctx.params.user.name}`;
});Data Loaders
Pre-fetch data before the route renders.
router.get('/posts/:slug', () => 'PostContent')
.loader(async (ctx) => {
return await fetchPost(ctx.params.slug);
});Lazy Loading
Optimize bundle size by loading handlers on demand.
router.get('/heavy-page', () => import('./LargeComponent')).lazy();Named Routes & URL Generation
Generate URLs dynamically using route names, handling parameters and query strings automatically.
router.get('/users/:id/posts/:post_id', (ctx) => {}).as('users.posts.show');
// Generate: /users/42/posts/101
const url = router.makeUrl('users.posts.show', { id: 42, post_id: 101 });
// With Query Strings: /search?q=re-router
const search = router.makeUrl('search', {}, { qs: { q: 're-router' } });Lifecycle Hooks
Intercept navigation at various points in the route lifecycle.
router.get('/editor', (ctx) => 'Editor content')
.beforeEnter((ctx) => console.log('Entering...'))
.afterEnter((ctx) => console.log('Entered!'))
.beforeLeave((ctx) => {
if (hasUnsavedChanges) return false; // Cancel navigation
});Internationalization (i18n)
Define localized paths for the same route in one go.
router.get({
en: '/about-us',
fr: '/a-propos',
es: '/sobre-nosotros'
}, (ctx) => 'Company Info').as('about');
// router.makeUrl('about', { locale: 'fr' }) -> /a-proposError Boundaries
Handle errors gracefully on a per-route basis.
router.get('/api/data', () => { throw new Error('API Fail'); })
.catch((err, ctx) => {
return ctx.send({ error: 'Data unavailable' }, 500);
});API Bridge (Universal)
Synchronize data between backend and frontend seamlessly.
router.setApi('https://api.example.com');
router.get('/users', async (ctx) => {
// ctx.apiResponse is automatically populated from the backend
return ctx.viewHtml('users.html', { users: ctx.apiResponse.data });
});File System Routing
Automatically discover routes based on your file structure, Next.js style.
// With Vite's glob import
const pages = import.meta.glob('./pages/**/*.js', { eager: true });
router.discover(pages);
// Bracket syntax for dynamic segments: ./pages/users/[id].js → /users/:id
// Catch-all routes: ./app/blog/[...slug]/page.js → /blog/*slug
// Grouped layouts: ./pages/(auth)/layout.js → wraps all auth pagesUniversal Routing
Re-Router can handle navigation in the browser using the UniversalAdapter.
const { Router } = require('re-router/server');
const Adapter = require('re-router/client');
const router = new Router();
// ... define routes ...
const adapter = new Adapter(router);
adapter.listen((chain, params) => {
// Render your UI based on the component chain
console.log('Rendering Chain:', chain);
});
// Navigate programmatically
adapter.navigate('/dashboard/settings');License
MIT © Saladin Jake
