DEV Community

loading...
Cover image for a first look at elder.js part 2 - routes

a first look at elder.js part 2 - routes

ajcwebdev profile image anthony-campolo Updated on ・5 min read

At the core of any site are its routes or templates. To learn more about routing in Elder.js, open localhost:3000/simple in your browser.

01-simple-route

In Elder.js a route is made up of 2 files that live in your route folder: ./src/routes/${routeName}/.

1 - A route.js file that defines route details

// src/routes/simple/routes.js

module.exports = {
  all: async () => [{ slug: 'simple' }],

  permalink: ({ request }) => `/${request.slug}/`,

  data: async ({ request }) => {
    return {
      title: 'Elder.js Route: An Overview',

      steps: [
        `Step 1: All routes require a route.js and a svelte template. Look at ./src/simple/route.js to follow along.`,
        `Step 2: We define an 'all()' function that returns an array of 'request' objects.`,
        `Step 3: We define a permalink function that transforms the 'request' objects from 'all()' into permalinks.`,
        `Step 4: We define a data function that makes data available in your svelte template.`,
      ],

      content: `
      <h2>How Routing Works:</h2>
      <p>Elder.js's routing flow is different from what you'll see in other frameworks such as <span class="code">express</span>.</p>
      <p>Most frameworks define routes like so: <span class="code">/blog/:slug/</span>.</p>
      <p>Then when you visit <span class="code">/blog/simple/</span> that route would receive a <span class="code">request</span> object of <span class="code">{ slug: "simple" }</span>.</p>
      <p>While this approach works, a huge downside is that it forces a static site generator to crawl all of the links on a site to know all of the request objects.</p>
      <p>Since Elder.js is built for speed and crawling is expensive, Elder.js asks you to define all of your <span class="code">request</span> objects in your <span class="code">all()</span> function.</p>
      <p>Once it has your requests, it runs them through the <span class="code">permalink()</span> function to can build an entire map of your site so we don't have to crawl it but can generate it on the fly.</p>

      <h3>Learning Exercise: </h3>
      <p>Try adding <span class="code">{ slug: "another-request" }</span> to the <span class="code">all()</span> function in <span class="code">./src/simple/route.js</span> and then visit /another-request/ to see that you added another page with the same data.</p>
      `,
    };
  },

  // template: 'Simple.svelte' // this is auto-detected.
  // layout: 'Layout.svelte' // this is auto-detected.
};
Enter fullscreen mode Exit fullscreen mode
  • all - Returns an array of all of the request objects of a route.
all: async () => [{ slug: 'simple' }]
Enter fullscreen mode Exit fullscreen mode
  • permalink - Takes a request object and returns a relative permalink, in this case /simple/.
permalink: ({ request }) => `/${request.slug}/`
Enter fullscreen mode Exit fullscreen mode
  • data - Populates an object that will be available in our Svelte template under the data key.
data: async ({ request }) => {
  return {
    title: 'Elder.js Route: An Overview',
    steps: [ ... ],
    content: `
    ...
    `,
  };
},
Enter fullscreen mode Exit fullscreen mode

2 - A Svelte component matching the ${routeName} is used as a template (referred to as "Svelte Templates")

// src/routes/simple/Simple.svelte

<script>
  export let data, helpers;
</script>

<style>
  a {
    margin-bottom: 1rem;
    display: inline-block;
  }
</style>

<svelte:head>
  <title>{data.title}</title>
</svelte:head>

<a href="/">&LeftArrow; Home</a>

<h1>{data.title}</h1>

