HTTP@rhythmjs/http
Mounting handlers
Hand a path, and everything beneath it, to a fetch-style handler from another library: Better Auth, a webhook SDK, a tRPC server.
On this page
Mount a fetch handler#
Many libraries expose a Web-standard (request) => Response handler. mount(path, handler) from @rhythmjs/http/mount runs it for the paths you give and lets every other request carry on through the app.
bun add @rhythmjs/http @rhythmjs/rhythm @rhythmjs/routerimport { mount } from "@rhythmjs/http/mount";
const app = new Rhythm<RhythmHttpContext>()
.use(mount("/api/auth/**", (ctx) => auth.handler(ctx.request)))
.use(router.middleware());When the handler returns a Response, its status, headers and body stream become the response and the chain stops. Every Set-Cookie header is kept, and headers set earlier in the chain (CORS, for example) stay unless the Response sets them again.
Paths follow the router's patterns#
path uses the same rou3 conventions as RhythmRouter: static segments, :name parameters, * for one segment and ** as the catch-all. The HTTP method is not part of the match.
"/api/auth/**"matches/api/authand everything beneath it at any depth, but not/api/authx. This is the usual choice for a library that owns a whole prefix."/api/auth/*"matches one segment only, so it would miss/api/auth/sign-up/email."/hooks/:id/**"matches a named segment followed by anything."/**"mounts the whole app.
A path that does not start with / throws a TypeError when you call mount.
Return a Response, or behave like middleware#
The handler is a middleware, (ctx, next) => Response | void. Returning a Response ends the request there. Returning nothing leaves the decision to the handler, which can write ctx.response itself or call next().
mount("/api/**", async (ctx, next) => {
ctx.response.headers.set("x-mounted", "true");
await next();
});An error thrown by the handler propagates like any middleware error, so a filter() above the mount maps it.
Order matters#
Middleware registered before the mount runs for the mounted path too, and one registered after it does not, because the mount ends the request. Put cross-cutting middleware such as cors() first, so a preflight is answered before the handler and the handler's responses carry the headers.
.use(cors({ origin: frontendOrigin, credentials: true }))
.use(mount("/api/auth/**", (ctx) => auth.handler(ctx.request)))Things to check#
- Rhythm middleware registered after the mount does not see the requests it answers, so rate-limit or authenticate those paths with the mounted library's own settings.
- The handler reads
ctx.request; if an earlier middleware already consumed the body, clone it first.
The Better Auth recipe mounts its handler this way, with CORS in front. The signature is in the API reference.