1.2.1 • Published 23 days ago

route-decorator v1.2.1

Weekly downloads
-
License
ISC
Repository
-
Last release
23 days ago

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'

// 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 called param into a value called value
  • param=:value? matches an optional single parameter
  • param=:value+ matches one or more parameters called param into an array called value
  • param=:value* matches zero or more
  • :rest... matches any remaining unmatched parameters and stores them in an array of param-value tuples called rest
  • param=example matches only if param=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);
1.2.0

23 days ago

1.2.1

23 days ago

1.0.0

6 months ago