Route Helpers
Rails route helpers (users_path, edit_post_path) don't exist on the frontend. Without them, you hardcode URL strings -- typos are silent, renamed routes break at runtime, and parameter mismatches go unnoticed until production.
Typelizer generates type-safe TypeScript (or JavaScript) route functions from your config/routes.rb. Each controller gets its own module with methods for every action, giving you autocompletion, type-checked parameters, and zero runtime dependencies beyond the generated code.
Enable Route Generation
Add to your initializer:
# config/initializers/typelizer.rb
Typelizer.configure do |config|
config.routes.enabled = true
endGenerate Route Helpers
Run the rake task:
# Generate route helpers only
rails typelizer:routes
# Generate both types and routes
rails typelizer:generate
# Clean and regenerate everything
rails typelizer:generate:refreshUnderstanding the Generated Output
Given these Rails routes:
Rails.application.routes.draw do
root "pages#index"
resources :users
resources :posts
endTypelizer generates three kinds of files in the output directory (default: app/javascript/routes/):
Per-controller files contain route helper methods grouped by controller:
// routes/UsersController.ts
import type { RouteDefinition, RouteOptions } from './runtime'
import { buildUrl } from './runtime'
export default {
/** GET /users */
index: (options?: RouteOptions): RouteDefinition<'get'> => ({
url: buildUrl('/users', {}, options),
method: 'get',
}),
/** GET /users/:id */
show: (
params: { id: string | number } | string | number,
options?: RouteOptions,
): RouteDefinition<'get'> => ({
url: buildUrl('/users/:id', params, options),
method: 'get',
}),
// ... index, create, new, edit, update, destroy
}index.ts re-exports all controllers as namespaces and provides shortcuts for named routes:
// routes/index.ts
export { default as users } from './UsersController'
export { default as posts } from './PostsController'
// Named route shortcuts
export const root = _pages.index
export const newUser = _users.new
export const editPost = _posts.edit
// ...runtime.ts contains the URL builder and type definitions used by all controller files.
Using Route Helpers
Import by Controller Namespace
Import the controller namespace to access all its routes:
import { users } from '@/routes'
// GET /users
const list = users.index()
// => { url: "/users", method: "get" }
// GET /users/42
const detail = users.show(42)
// => { url: "/users/42", method: "get" }Import Named Routes
Import named route shortcuts directly:
import { editUser, newPost } from '@/routes'
const edit = editUser(42)
// => { url: "/users/42/edit", method: "get" }Passing Parameters
Routes with a single required parameter accept a value directly:
users.show(42)
users.show("abc-123")Routes with multiple parameters require an object:
import { posts } from '@/routes'
// GET /users/:user_id/posts/:id
posts.userPost({ userId: 1, id: 42 })Query Strings and Anchors
Pass query and anchor in the options:
users.index({ query: { page: 2, per: 25 } })
// => { url: "/users?page=2&per=25", method: "get" }
posts.show(42, { anchor: "comments" })
// => { url: "/posts/42#comments", method: "get" }Optional Parameters
Optional route segments are typed with ?:
import { posts } from '@/routes'
// GET /archive(/:year)(/:month)
posts.archive({})
// => { url: "/archive", method: "get" }
posts.archive({ year: 2025 })
// => { url: "/archive/2025", method: "get" }
posts.archive({ year: 2025, month: 3 })
// => { url: "/archive/2025/3", method: "get" }Form Submissions
PATCH and DELETE routes include a .form variant that returns an HTML-form-compatible action with a _method query parameter:
import { users } from '@/routes'
// Standard route
users.update(42)
// => { url: "/users/42", method: "patch" }
// Form variant
users.update.form(42)
// => { action: "/users/42?_method=PATCH", method: "post" }
users.destroy.form(42)
// => { action: "/users/42?_method=DELETE", method: "post" }This is useful for HTML forms and libraries like Inertia.js that need a POST method with _method override.
URL Defaults
Set global URL defaults that are merged into every route:
import { setUrlDefaults, addUrlDefault } from '@/routes/runtime'
// Set all defaults at once
setUrlDefaults({ locale: 'en' })
// Add a single default
addUrlDefault('locale', 'en')
// Dynamic defaults with a function
setUrlDefaults(() => ({
locale: getCurrentLocale(),
}))Base URL
By default, route helpers generate relative paths. If your frontend talks to a different host (e.g., a separate API server), set a base URL:
import { setBaseUrl } from '@/routes/runtime'
setBaseUrl('https://api.example.com')
posts.index()
// => { url: "https://api.example.com/posts", method: "get" }This is useful when your Rails API and frontend are deployed separately. Set it once at app startup and all route helpers prepend it automatically.
Filtering Routes
Use include and exclude to control which routes are generated:
Typelizer.configure do |config|
config.routes.enabled = true
# Only generate routes matching these patterns
config.routes.include = [/^\/api/]
# Skip routes matching these patterns
config.routes.exclude = [/^\/admin/, /^\/internal/]
endBoth accept a single Regexp or an array of patterns. When include is set, only matching routes are generated. exclude is applied after include.
Engine Support
Mounted Rails engines are included automatically. Route paths include the mount prefix:
# config/routes.rb
mount BlogEngine::Engine, at: "/blog"This generates BlogEngine/ArticlesController.ts with paths like /blog/articles and /blog/articles/:id. The engine's named routes are prefixed with the mount name (e.g., blogEngineArticles).
Auto-Regeneration
When the Listen gem is installed and routes are enabled, Typelizer watches config/ for changes to route files and regenerates automatically.
JavaScript Output
For projects without TypeScript, set the format to :js:
Typelizer.configure do |config|
config.routes.enabled = true
config.routes.format = :js
endThis generates .js files without type annotations. The runtime functions and API are identical.
Namespaced Routes
Namespaced routes are placed in subdirectories matching the namespace:
namespace :admin do
resources :users, only: [:index, :show, :destroy]
endGenerates Admin/UsersController.ts with paths like /admin/users and /admin/users/:id.
Import via the namespace:
import { adminUsers } from '@/routes'
adminUsers.index()
// => { url: "/admin/users", method: "get" }