|
| 1 | +--- |
| 2 | +Title: Dedupe Middleware |
| 3 | +Description: Improve performance and ensure safety when running oRPC middleware multiple times. |
| 4 | +--- |
| 5 | + |
| 6 | +# Dedupe Middleware |
| 7 | + |
| 8 | +This guide explains how to optimize your [middleware](/docs/middleware) so it remains fast, efficient, and safe even when executed multiple times. |
| 9 | + |
| 10 | +## Problem |
| 11 | + |
| 12 | +When a procedure [calls](/docs/client/server-side#using-the-call-utility) another procedure, overlapping middleware might be applied in both. |
| 13 | + |
| 14 | +Similarly, when using `.use(auth).router(router)`, some procedures inside `router` might already include the `auth` middleware. |
| 15 | + |
| 16 | +:::warning |
| 17 | +This duplication can lead to repeated execution of middleware, causing unexpected behaviors or performance issues, especially if the middleware is resource-intensive. |
| 18 | +::: |
| 19 | + |
| 20 | +## Solution |
| 21 | + |
| 22 | +The solution is to use the `context` to track which middleware has already run. For example: |
| 23 | + |
| 24 | +```ts twoslash |
| 25 | +import { os } from '@orpc/server' |
| 26 | +declare function connectDb(): Promise<'a_fake_db'> |
| 27 | +// ---cut--- |
| 28 | +const dbProvider = os |
| 29 | + .$context<{ db?: Awaited<ReturnType<typeof connectDb>> }>() |
| 30 | + .middleware(async ({ context, next }) => { |
| 31 | + if (context.db) { |
| 32 | + return next({ context: { db: context.db } }) |
| 33 | + } |
| 34 | + |
| 35 | + const db = await connectDb() |
| 36 | + return next({ context: { db } }) |
| 37 | + }) |
| 38 | +``` |
| 39 | + |
| 40 | +In this example, the `dbProvider` middleware checks if a database connection already exists in the `context`. If it does, it simply passes the context along; if not, it establishes a connection and saves it in the `context`. |
| 41 | + |
| 42 | +This middleware can now be safely applied multiple times: |
| 43 | + |
| 44 | +```ts twoslash |
| 45 | +import { call, os } from '@orpc/server' |
| 46 | + |
| 47 | +declare function connectDb(): Promise<'a_fake_db'> |
| 48 | +const dbProvider = os |
| 49 | + .$context<{ db?: Awaited<ReturnType<typeof connectDb>> }>() |
| 50 | + .middleware(async ({ context, next }) => { |
| 51 | + if (context.db) { |
| 52 | + return next({ context: { db: context.db } }) |
| 53 | + } |
| 54 | + |
| 55 | + const db = await connectDb() |
| 56 | + return next({ context: { db } }) |
| 57 | + }) |
| 58 | +// ---cut--- |
| 59 | +const foo = os.use(dbProvider).handler(({ context }) => 'Hello World') |
| 60 | + |
| 61 | +const bar = os.use(dbProvider).handler(({ context }) => call(foo, { context })) |
| 62 | + |
| 63 | +const router = os |
| 64 | + .use(dbProvider) |
| 65 | + .use(({ next }) => { |
| 66 | + // Additional middleware logic |
| 67 | + return next() |
| 68 | + }) |
| 69 | + .router({ |
| 70 | + foo, |
| 71 | + bar, |
| 72 | + }) |
| 73 | +``` |
| 74 | + |
| 75 | +Even when `dbProvider` is used multiple times, the middleware efficiently ensures that the database connection is established only once, preserving both correctness and performance. |
| 76 | + |
| 77 | +## Conclusion |
| 78 | + |
| 79 | +This recommended pattern leverages the context to prevent duplicate middleware execution, ensuring both efficiency and safety. We believe this approach is highly effective, and when writing middleware, you should keep this pattern in mind to avoid unnecessary overhead and potential performance issues. |
0 commit comments