A minimal, composable router built on the web Fetch API and route-pattern. Ideal for building APIs, web services, and server-rendered applications.
Installation
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
Configuration options for the router.The default request handler that runs when no route matches. Defaults to a 404 “Not Found” response.
The matcher to use for matching routes. Defaults to new ArrayMatcher().
Global middleware to run for all routes. This middleware runs on every request before any routes are matched.
Returns
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.
The request init options.
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
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.
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
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.
The URL that was matched by the route.
The request method. This may differ from request.method when using the methodOverride middleware.
The headers of the request.
Params that were parsed from the URL. Fully typed based on the route pattern.
The router handling this request.
Methods
context.get()
Get a value from request context.
get<key extends object>(key: key): ContextValue<key>
The key to read. Can be a ContextKey or a class constructor.
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
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
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>
The default value for the context key.
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.
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 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>
The base pattern for all routes.
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'
}
})
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 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.
Only include these resource routes.
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.
Only include these resource routes.
Exclude these resource routes.
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.