jlnstack

Middlewares

Creating and composing middlewares

Middlewares

Middlewares are functions that transform the context. Each middleware receives the current context and a next function to continue the chain.

Creating Middlewares

Use the middleware helper from your initialized factory:

const { middleware } = init({
  ctx: async () => ({ db: getDb() })
})

const withAuth = middleware(({ ctx, next }) => {
  return next({ ctx: { ...ctx, user: getCurrentUser() } })
})

const withTenant = middleware(({ ctx, next }) => {
  return next({ ctx: { ...ctx, tenant: getTenant(ctx.user) } })
})

Chaining Middlewares

Use .use() to add middlewares to a procedure. Each middleware receives the context from the previous one:

procedure
  .use(withAuth)      // ctx: { db, user }
  .use(withTenant)    // ctx: { db, user, tenant }
  .run(async ({ ctx }) => {
    // ctx is fully typed with all properties
  })

Parallel Execution

Pass an array to .use() to run middlewares in parallel. Their results are merged:

const fetchUser = middleware(async ({ ctx, next }) => {
  return next({ ctx: { ...ctx, user: await fetchUserData() } })
})

const fetchSettings = middleware(async ({ ctx, next }) => {
  return next({ ctx: { ...ctx, settings: await fetchUserSettings() } })
})

procedure
  .use([fetchUser, fetchSettings])  // runs in parallel
  .run(async ({ ctx }) => {
    // ctx: { db, user, settings }
  })

Async Middlewares

Middlewares can be async:

const withAuth = middleware(async ({ ctx, next }) => {
  const session = await getSession()
  return next({ ctx: { ...ctx, session } })
})

Reusing Middlewares

Define middlewares once and reuse across procedures:

// lib/middlewares.ts
export const withAuth = middleware(...)
export const withTenant = middleware(...)

// features/posts.ts
const getPost = procedure
  .use(withAuth)
  .run(...)

// features/users.ts  
const getUser = procedure
  .use(withAuth)
  .use(withTenant)
  .run(...)

On this page