Caleb Porzio and I started a podcast recently in our Tighten-provided 20% time . We call it Twenty Percent Time, and lately we've been talking a lot (#growthhacking, resources) about how we transfer data between our Laravel code and our JavaScript code. One thing we’ve come back to a few times is the desire to use the Laravel route()
helper with named routes in JavaScript.
My JavaScript is full of axios
calls, which are made to hard-coded API endpoint URLs in my Laravel apps. This can be a real pain when an endpoint route needs to be moved to a group with a URL prefix, when a parameter needs to be added, or when any other URL-breaking change needs to be made.
This is why, in Laravel, we have named routes, and why we don’t hard-code URLs in our Blade templates. By abstracting away the need for our consuming code to know the exact (down-to-the-letter) URL for a route, we are able to write more change-resilient code—and avoid a lot of “find and replace.”
We wanted the same kind of protection and convenience in JavaScript that we have inside our Laravel apps. And that’s why we built Ziggy.
After you have Ziggy installed (composer require tightenco/ziggy
and add the service provider to config/app.php
), just drop our custom Blade directive (@routes
) somewhere in your app’s Blade layout file before you load your main app JavaScript. Here's an example:
<!DOCTYPE html><html lang="en"> <head> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <link rel="stylesheet" href="{{ mix('css/app.css') }}"> </head> <body> <div class="app"> </div> @routes <script src="{{ mix('js/app.js') }}"></script> </body></html>
This Blade directive will be replaced with a <script>
tag containing our route()
helper function as well as a JavaScript object of your named routes, keyed by name. Let's assume this is what our routes file looks like:
// Not named; shouldn't end up in output JSONRoute::get('/', 'HomeController'); // Named; should end up in output JSONRoute::group(['as' => 'posts.'], function () { Route::get('/', 'PostsController@index') ->name('index'); Route::get('/{post}', 'PostsController@show') ->name('show'); Route::post('/', 'PostsController@store') ->name('store');}); Route::get('/{post}/comments', 'PostCommentsController@index') ->name('post-comments.index');
Here's the actual code this would output (truncated to just show the first named route):
<script>var namedRoutes = JSON.parse( '{"posts.index":{"uri":"posts","methods":["GET","HEAD"]}}');// below, the route() helper is defined</script>
As you can see, we've added a window.namedRoutes
object you can access directly; it's an object that contains a series of objects, one for each named route. Those objects are keyed by their route name, so you could just directly ask for that route in your code: var routeObject = window.namedRoutes['posts.index'];
But you can also use our new route()
helper, which mimics the same helper function in Laravel's PHP:
var routeUrl = route('posts.index');var routeUrl = route('posts.show', {post: 1337});
Here's what it might look like in your real code:
// Returns results from /postsreturn axios.get(route('posts.index')) .then((response) => { return response.data; }); // Returns results from /posts/1return axios.get(route('posts.show', {post: postId})) .then((response) => { return response.data; });
You’ll notice that the route()
helper takes two arguments:
- A required Route name
- An optional object full of parameters
The posts.show
route, for example, requires a post
parameter, so we pass {post: postId}
as the second argument to route.
This is very much a first iteration of this concept. We have lots of features still to come.
Here are a few ideas we're already considering:
I've mentioned this need on Twitter and the podcast and had a few people reach out with other people who have worked on different solutions. The most similar package is called laroute, which I hoped would be the solution when I discovered it.
Here's why we decided to go forward with Ziggy anyway: laroute has over a dozen open pull requests and doesn't seem to have been updated recently except to support new versions of Laravel, but more importantly, it works differently on a foundational level: it requires you to re-publish your JavaScript every time you make a change to any of your routes. That's just not a cost we were willing to introduce to our development workflow, so we set out to do it more simply.
So, we're moving forward with Ziggy, but we also want to send some love in laroute's direction. If you need a package that provides a ton more functions than just our route()
helper and you don't mind publishing your JavaScript every time you change your routes, definitely check out laroute.
There were two other Gists folks sent me that I wanted to acknowledge here:
Both Jeff and Clayton shared their solutions with me recently, and I like them both. If you'd rather roll your own, I suggest checking out these Gists for inspiration.
Here's the TL;DR: bring in Ziggy to your Laravel applications and put the @routes
directive in one of your global templates, and you'll instantly get a route()
helper that you can use just like the one included with Laravel. Now, your aliases go the whole way from your routes file to your JavaScript code, and a change in one will instantly bring a change in the other.
We appreciate your interest.
We will get right back to you.