versh/jsrouting-bundle
versh/jsrouting-bundle is a Symfony bundle that exposes application routes to JavaScript, letting frontend code generate URLs by route name and parameters. It helps keep client-side links, AJAX calls, and API endpoints in sync with Symfony routing definitions.
Before anything else, note the framework mismatch. versh/jsrouting-bundle is a Symfony2 bundle (a fork of friendsofsymfony/FOSJsRoutingBundle), not a Laravel package. There is no ServiceProvider, no facade, no config publish step. composer require may technically install it, but nothing will autoload, register routes, or render JavaScript in a Laravel app. If you are evaluating it against a Laravel codebase, the honest answer is: use Ziggy (tightenco/ziggy) — it solves the exact same problem with a Laravel-native package. The FOS bundle is worth understanding only because it defines the canonical pattern that Ziggy, Laravel's route() helper, and similar tools follow.
If you are in a legacy Symfony2/3 app that already has it:
composer require versh/jsrouting-bundle and register FOS\JsRoutingBundle\FOSJsRoutingBundle in AppKernel.fos_js_routing: { resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml" }.Resources/public/js/router.js (or the minified build).options: { expose: true } in your route definition.Routing.generate('my_route_name', { id: 42 }) to get a URL.Where to look first, in order of usefulness:
Resources/config/routing/routing.xml — the single route (fos_js_routing_js) that dumps the route table as JS.Controller/Controller.php — the dump controller; this is what serializes your routes.Resources/public/js/router.js — the client-side Routing object (generate, setBaseUrl, setHost, setScheme).DependencyInjection/Configuration.php — the config tree (routes_to_expose, serializer, cache).First useful case (the one everyone hits): expose a detail-page route (/posts/{id}) so a JS click handler can build the URL instead of hardcoding strings — Routing.generate('post_show', { id: postId }). In Laravel, the direct equivalent is @routes in your Blade layout followed by route('posts.show', postId) in your JS.
The core pattern — dumping the route table to JS and resolving URLs client-side — is what matters. Below are the patterns as the bundle implements them, with the Laravel equivalent for each, since that is what you will actually build.
Opt-in exposure (preferred). Annotate/YAML-mark individual routes with options: { expose: true }. Nothing is exposed unless requested. In Laravel/Ziggy this is the only/except config or Ziggy::filter() — same mental model.
Opt-out / blanket exposure. Configure fos_js_routing.routes_to_expose: ['^api_'] to expose by regex. Useful for internal APIs, dangerous for admin routes — see Gotchas. Ziggy equivalent: only: ['api.*'] in config/ziggy.php.
Build-time dump vs runtime endpoint. The bundle can either serve the routes over HTTP on demand (the fos_js_routing_js route) or be dumped to a static file with bin/console fos:js-routing:dump --target=web/js/routes.js. The static-file approach is the one to prefer for production: it caches, it can be versioned, and it does not leak the API surface on every page load. Laravel/Ziggy mirrors this precisely with php artisan ziggy:generate resources/js/ziggy.js.
Client-side resolution with defaults. Routing.generate(name, params, absolute) merges route defaults, satisfies requirements (regex constraints), and appends unmatched params as a query string. The absolute flag returns a full URL instead of a path. Laravel's route() and Ziggy's route() behave the same way, including the query-string fallback.
Base URL / host override. Behind a load balancer or in a subdirectory deploy, call Routing.setBaseUrl('/subdir') and Routing.setHost(...) at boot. Getting this wrong is the most common source of "URLs work in dev, break in staging" bugs. In Laravel set APP_URL and TrustProxies; Ziggy reads the same trusted-proxy host.
SPA integration. Inject the routes payload into the document <head> (or a boot script) so the router is available before your bundle hydrates. Laravel/Inertia: @routes in app.blade.php, then import { route } from 'ziggy-js' anywhere.
Workflow for day-to-day use:
expose: true.Routing.generate('name', {...}) from your JS.It will not run on modern Symfony, let alone Laravel. Last release 2017-02-15 targets Symfony2/3 and PHP 5.x/7.x idioms. It predates Symfony Flex, AbstractController, autowiring defaults, and modern Twig. If you are on Symfony 4+ or Laravel, do not attempt to make it work — port the pattern (Ziggy), not the package.
Zero stars, zero maintenance. This is a single-person fork with no dependents and no releases after 2017. Treat it as read-only reference code. There will be no security patches and no compatibility fixes.
Exposure is a security boundary. Blanket-exposing every route leaks names, path shapes, and admin surfaces to anyone who fetches the JS endpoint. Always prefer per-route expose: true. The runtime endpoint is publicly reachable by default; the static-dump mode is safer because it is a build artifact you control.
Requirements are evaluated in JS. A route with {id} requiring \d+ will throw client-side if you pass a string. There is no server-side validation of what Routing.generate produces — it is string interpolation. Validate before you navigate.
Anything not in the path becomes a query string. Arrays, booleans, and nulls serialize inconsistently between versions; for the bundle era, arrays typically render as key[]=..., and this differs from what Laravel's route() produces. If you compare output between the two, do not assume parity.
Trailing slashes, locale prefixes, and scheme/host mismatches. Absolute URL generation goes through the request context by default. Behind proxies, set base URL and trusted proxies explicitly or every absolute URL will point at http://localhost.
Caching quirks. The runtime dump benefits from the route cache and from HTTP caching, but the dumped file has no content hash — add a ?v= or use versioned filenames, otherwise browsers will serve stale route tables after a deploy. This is the single most common "the route exists in the app but not in the browser" bug.
Extension points (if you truly must maintain this). Override the dump controller, subclass/replace the route exposer service (fos_js_routing.route_exposer), or decorate the dump command. The serializer is swappable via the serializer config (JsonSerializable vs JMS) — a legacy integration detail that mostly matters when migrating off it.
Debugging playbook. Hit the fos_js_routing_js endpoint directly and inspect the JSON — compare it line-by-line with bin/console debug:router. If a route is missing, it is almost always (a) not exposed, (b) filtered by routes_to_expose, or (c) a stale dumped file. If the generated URL is wrong, check setBaseUrl/setHost before touching the route definition.
How can I help you explore Laravel packages today?