A Javascript route() helper for Laravel

Clayton Carter • 2017-03-23 • Updated: 2018-01-31, 2020-01-10

laravel javascript routes commands

Note: this is an updated version of a post originally published on GitHub.

Using named routes in Laravel makes it easy to change URLs as an app matures during development. But there’s no built-in way to access routes by name from your front-end JS code. The solution I’ve come up with is to:

  1. Create a new artisan route:json command to export all named routes as JSON
  2. Create a simple JS route() function to access and fill in URLs with parameters in my front-end code

Installation

Install RouteJson.php

Create a file named app/Console/Commands/RouteJson.php in your app. Copy the following code into that file:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Route;

class RouteJson extends Command
{
  /**
   * The name and signature of the console command.
   *
   * @var string
   */
  protected $signature = 'route:json';

  /**
   * The console command description.
   *
   * @var string
   */
  protected $description = 'Export named routes as JSON';

  /**
   * Create a new command instance.
   *
   * @return void
   */
  public function __construct()
  {
    parent::__construct();
  }

  /**
   * Execute the console command.
   *
   * @return mixed
   */
  public function handle()
  {
    $routes = $this->generateRoutes();
    $this->writeJson($routes);
    return;
  }

  public function generateRoutes()
  {
    $routes = [];
    foreach (Route::getRoutes()->getRoutes() as $route) {
      if (is_null($route->getName())) {
        continue;
      }
      if (isset($routes[$route->getName()])) {
        $this->comment(
          'Overwriting duplicate named route: ' . $route->getName()
        );
      }
      $routes[$route->getName()] = '/' . $route->uri();
    }
    return $routes;
  }

  protected function writeJson($routes)
  {
    $filename = 'resources/assets/js/routes.json';

    if (!($handle = fopen($filename, 'w'))) {
      $this->error("Cannot open file: $filename");
      return;
    }

    // Write $somecontent to our opened file.
    if (fwrite($handle, json_encode($routes)) === false) {
      $this->error("Cannot write to file: $filename");
      return;
    }

    $this->info("Wrote routes to: $filename");

    fclose($handle);
  }
}

Edit Kernel.php

Edit the app/Console/Kernel.php file to make sure that Commands\RouteJson::class is included in the $commands array. For example, the $commands in my Kernel.php looks like this:

/**
 * The Artisan commands provided by your application.
 *
 * @var array
 */
protected $commands = [
    Commands\RouteJson::class
];

Install the JS route() helper

Create a file named resources/assets/js/route.js. Copy the following code into that file:

import routes from './routes.json';

/**
 * Resolve a named route into a valid URI
 * @param  {string} routeName         name of route
 * @param  {Object} [replacements={}] Object whose keys are parameter names, and whose values are the values to replace those parameters with.
 * @return {string}                   A filled, resolved URI
 */
export default function (routeName, replacements = {}) {
  var uri = routes[routeName];

  if (!uri) {
    throw new Error(`Cannot find route: ${routeName}`);
  }

  Object.keys(replacements).forEach(
    (key) =>
      (uri = uri.replace(new RegExp('{' + key + '\\??}'), replacements[key])),
  );

  // remove any leftover optional parameters (inc leading slash)
  uri = uri.replace(/{[^/]+\?}/, '');

  if (uri.match(/{.*}/)) {
    throw new Error(`Route contains unfilled parameters: ${routeName} ${uri}`);
  }

  return uri;
}

Usage

Issue the command php artisan route:json to export your routes to the file resources/assets/js/routes.json. Then, in your Javascript code, you can import route from './route.js' and use the route() helper very similarly to the PHP version in Laravel:

import route from './route.js'; // or '../route.js', '../../route.js', etc

console.log(route('user.note.store', { user: 123 })); // -> /user/123/note

Optional route parameters that aren’t passed in the paramerter object will be discarded, but mandatory route parameters that aren’t passed in will trigger an error.

It’s not perfect, but it’s worked pretty well for me. Maybe it will work for you.

Update

After I wrote this, TightenCo published a related package called Ziggy. Initially, Ziggy could only send the routes to the browser on every page load, but recent versions appear to be able to also output a file, which can be bundled and cached as part of your build process, similar to how this works.