js-from-routes.jpg

Generating a JS API from Rails Routes

June 22, 2020 5 min read

One idea I’ve always appreciated about the routing approach in Rails is path helpers, which are automatically generated from route definitions.

Some advantages of using path helpers are:

Path Helpers in JS

Traditionally, frontend code in Rails replicates the path structure as defined in the backend, hardcoding the paths for every endpoint that needs to be accessed, and specifying HTTP verbs as needed (GET, POST, PATCH).

$.get('/search', { query })

This approach is very cumbersome, and it is possible to introduce typos, or make mistakes like choosing the wrong HTTP verb (PUT instead of PATCH, and so on).

We could avoid these problems if we had path helpers in JS.

While some tools that allowed to expose path helpers to JS already existed, they were created when sprockets was the way to compile frontend assets in Rails, long before the Webpack era.

I wanted a solution that provided an enjoyable front-end development experience. One that could leverage Webpack loaders and aliases, and was based on ES6 modules to allow tree-shaking, was easy to inspect, and where changes could be tracked under version control.

And that’s why I created JsFromRoutes.

Shaping the Vision ✨

From a developer’s UX perspective, the goal was to simplify how to make requests to a Rails API.

This meant not thinking about paths, parameter interpolation, nor HTTP verbs. Just plain function calls and promises.

import UsersRequests from '@requests/UsersRequests'

const user = await UsersRequests.create({ name, email })

These functions would be automatically generated from the Rails route definitions. The generation process should be transparent, and happen automatically as routes are added and the application is reloaded.

For maintainability, as well as for tree-shaking purposes, it was desirable to do a simple mapping: one request object per controller.

import { request } from '@services/ApiService'

export default {
  search: options =>
    request('get', '/users/search', options),

  create: options =>
    request('post', '/users', options),

  destroy: options =>
    request('delete', '/users/:id', options),
}

Delegating the actual request to an existing service would allow to keep the generated code lean. Any changes or improvements could then be made on the service itself, without having to regenerate the code. For example, switching from axios to redaxios, or to the fetch API.

Generating JS from Rails Routes ⚙️

Although the routes DSL in Rails wasn’t designed for inspection purposes, it does provide the necessary metadata: the name of the endpoints, the HTTP verbs, and the paths including named parameters.

By doing some slight processing to the information in Rails.application.routes, and combining that with an ERB template, we are able to generate the JS functions we need.

import { request } from '@services/ApiService'

export default {
<% routes.each_with_index do |route, index| %>
  <%= route.helper %>: options =>
    request('<%= route.verb %>', '<%= route.path %>', options),
<% end %>
}

Automating the Flow 🤖

Why trigger the generation manually? Let the machines do it 😃

By adding a hook to Rails’ reload process in development, we can automatically generate files from routes when a route is added, modified, or removed.

ActiveSupport::Reloader.to_prepare { JsFromRoutes.generate! }

In order to optimize file generation, we split the generated JS files by controller, and add a cache key based on the routes to avoid rewriting the file if the route definition hasn’t changed.

When the Webpack development server is running, this automatically triggers a new build, and our request method or path helper is ready to be used!

The Gem 💎

The flow described above is now available through the js_from_routes gem. The library provides additional configuration options, visit the readme for more information.

This approach has similar benefits to path helpers in Rails:

Summary

js_from_routes allows to automatically generate path and API helpers from Rails route definitions, allowing you to save development effort and focus on the things that matter.

Since introducing this tool, we have saved a lot of time that we would have spent writing boilerplate code, avoided a lot of mistakes like typos and interpolation errors, and made the front-end development experience more enjoyable 😃

by Máximo Mussini

Store Objects for Vuex

Vuex is a state-management solution that integrates nicely with Vue components.

When starting to use Vuex, one quickly realizes that it’s easier to manage and understand the state of a large application when the state is split between different modules in the Vuex store.

Unfortunately, working with modules involves using a namespace string to access state and getters, which makes typos hard…