Routes

Routes is an Elixir library that automatically generates JavaScript and TypeScript route helpers directly from your Phoenix router. It provides type-safe route handling in your frontend code, ensuring that your client-side routing stays in sync with your Phoenix routes.

By integrating Routes into your application, you can effortlessly access your Phoenix routes within your JavaScript or TypeScript codebase, allowing for seamless and type-safe navigation and API calls.

Table of Contents

Features

Installation

Add routes to your list of dependencies in mix.exs:

def deps do
  [
    {:routes, "~> 0.1.0"}
  ]
end

Then, run:

mix deps.get

Configuration

  1. Add Routes to your Phoenix Router:

    In your router file (e.g., lib/your_app_web/router.ex), add use Routes:

    defmodule YourAppWeb.Router do
      use Phoenix.Router
      use Routes  # Add this line
    
      # Your routes...
    end
  2. Configure Routes in config/config.exs:

    Specify your router module and optional settings:

    config :routes,
      router: YourAppWeb.Router,
      typescript: true,         # Enable TypeScript output, defaults to false
      routes_path: "assets/js/routes"     # Optional, defaults to "assets/js"
  3. [Optional] Enable live reloading of routes:

    To automatically regenerate routes when your router file changes during development, add the Routes.Watcher to your application's supervision tree in lib/your_app/application.ex:

    def start(_type, _args) do
      children = [
        # ... other children
      ]
    
      # Add the Routes.Watcher in development environment
      children = if Mix.env() == :dev do
        children ++ [{Routes.Watcher, []}]
      else
        children
      end
    
      opts = [strategy: :one_for_one, name: YourApp.Supervisor]
      Supervisor.start_link(children, opts)
    end

Usage

JavaScript and TypeScript

Once configured, Routes will generate two files in your configured routes_path:

You can import and use Routes in your JavaScript or TypeScript code as follows:

// Import Routes
import Routes from './routes';

// Generate a URL with parameters
const url = Routes.path('user.show', { id: 123 });
// => "/users/123"

// Add query parameters
const searchUrl = Routes.path('user.index', {
  _query: { search: 'john', filter: 'active' }
});
// => "/users?search=john&filter=active"

// Get the HTTP method for a route
const method = Routes.method('user.create');
// => "POST"

// Check if a route exists
if (Routes.hasRoute('user.edit')) {
  // Route exists, proceed...
}

Replacing Path Parameters Directly

You can also use the replaceParams function to replace parameters in a given path:

const customUrl = Routes.replaceParams('/posts/:postId/comments/:commentId', {
  postId: 42,
  commentId: 10,
  _query: { highlight: true }
});
// => "/posts/42/comments/10?highlight=true"

Inertia.js Integration

If you're using Inertia.js, you can create a type-safe Link component that integrates seamlessly with Routes:

// components/Link.tsx
import { Link as InertiaLink, InertiaLinkProps } from "@inertiajs/react";
import React from "react";
import type { PathParamsWithQuery, RoutePath } from "./routes";
import Routes from "./routes";

type LinkProps<T extends RoutePath> = Omit<InertiaLinkProps, "href"> & {
  to: T;
  params?: PathParamsWithQuery<T>;
  children: React.ReactNode;
};

export const Link = <T extends RoutePath>({
  to,
  params,
  children,
  ...props
}: LinkProps<T>) => {
  const href = Routes.replaceParams(to, params);

  return (
    <InertiaLink href={href} {...props}>
      {children}
    </InertiaLink>
  );
};

Usage of the Inertia Link component:

import { Link } from "./components/Link";

// In your component
<Link
  to="/users/:id"
  params={{ id: 123, _query: { tab: "profile" } }}
>
  View Profile
</Link>

Type Safety

Routes provides full TypeScript support with:

Example:

// Correct usage
const url = Routes.path(&#39;user.show&#39;, { id: 123 });

// TypeScript Error: Missing required &#39;id&#39; parameter
const url = Routes.path(&#39;user.show&#39;);

// TypeScript Error: Route &#39;user.nonexistent&#39; does not exist
const url = Routes.path(&#39;user.nonexistent&#39;);

API Reference

Routes.path(name, params?)

Generates a path (URL) for the given route name with optional parameters.

Example:

const url = Routes.path(&#39;post.show&#39;, { id: 42 });
// => "/posts/42"

const urlWithQuery = Routes.path(&#39;post.index&#39;, {
  _query: { page: 2, sort: &#39;desc&#39; }
});
// => "/posts?page=2&sort=desc"

Routes.route(name, params?)

Alias for Routes.path.

Routes.method(name)

Returns the HTTP method associated with the given route name.

Example:

const method = Routes.method(&#39;post.create&#39;);
// => "POST"

Routes.hasRoute(name)

Checks if a route with the given name exists.

Example:

if (Routes.hasRoute(&#39;post.edit&#39;)) {
  // Route exists
} else {
  // Route does not exist
}

Routes.replaceParams(path, params?)

Replaces the path parameters in the given path string with the provided values.

Example:

const url = Routes.replaceParams(&#39;/users/:id&#39;, { id: &#39;123&#39; });
// => "/users/123"

Development

Routes automatically watches for changes in your Phoenix router file during development and regenerates the route helpers accordingly. This ensures that your frontend code stays up-to-date with your backend routes without manual intervention.

To enable live reloading, make sure you've added the Routes.Watcher to your application's supervision tree as described in the Configuration section.

Contributing

We welcome contributions to Routes! To contribute:

  1. Fork the repository on GitHub.
  2. Create a new branch for your feature or bugfix.
    git checkout -b my-new-feature
  3. Commit your changes with clear commit messages.
    git commit -am 'Add new feature'
  4. Push to your branch on GitHub.
    git push origin my-new-feature
  5. Create a Pull Request explaining your changes.

Please make sure to write tests for your changes and follow the existing code style.