DEV Community

Cover image for Persistent routes
Konstantin
Konstantin

Posted on

Persistent routes

Hi, everyone. I'm sure you know, route management in an enterprise project is a tricky task. Enum (typescript data structure) is often used to keep them persistent. But enum itself doesn't solve all problems while we handle the route concatenation. Let's take a look at an example.

Suppose we have three levels of nesting routes:

enum ROOT_ROUTES {
  CATALOG = 'catalog',
  ABOUT = 'about',
  SUPPORT = 'support',
}

enum CATALOG_ROUTES {
  CATEGORY = 'category',
  ITEM = 'item',
  CART = 'cart'
}

enum CATEGORY_ROUTES {
  TOP = 'top',
  SALES = 'sales',
  ALL = 'all'
}
Enter fullscreen mode Exit fullscreen mode

Then, in order to assemble the full route, we have to correctly concatenate all the intermediate values:

const topCategoryRoute = `/${ROOT_ROUTES.CATALOG}/${CATALOG_ROUTES.CATEGORY}/${CATEGORY_ROUTES.TOP}`;
Enter fullscreen mode Exit fullscreen mode

It's a very bad idea to keep it just as it is. I'm sure you don't do that. It's easy to overlook any nesting level. And if similar code starts to appear in several places of project, we violate the DRY principle. It will also lead to problems in the future when we want to change the navigation.

The situation looks better when we add global constants with the routes already concatenated:

const rootRoutes: { [key in keyof typeof ROOT_ROUTES]: string } = {
  CATALOG: `/${ROOT_ROUTES.CATALOG}`,
  ABOUT: `/${ROOT_ROUTES.ABOUT}`,
  SUPPORT: `/${ROOT_ROUTES.SUPPORT}`
}

const catalogRoutes: { [key in keyof typeof CATALOG_ROUTES]: string } = {
  CATEGORY: `${rootRoutes.CATALOG}/${CATALOG_ROUTES.CATEGORY}`,
  ITEM: `${rootRoutes.CATALOG}/${CATALOG_ROUTES.ITEM}`,
  CART: `${rootRoutes.CATALOG}/${CATALOG_ROUTES.CART}`
}

// It's terribly tedious even just to write this example.
// Let's move on to the final part.
Enter fullscreen mode Exit fullscreen mode

And even so, we are not immune to mistakes.

Let's try to automate the process to get rid of the routine and reduce the human factor. I started to think not from what I can do, but from what I want to get.

Suppose we already have rootRoutes with a signature corresponding to ROOT_ROUTES. I would like the rootRoutes properties to behave like primitive values in operations, but at the same time have a method for the subsequent concatenation of routes. Something like that (TDD in action):

// https://nodejs.org/api/assert.html#assert_assert_strictequal_actual_expected_message
assert.strictEqual(`${rootRoutes.CATALOG}`, '/catalog')
const catalogRoutes = rootRoutes.CATALOG.concatRoutes(CATALOG_ROUTES);
assert.strictEqual(`${catalogRoutes.CATEGORY}`, '/catalog/category');
Enter fullscreen mode Exit fullscreen mode

The implementation of the [Symbol.toPrimitive] method in the class will help us achieve this.

Final result:

class Route {
  constructor(private _value: string) {}

  [Symbol.toPrimitive]() {
    return this._value;
  }

  concatRoutes<R extends Record<string, string>, K extends keyof R>(routes: R) {
    return Object.fromEntries(
      Object.entries(routes).map(([key, value]) => [key, new Route(`${this._value}/${value}`)])
    ) as { [key in K]: Route };
  }
}

const rootRoutes = new Route('').concatRoutes(ROOT_ROUTES);
Enter fullscreen mode Exit fullscreen mode

Thank you for reading. Please share, how do you manage routes in your project?

Discussion (0)