npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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 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);