<ul>
  {#each data.steps as step}
    <li>{step}</li>
  {/each}
</ul>

{@html data.content}
Enter fullscreen mode Exit fullscreen mode

There is a learning exercise at the bottom of the page:

Try adding { slug: "another-request" } to the all() function in ./src/simple/route.js and then visit /another-request/ to see that you added another page with the same data.

all: async () => [
  { slug: 'simple' },
  { slug: 'another-request' }
],
Enter fullscreen mode Exit fullscreen mode

02-another-request

Explicit Routing

Elder.js uses "explicit routing" instead of the more common "parameter based" routing found in most frameworks like express. Elder.js asks you to define all of your request objects in your all() function.

Once it has your requests, it runs them through the permalink() function to build an entire map of your site. This means we don't have to crawl the site, instead we can generate it on the fly.

Here's an example of how you'd setup a route like /blog/:slug/ where there are only 2 blogposts.

// ./src/routes/blog/route.js

module.exports = {
  template: 'Blog.svelte',

  permalink: ({ request }) => `/blog/${request.slug}/`,

  all: async () => {
    return [
      { slug: 'blogpost-1' },
      { slug: 'blogpost-2' }
    ],
  },

  data: async ({ request }) => {
    return {
      blogpost: `This is the blogpost for the slug: ${request.slug}`.
    }
  };
Enter fullscreen mode Exit fullscreen mode

permalink()

Similar to standard route definitions seen with placeholders. /blog/:slug/ would be defined as /blog/${request.slug}/. It takes the request objects returned from all and transforms them into relative urls. Async is not supported.

permalink: ({ request, settings, helpers }): String => {

  // request: object received from all() function, passing a 'slug' parameter is recommended, but any naming can be used

  // settings: describes Elder.js bootstrap settings

  // helpers: helpers from `./src/helpers/index.js` file

  return String;
};
Enter fullscreen mode Exit fullscreen mode

all()

Returns an array of all of the request objects for a given route. Often this array may come from a data store. Since we're explicitly saying we only have 2 blog posts, only two pages will be generated.

all: async ({ settings, query, data, helpers }): Array<Object> => {

  // settings: describes Elder.js settings at initialization

  // query: empty object usually populated on the 'bootstrap' hook with a db or api connection that is sharable throughout all hooks, functions, and shortcodes

  // data: any data set on the 'bootstrap' hook

  return Array<Object>;
}
Enter fullscreen mode Exit fullscreen mode

data()

Prepares the data required in the Blog.svelte file. Whatever object is returned will be available as the data prop. In the example, we're returning a static string, but you could do anything you can do in Node such as:

  • Hit an external CMS
  • Query a database
  • Read from the file system
data: async ({

  data, // any data set by plugins/hooks on 'bootstrap' hook

  helpers, // helpers from ./src/helpers/index.js` file

  allRequests, // all `request` objects returned by all() function

  settings, // Elder.js settings

  request, // the requested page's `request` object

  errors, // any errors

  perf, // performance helper

  query, // search for 'query' in docs for more details

}): Object => {
  return Object;
};
Enter fullscreen mode Exit fullscreen mode

In this example, we're just returning a simple object in our data() function, but we could have easily used node-fetch and gotten our blogpost from a CMS or used fs to read from the filesystem:

const blogpost = await fetch(
  `https://api.mycms.com/getBySlug/${request.slug}/`
).then((res) => res.json());
Enter fullscreen mode Exit fullscreen mode

Why Routing Differs from Express-like Frameworks

While Elder.js' approach to routing is unconventional it offers several distinct advantages over traditional 'parameter based' routing:

Elder.js doesn't have to crawl all links of a site to know what pages need to be generated.

  • Build times can be fully parallelized and scale with CPU resources.
  • ElderGuide.com has ~20k pages and builds in 1 minute 22 seconds as of October 2020.

Users have full control over their URL structure.

This makes internationalization (i18n) and localization (l10n) more approachable. No complex regex is needed to have:

  • /senior-living/:facilityId/
  • /senior-living/:articleId/
  • /senior-living/:parentCompanyId/

Route.js Best Practices

A route's all function should return the minimum viable data points needed to generate a page; skinny request objects and fat data functions. It's possible to load the request objects returned by a route's all function with tons of data.

But this doesn't scale very well, so it's recommended you only include the bare minimum on the request object for querying your database, api, file system, or data store. Fetching, preparing, and processing data should then be done in the route's data function.

Discussion (0)

pic
Editor guide