Skip to main content
A minimal, composable router built on the web Fetch API and route-pattern. Ideal for building APIs, web services, and server-rendered applications.

Installation

npm i remix

Imports

import { createRouter, RequestContext, createContextKey } from 'remix/fetch-router'
import { route, form, resource, resources } from 'remix/fetch-router/routes'
import type { Router, Middleware, Controller, Action } from 'remix/fetch-router'

createRouter()

Create a new router instance.
function createRouter(options?: RouterOptions): Router

Parameters

options
RouterOptions
Configuration options for the router.
options.defaultHandler
RequestHandler
The default request handler that runs when no route matches. Defaults to a 404 “Not Found” response.
options.matcher
Matcher<MatchData>
The matcher to use for matching routes. Defaults to new ArrayMatcher().
options.middleware
Middleware[]
Global middleware to run for all routes. This middleware runs on every request before any routes are matched.

Returns

router
Router
A router instance with methods for registering routes and handling requests.

Example

import { createRouter } from 'remix/fetch-router'
import { ArrayMatcher } from 'remix/route-pattern'
import { logger } from 'remix/logger-middleware'

let router = createRouter({
  matcher: new ArrayMatcher(),
  middleware: [logger()],
  defaultHandler: () => new Response('Not Found', { status: 404 })
})

Router

The router interface for registering routes and handling requests.

router.fetch()

Fetch a response from the router.
router.fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>
input
string | URL | Request
required
The request input to fetch.
init
RequestInit
The request init options.
response
Promise<Response>
The response from the route that matched the request.
let response = await router.fetch('https://remix.run/blog/hello')
let text = await response.text()

router.route()

Add a route to the router.
router.route<method extends RequestMethod | 'ANY', pattern extends string>(
  method: method,
  pattern: pattern | RoutePattern<pattern> | Route<method | 'ANY', pattern>,
  action: Action<method, pattern>
): void
method
RequestMethod | 'ANY'
required
The request method to match: 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', or 'ANY'.
pattern
string | RoutePattern | Route
required
The pattern to match against incoming requests.
action
Action<method, pattern>
required
The action to invoke when the route matches.
router.route('GET', '/posts/:id', ({ params }) => {
  return new Response(`Post ${params.id}`)
})

router.map()

Map a route or route map to an action or controller.
router.map<target extends MapTarget>(
  target: target,
  handler: MapHandler<target>
): void
target
MapTarget
required
The route/pattern or route map to match. Can be a string, RoutePattern, Route, or RouteMap.
handler
MapHandler<target>
required
The action or controller to invoke when the route(s) match.
import { route } from 'remix/fetch-router/routes'

let routes = route({
  home: '/',
  blog: {
    index: '/blog',
    show: '/blog/:slug'
  }
})

router.map(routes, {
  actions: {
    home() {
      return new Response('Home')
    },
    blog: {
      actions: {
        index() {
          return new Response('Blog')
        },
        show({ params }) {
          return new Response(`Post ${params.slug}`)
        }
      }
    }
  }
})

router.get() / post() / put() / patch() / delete() / head() / options()

Convenience methods for registering routes for specific HTTP methods.
router.get<pattern extends string>(
  route: pattern | RoutePattern<pattern> | Route<'GET' | 'ANY', pattern>,
  action: Action<'GET', pattern>
): void
route
string | RoutePattern | Route
required
The route/pattern to match.
action
Action
required
The action to invoke when the route matches.
router.get('/users/:id', ({ params }) => {
  return Response.json({ id: params.id })
})

router.post('/users', async ({ request }) => {
  let user = await request.json()
  return Response.json(user, { status: 201 })
})

router.delete('/users/:id', ({ params }) => {
  return new Response(null, { status: 204 })
})

RequestContext

A context object that contains information about the current request. Every request handler or middleware receives the same context object.
class RequestContext<params extends Record<string, any> = {}>

Properties

