sdxc
SearchCtrlK

[ The sdxc command ]

Make a middleware

Write a middleware with sdxc make middleware and, with --mount, call it last in the router's chain or in a controller chain such as page or api.

Last updated 2026-10-19

A middleware runs before a controller: it publishes something on the context, such as the signed-in user, or answers the request itself, such as a redirect to sign in. sdxc make middleware writes one in app/http/middleware/, and --mount puts it in a chain in the same run:

sdxc make middleware require-user --mount page
Wrote app/http/middleware/require-user.ts
Wrote app/http/middleware/require-user.test.ts
Edited app/http/middleware/page.ts

Reach for it when several controllers need the same step. A step one controller needs belongs in that controller.

Flags

FlagDefaultWhat it does
--mount <target>import it and call it last in that chain
--test, --no-testmake.testswrite the test beside it, or skip it
--dry-runprint the files and the diff, and change nothing

What it writes

The middleware is a factory that takes no options and returns the function the chain runs:

app/http/middleware/require-user.ts
import type { Middleware } from "remix/router";

/** Creates the middleware; add it to a router's chain or to a route's `lazy()` guards. */
export default function requireUser(): Middleware {
	return (_context, next) => {
		// TODO: publish on the context, or answer before the handler runs
		return next();
	};
}

The function is named after the file's last segment in camelCase, so auth/require-user writes app/http/middleware/auth/require-user.ts exporting requireUser.

Mount it on a chain

--mount imports the middleware's default export and calls it last in the chain you name, so it runs after everything the chain already publishes: the session, the language, the renderer.

sdxc make middleware require-user --mount page
sdxc make middleware request-id --mount router
--- a/app/http/middleware/page.ts
+++ b/app/http/middleware/page.ts
+import requireUser from "~/app/http/middleware/require-user";
@@
 	render({ assets: assetResolver }),
+	requireUser(),
 );

The targets are your project's own, each named after its file:

TargetFound asThe call goes
router, …an initializer in app/initializers/ that imports and calls createRouterlast in its middleware array
page, api, …a module under app/http/middleware/ whose default export is createMiddleware(…)last among its arguments

A chain in a folder takes its path, such as admin/page. A router initializer and a chain with the same name resolve to the router initializer. A target the project lacks stops the command before anything is written and lists the ones it has:

No middleware chain "admin" to mount on; this project has: router, api, page

The router chain runs for every request, pages and endpoints alike; page runs for every controller that declares it as its middleware, and api for every endpoint. When the call cannot be placed, such as a router that passes no middleware array or a chain that already binds the name, make writes the middleware and its test and prints the import and the call to add by hand.

To run it before an entry that is already in the chain, move the call after the run. A middleware that needs options gets them where it is called.

The test

app/http/middleware/require-user.test.ts
import { describe, expect, test } from "vitest";

import requireUser from "~/app/http/middleware/require-user";

describe("require-user", () => {
	test("creates a middleware function", () => {
		expect(typeof requireUser()).toBe("function");
	});
});
bun run test app/http/middleware/require-user.test.ts

Fill it in

Publish a value with a context key the controllers read, or return a response to stop the chain. A middleware that guards a group of routes, instead of a whole chain, goes in lazy()'s second argument where the router maps them, so it answers before the controller's module loads. Wire the router covers both, and Authorization the rules a guard asks, which sdxc make policy writes.

Where to go next

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.