route-decorator
v1.3.0
Published
A decorator for maping URL paths to class methods
Downloads
10
Readme
Router Decorator
This library provides a decorator for mapping URL paths and queries to class methods. It's been designed with Typescript compatibility in mind but can be used in vanilla javascript if your environment or build tools support ES Decorators.
A note on path definitions
This module uses the path-to-regexp package to parse and match path strings. Take a look at that package's documentation to find out more about the syntax.
For query parameters, a simpler format is used - see down the page for more info.
Usage
You can install this package into your project from NPM using your favourite package manager.
For example, to install it using NPM's CLI tool, run npm install route-decorator
.
This package exports its code as ES2022 JavaScript, and uses recent features like private fields, nullish coalescing assignment operator, and the ES module format.
If your environment doesn't support these features you will need to transpile this package before you use it.
Here is an example of usage:
import RouteMap from "route-decorator";
// Create an instance of the decorator
// Make sure you tell it the the shape of the class you're applying it to
// This is for type checking, so it's not important if you're using vanilla JS
const route = new RouteMap<Routes>;
// Apply the routes
class Routes {
// This is a plain route with no variables
@route('/')
home(){
// You can return anything from your route;
// A model, a controller, or a view for example
// It could be sync or async, or even an iterator
// Here we return just a string
//
// You could also refer to public or private fields set in the constructor.
return 'Home page'
}
// This is a route with a variable in the path.
// You must make sure the variable name in the path matches the variable name in the object
@route('/posts/:postId')
post({ postId }: { postId: string }) {
return 'Post ID ' + postId;
}
// This route has multiple URLs, and an optional variable with a default value
@route('/user/:userId')
@route('/default-user')
user({ userId }: { userId: string } = { userId: 'default-id' }) {
return 'User ID ' + userId
}
}
// You can now use the `route` object to generarte URLs based on your route method's function name:
route.compile('home') // returns '/'
route.compile('post', { postId: '1' }) // returns '/posts/1'
// The compile function will select the best route based on the arguments provided
route.compile('user', { userId: '2' }) // returns '/user/2'
route.compile('user') // returns '/default-user'
// If you're not sure whether the router has the route you want, use `compileOrNull`
route.compileOrNull('nope') // returns null
// To call your route function, you'll need an instance of your routes class:
const router = route.getRouter(new Routes);
// Check if a route exists:
router.has('/')
// Find multiple matching routes
for(const val of router.routeAll('/')) {
if(val !== null) {
console.log(val);
break;
}
}
// Find a specific route
router.route('/posts/100') // Throws an error if the route doesn't exist
router.routeOrNull('/user/fred') // Returns null if the route doesn't exist
Matching Query Parameters
You can match query parameters by passing in a second argument:
class Routes {
@route('/home', 'page=:page?&tag=:tags*&:rest...')
home({ page, tags, rest }: { page?: string, tags?: string[], rest: [string, string][] }) {
console.log(page, tags, rest);
}
@route('/', 'prev=true&page=:page')
preview(args) {
console.log(args)
}
}
const router = route.getRouter(new Routes());
router.route('/home', 'tag=red&page=4&tag=yellow&campaign=spring');
// logs '4' and ['red', 'yellow'] and [['campaign', 'spring']]
router.route('/', 'prev=true&page=1');
// logs { page: '1' }
route.compile('home', { page: '7', rest: [['newLayout', 'enabled']] })
// '/home?page=7&newLayout=enabled
route.compile('preview', { page: '2' })
// '/?prev=true&page=2
This matches parameters regardless of what order they appear.
The syntax for matching is different from URL matching:
param=:value
matches a required single parameter calledparam
into a value calledvalue
param=:value?
matches an optional single parameterparam=:value+
matches one or more parameters calledparam
into an array calledvalue
param=:value*
matches zero or more:rest...
matches any remaining unmatched parameters and stores them in an array of param-value tuples calledrest
param=example
matches only ifparam=example
is in the query object, but doesn't parse it to a value
You currently can't use regular expressions
Extending an existing router
Here's an example of extending an existing router:
// Set up your basic router as above
const route = new RouteMap<Routes>();
class Routes {
@route('/')
home(){ return 'Home' }
}
// Create a new decorator/route map, cloning the existing one
const authRoute = new RouteMap<RoutesWithAuth>(route);
// Apply to your extended route class
class RoutesWithAuth extends Routes {
@authRoute('/auth')
auth() { return 'Auth endpoint'; }
}
// Continue as normal
const router = authRoute.getRouter(new RoutesWithAuth);