request
Request
The original request that was dispatched to the router. Note: Various properties of the original request may not be available or may have been modified by middleware.
url
URL
The URL that was matched by the route.
method
RequestMethod
The request method. This may differ from request.method when using the methodOverride middleware.
headers
Headers
The headers of the request.
params
params
Params that were parsed from the URL. Fully typed based on the route pattern.
router
Router
The router handling this request.

Methods

context.get()

Get a value from request context.
get<key extends object>(key: key): ContextValue<key>
key
object
required
The key to read. Can be a ContextKey or a class constructor.
value
ContextValue<key>
The value for the given key.
let userKey = createContextKey<User>()
let user = context.get(userKey)

context.set()

Set a value in request context.
set<key extends object>(key: key, value: ContextValue<key>): void
key
object
required
The key to write.
value
ContextValue<key>
required
The value to write.
let userKey = createContextKey<User>()
context.set(userKey, { id: '123', name: 'Alice' })

context.has()

Check whether a value exists in request context.
has<key extends object>(key: key): boolean
key
object
required
The key to check.
exists
boolean
true if a value has been set for the key.
if (context.has(userKey)) {
  let user = context.get(userKey)
}

createContextKey()

Create a type-safe request context key with an optional default value.
function createContextKey<value>(defaultValue?: value): ContextKey<value>
defaultValue
value
The default value for the context key.
key
ContextKey<value>
A type-safe key for storing and retrieving values from RequestContext.
import { createContextKey } from 'remix/fetch-router'

interface User {
  id: string
  name: string
}

let userKey = createContextKey<User>()

// In middleware:
context.set(userKey, { id: '123', name: 'Alice' })

// In action:
let user = context.get(userKey)

Middleware

A special kind of request handler that either returns a response or passes control to the next middleware or request handler in the chain.
interface Middleware<
  method extends RequestMethod | 'ANY' = RequestMethod | 'ANY',
  params extends Record<string, any> = {}
> {
  (
    context: RequestContext<params>,
    next: NextFunction
  ): Response | undefined | void | Promise<Response | undefined | void>
}
context
RequestContext<params>
required
The request context.
next
NextFunction
required
A function that invokes the next middleware or handler in the chain.
response
Response | undefined | void | Promise<Response | undefined | void>
A response to short-circuit the chain, or undefined/void to continue.

Example

import type { Middleware } from 'remix/fetch-router'

function logger(): Middleware {
  return async (context, next) => {
    let start = Date.now()
    let response = await next()
    let duration = Date.now() - start
    console.log(`${context.method} ${context.url.pathname} ${response.status} ${duration}ms`)
    return response
  }
}

function auth(token: string): Middleware {
  return (context, next) => {
    if (context.headers.get('Authorization') !== `Bearer ${token}`) {
      return new Response('Unauthorized', { status: 401 })
    }
    return next()
  }
}

Action

An individual route action, which can be a request handler or a request handler with middleware.
type Action<method extends RequestMethod | 'ANY', pattern extends string> =
  | RequestHandlerWithMiddleware<method, Params<pattern>>
  | RequestHandler<method, Params<pattern>>

Request Handler

interface RequestHandler<
  method extends RequestMethod | 'ANY' = RequestMethod | 'ANY',
  params extends Record<string, any> = {}
> {
  (context: RequestContext<params>): Response | Promise<Response>
}

Request Handler with Middleware

type RequestHandlerWithMiddleware = {
  middleware: Middleware[]
  action: RequestHandler
}

Example

// Simple action
router.get('/posts/:id', ({ params }) => {
  return Response.json({ id: params.id })
})

// Action with middleware
router.get('/admin/dashboard', {
  middleware: [auth('secret')],
  action: () => {
    return new Response('Dashboard')
  }
})

Controller

A controller maps multiple routes to their corresponding actions, with optional shared middleware.
type Controller<routes extends RouteMap> = {
  actions: ControllerActions<routes>
  middleware?: Middleware[]
}
actions
ControllerActions<routes>
required
A map of route names to actions or nested controllers.
middleware
Middleware[]
Middleware that applies to all routes in this controller.

Example

import { route } from 'remix/fetch-router/routes'

let routes = route({
  admin: {
    dashboard: '/admin/dashboard',
    users: '/admin/users'
  }
})

