# Middleware and Server Setup ## What This Covers How to compose the request lifecycle and bridge the router to a runtime. Read this when the task involves: - Choosing or ordering built-in middleware in the stack - Writing custom middleware that sets typed context values - Adding fast-exit handling (static files, CORS preflights) versus request-enriching layers (sessions, auth, data loading) - Choosing when to keep the generated Node server versus switching server adapters For data and persistence specifics, see `data-and-validation.md`. For session and auth specifics, see `auth-and-sessions.md`. ## Middleware Stack Middleware runs in order for every request. Place fast-exit middleware (static files) early and request-enriching middleware (session, auth) later. Recommended ordering: ```typescript import { createRouter } from 'remix/router' import { compression } from 'remix/middleware/compression' import { formData } from 'remix/middleware/form-data' import { logger } from 'remix/middleware/logger' import { methodOverride } from 'remix/middleware/method-override' import { session } from 'remix/middleware/session' import { staticFiles } from 'remix/middleware/static' import { asyncContext } from 'remix/middleware/async-context' let middleware = [] if (process.env.NODE_ENV === 'development') { middleware.push(logger()) } middleware.push(compression()) middleware.push(staticFiles('./public')) middleware.push(formData()) middleware.push(methodOverride()) middleware.push(session(cookie, storage)) middleware.push(asyncContext()) middleware.push(loadDatabase()) middleware.push(loadAuth()) let router = createRouter({ middleware }) ``` ### Built-in middleware catalog | Middleware | Import | Use when | Notes | | -------------------------- | ---------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------- | | `staticFiles(dir, opts?)` | `remix/middleware/static` | Serve files from `public/` or another directory exactly as they exist on disk | Fast exit; usually near the top | | `compression()` | `remix/middleware/compression` | Compress text-like responses | Usually app-wide | | `logger()` | `remix/middleware/logger` | Log requests and responses | Often development-only; `colors` can force color output on/off | | `cors(opts?)` | `remix/middleware/cors` | Endpoints must serve cross-origin browsers or preflight `OPTIONS` requests | Usually early so preflights can short-circuit | | `cop(opts?)` | `remix/middleware/cop` | Reject unsafe cross-origin browser requests without synchronizer tokens | Put before session or CSRF when used | | `formData(opts?)` | `remix/middleware/form-data` | Parse `FormData` bodies, especially forms and uploads | Needed for `_csrf` form field extraction | | `methodOverride()` | `remix/middleware/method-override` | HTML forms need `PUT`, `PATCH`, or `DELETE` semantics | Run after form parsing | | `session(cookie, storage)` | `remix/middleware/session` | Cookie-backed sessions | Must run before session-backed auth or CSRF | | `csrf(opts?)` | `remix/middleware/csrf` | Session-backed form workflows need synchronizer-token CSRF protection | Requires `session()` before it | | `asyncContext()` | `remix/middleware/async-context` | Helpers outside handlers need request context via `getContext()` | Add before helpers rely on it | | `auth({ schemes })` | `remix/middleware/auth` | Resolve auth state into `context.get(Auth)` | Run after `session()` for session-backed auth | | `requireAuth()` | `remix/middleware/auth` | A controller or action must reject anonymous access | Usually controller middleware or action middleware | ### Static files vs browser modules - Use `staticFiles()` for files that should be served directly from disk, such as images, fonts, or already-built assets in `public/` - Use `remix/assets` when browser modules should be compiled and served from source files with import rewriting, preloads, or fingerprinted URLs ### Ordering notes - Put fast exits early: `staticFiles()`, `cors()` preflight handling, and `cop()` when used - Parse request bodies before middleware that depends on them, such as `methodOverride()` and form field token extraction in `csrf()` - Run `session()` before `csrf()` and before session-backed `auth()` - Add `asyncContext()` before helpers or shared code call `getContext()` - Keep route protection like `requireAuth()` as controller middleware or action middleware unless the entire app is private ### Common stacks - **Session-backed HTML app** -> `compression()`, `staticFiles()`, optional `cop()`, `formData()`, `methodOverride()`, `session()`, optional `csrf()`, `asyncContext()`, `auth({ schemes })` - **Cross-origin API** -> `compression()`, `cors()`, optional `asyncContext()`, optional `auth({ schemes })` - **Upload flow** -> `compression()`, `staticFiles()`, `formData({ uploadHandler })`, then sessions, auth, and data-loading middleware as needed ### Middleware with options ```typescript // Static files with cache headers staticFiles('./public', { cacheControl: 'no-store, must-revalidate', etag: false, lastModified: false, }) // Form data with upload handler import type { FileUpload } from 'remix/form-data-parser' import { createFsFileStorage } from 'remix/file-storage/fs' let fileStorage = createFsFileStorage('./tmp/uploads') formData({ uploadHandler(fileUpload: FileUpload) { return fileStorage.set(fileUpload.name, fileUpload) }, }) ``` Errors thrown or rejected by `uploadHandler` propagate directly. Catch domain-specific upload errors at the route boundary when they should become user-facing `Response` objects. ## Writing Custom Middleware Middleware is a function that receives `(context, next)`. Return a `Response` to short-circuit, call and return `next()` when you need the downstream response, or return nothing when you only set context and want the router to continue automatically. ### Setting context values Use `context.set(key, value)` to add typed values accessible downstream via `context.get(key)`. ```typescript import type { Middleware } from 'remix/router' import { Database } from 'remix/data-table' export function loadDatabase(): Middleware { return async (context, next) => { context.set(Database, db) return next() } } ``` ### Guarding routes ```typescript import { Auth } from 'remix/middleware/auth' export function requireAdmin(): Middleware { return (context, next) => { let auth = context.get(Auth) if (auth.identity?.role !== 'admin') { return new Response('Forbidden', { status: 403 }) } return next() } } ``` ### Async context for helpers `asyncContext()` stores the request context in `AsyncLocalStorage` so helpers can reach it without the context being threaded through every call. Wrap `getContext()` in app-specific helpers: ```typescript // app/utils/context.ts import { getContext } from 'remix/middleware/async-context' import { Auth } from 'remix/middleware/auth' import { Database } from 'remix/data-table' import { Session } from 'remix/session' export function getCurrentDb() { return getContext().get(Database) } export function getCurrentSession() { return getContext().get(Session) } export function getCurrentUser() { let auth = getContext().get(Auth) if (!auth.ok) { throw new Error('Expected an authenticated user. Run requireAuth() before this code.') } return auth.identity } export function getCurrentUserSafely() { let auth = getContext().get(Auth) return auth.ok ? auth.identity : null } ``` ## Middleware Types Middleware has three API-owned forms: 1. **Router middleware** — runs for every request: ```typescript let router = createRouter({ middleware: [logger(), session(cookie, storage)] }) ``` 2. **Controller middleware** — runs for the direct actions in one controller: ```typescript export default createController(routes.account, { middleware: [requireAuth()], actions: { ... }, }) ``` Controller middleware does not flow into other controllers. Add the middleware to each controller that needs it. 3. **Action middleware** — runs for a single action: ```typescript router.get(routes.account.index, { middleware: [requireAuth()], handler(context) { return render() }, }) ``` Prefer inline arrays for `middleware` options. Use `RouterContext` to derive an app context from a router that uses inline middleware. Use `createMiddleware()` only when a chain is stored in a variable and its exact tuple type needs to be preserved, such as when deriving `MiddlewareContext` without a router value, exporting a reusable chain, or returning a chain from a factory. ## Node Server Setup New apps already include a `server.ts` that adapts the app router with `remix/node-fetch-server`. Keep that generated server unless the task specifically needs to change runtime behavior such as host/protocol handling, TLS, HTTP/2, WebSockets, deployment lifecycle, or test-only server setup. Use `remix/node-fetch-server` when you want to keep owning a standard Node `http`, `https`, or `http2` server directly.