Remix Middleware

raw JSON →
0.1.1 verified Thu Apr 23 auth: no javascript

remix-middleware provides an Express-like middleware stack for Remix's `loader` and `action` functions, allowing developers to centralize cross-cutting concerns such as authentication, logging, and response manipulation. Currently at version `0.1.1`, this library is still considered experimental, indicating that its API may evolve rapidly. It differentiates itself by deeply integrating with Remix's server-side data functions and offering specialized middleware for common Remix patterns, including streamlined integration with `remix-auth`. While there isn't a fixed release cadence, development appears active, with several minor releases building out core features and addressing early architectural concerns, particularly around preventing server-only code from being bundled for the browser.

error TypeError: (0 , remix_middleware__WEBPACK_IMPORTED_MODULE_X__.createMiddleware) is not a function
cause This error typically occurs in environments that struggle with ESM imports, often when a build system or test runner is misconfigured to treat ESM as CommonJS, or vice-versa.
fix
Ensure your project, especially test setups or unusual build configurations, is correctly configured for ESM. Check tsconfig.json for module and moduleResolution settings (e.g., "module": "esnext", "moduleResolution": "bundler" for newer Node/Remix projects) and your package.json type field (set to "module").
error Error: You did not return a value or a Response from your loader/action. This is a common issue when using external tools or incorrectly wrapping the `mdw.run` call.
cause The `mdw.run` function executes your middleware stack, and the final handler in the stack (or a preceding middleware) must set `ctx.response`. If this is missed, Remix receives no return value.
fix
Verify that the final function passed to mdw.run(props, (ctx) => { /* ... */ }) explicitly assigns ctx.response = yourData or ctx.response = new Response(...). The mdw.response helper can simplify this for simple JSON data.
error Property 'user' does not exist on type 'RemixMiddlewareContext<unknown>'
cause This TypeScript error occurs when using the `isAuthenticated` middleware for `remix-auth` but the `AuthCtx` type parameter has not been correctly applied to `createMiddleware`.
fix
When creating your middleware instance that uses isAuthenticated, explicitly define its context type: export const authed = createMiddleware<AuthCtx<User>>(); where User is your application's user type.
breaking Version `0.1.0` introduced a significant change to prevent server-side code from being erroneously included in the browser bundle. This involved a new API, meaning direct upgrades from `0.0.x` might require updating how middleware is defined and executed to avoid bundling issues.
fix Review the new API usage documented for `0.1.0` and ensure your middleware setup correctly uses the updated patterns to manage server-only code. Specifically, ensure `mdw.run` is used to wrap your loader/action logic.
gotcha The package is still marked as 'experimental' (first released as `0.0.1` experimental). This implies that the API surface may not be stable and could change in future minor or patch releases, potentially introducing breaking changes without a major version bump.
fix Monitor GitHub releases and the project's README for updates. Consider pinning to a specific patch version if stability is critical, and be prepared for potential refactoring when upgrading.
gotcha For middleware to correctly set the response, you must assign the return value to `ctx.response`. If `ctx.response` is not set by any middleware or the final handler, the Remix `loader` or `action` will not return a valid response, leading to errors.
fix Ensure that your terminal middleware function (the one passed to `mdw.run`) or one of the preceding middleware in the stack explicitly sets `ctx.response = yourData` or `ctx.response = new Response(...)`.
gotcha When integrating with `remix-auth`, ensure you properly type your `createMiddleware` call with `AuthCtx<UserType>` to gain correct TypeScript inference for `ctx.user`. Forgetting this will result in `ctx.user` being typed as `unknown` or `undefined`.
fix Declare your middleware creator like `export const authed = createMiddleware<AuthCtx<User>>();` where `User` is your authenticated user type.
npm install remix-middleware
yarn add remix-middleware
pnpm add remix-middleware

This example demonstrates how to set up `remix-middleware` with a global logger and then apply it to a Remix `loader` and `action` to process requests and set responses, including basic form handling. It also outlines the optional setup for `remix-auth` integration.

import { createMiddleware, isAuthenticated } from 'remix-middleware';
import type { ActionFunction, LoaderFunction } from '@remix-run/node'; // Or '@remix-run/react' depending on Remix version

// For remix-auth integration (optional)
// import { Authenticator } from 'remix-auth';
// import { sessionStorage } from './session';
// interface User { id: string; email: string; }
// export const authenticator = new Authenticator<User>(sessionStorage);

// ./app/middleware.ts
export const mdw = createMiddleware();

mdw.use(async (ctx, next) => {
  console.log('middleware activated for', ctx.request.url);
  // ctx.request and ctx.response are available here
  await next();
  console.log('middleware completed for', ctx.request.url);
});

// Example using remix-auth middleware (uncomment if using remix-auth)
// export const authed = createMiddleware<AuthCtx<User>>();
// authed.use(isAuthenticated(authenticator, { failureRedirect: '/login' }));
// authed.use(authed.routes());

mdw.use(mdw.routes()); // Important: This must be called at the end of the middleware stack

// ./app/routes/posts/index.tsx
// Assuming you have a file at `~/middleware.ts`
// import { mdw } from '~/middleware';
// import { Form, useLoaderData } from '@remix-run/react';

interface Post { id: string; title: string; }

export const loader: LoaderFunction = (props) =>
  mdw.run(props, (ctx) => {
    // Simulate fetching data
    ctx.response = [
      { id: '1', title: 'My First Post' },
      { id: '2', title: 'A Mixtape I Made Just For You' }
    ];
  });

export const action: ActionFunction = (props) =>
  mdw.run(props, async (ctx) => {
    const body = await ctx.request.formData();
    const post = { id: '3', title: String(body.get('title')) };
    ctx.response = post;
  });

// export default function Posts() {
//   const posts = useLoaderData<Post[]>();
//   return (
//     <div>
//       <h1>Posts</h1>
//       <div>{posts.map((post) => (<div key={post.id}>{post.title}</div>))}
//       </div>
//       <Form method="post"><p><label>Title: <input name="title" type="text" /></label></p><p><button type="submit">Create</button></p></Form>
//     </div>
//   );
// }