router.map(routes.admin, {
  middleware: [auth('secret')],
  actions: {
    dashboard() {
      return new Response('Dashboard')
    },
    users() {
      return Response.json([{ id: '1', name: 'Alice' }])
    }
  }
})

Route Definitions

Helper functions for defining routes.

route()

Create a route map from route definitions.
function createRoutes<const defs extends RouteDefs>(
  defs: defs
): BuildRouteMap<'/', defs>

function createRoutes<base extends string, const defs extends RouteDefs>(
  base: base | RoutePattern<base>,
  defs: defs
): BuildRouteMap<base, defs>
base
string | RoutePattern
The base pattern for all routes.
defs
RouteDefs
required
The route definitions.
routes
RouteMap
A map of route names to Route objects or nested route maps.
import { route } from 'remix/fetch-router/routes'

let routes = route({
  home: '/',
  about: '/about',
  blog: {
    index: '/blog',
    show: '/blog/:slug'
  }
})

form()

Create a route map with an index (GET) and action (POST) route for HTML forms.
function createFormRoutes<pattern extends string>(
  pattern: pattern | RoutePattern<pattern>,
  options?: FormOptions
): {
  index: Route<'GET', pattern>
  action: Route<'POST', pattern>
}
pattern
string | RoutePattern
required
The pattern for both routes.
options
FormOptions
Options for customizing route names.
import { form } from 'remix/fetch-router/routes'

let routes = route({
  contact: form('contact')
})

// Creates:
// contact.index: Route<'GET', '/contact'>
// contact.action: Route<'POST', '/contact'>

resource()

Create a route map for a singleton resource (no collection).
function createResourceRoutes<pattern extends string>(
  pattern: pattern | RoutePattern<pattern>,
  options?: ResourceOptions
): RouteMap
pattern
string | RoutePattern
required
The base pattern for the resource.
options
ResourceOptions
options.only
ResourceMethod[]
Only include these resource routes.
options.exclude
ResourceMethod[]
Exclude these resource routes.
options.names
Record<ResourceMethod, string>
Custom names for resource routes.
import { resource } from 'remix/fetch-router/routes'

let routes = route({
  profile: resource('profile', { only: ['show', 'edit', 'update'] })
})

// Creates:
// profile.show: Route<'GET', '/profile'>
// profile.edit: Route<'GET', '/profile/edit'>
// profile.update: Route<'PUT', '/profile'>

resources()

Create a route map for a collection of resources (RESTful routes).
function createResourcesRoutes<pattern extends string>(
  pattern: pattern | RoutePattern<pattern>,
  options?: ResourcesOptions
): RouteMap
pattern
string | RoutePattern
required
The base pattern for the resource collection.
options
ResourcesOptions
options.only
ResourcesMethod[]
Only include these resource routes.
options.exclude
ResourcesMethod[]
Exclude these resource routes.
options.param
string
Custom parameter name (defaults to ‘id’).
options.names
Record<ResourcesMethod, string>
Custom names for resource routes.
import { resources } from 'remix/fetch-router/routes'

let routes = route({
  users: resources('users', { param: 'userId' })
})

// Creates 7 routes:
// users.index: Route<'GET', '/users'>
// users.new: Route<'GET', '/users/new'>
// users.show: Route<'GET', '/users/:userId'>
// users.create: Route<'POST', '/users'>
// users.edit: Route<'GET', '/users/:userId/edit'>
// users.update: Route<'PUT', '/users/:userId'>
// users.destroy: Route<'DELETE', '/users/:userId'>

Types

RequestMethod

type RequestMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
All HTTP request methods supported by the router.

RouterOptions

interface RouterOptions {
  defaultHandler?: RequestHandler
  matcher?: Matcher<MatchData>
  middleware?: Middleware[]
}

MatchData

type MatchData = {
  handler: RequestHandler<any>
  method: RequestMethod | 'ANY'
  middleware: Middleware<any>[] | undefined
}
Internal data structure for route matching.

Build docs developers (and LLMs) love