<!-- Markdown mirror of https://justscale.sh/docs/fundamentals/middleware -->

# Middleware

Context accumulation with type-safe middleware

Middleware extends the handler context with new properties. Each `.use()` call adds to what came before — TypeScript tracks the accumulated context so your handler sees exactly what's available.

## Inline Middleware

The simplest middleware is an inline function that returns an object. The returned properties are merged into the context:

inline-middleware.tsTypeScript

```typescript
import { Get } from '@justscale/http';

Get('/tickets')
  .use(() => ({ requestedAt: Date.now() }))
  .handle(({ requestedAt, res }) => {
    // requestedAt is typed as number — added by the middleware
    res.json({ requestedAt });
  });
```

## Context Accumulation

Multiple `.use()` calls accumulate context. Each middleware sees everything that came before it:

accumulation.tsTypeScript

```typescript
import { Get } from '@justscale/http';
import { auth } from '@justscale/auth';

Get('/tickets/:ticket')
  .use(auth)                  // ctx now has: { user }
  .use(async ({ user }) => {  // can access user from previous .use()
    const role = user.email.endsWith('@support.com') ? 'agent' : 'customer';
    return { role };           // adds: { role }
  })
  .handle(({ user, role, params, res }) => {
    // Both user and role are available and typed
    if (role === 'agent') {
      res.json({ tickets: 'all' });
    } else {
      res.json({ tickets: 'own' });
    }
  });
```

## Async Middleware

Middleware can be async — useful for loading data that the handler needs:

async-middleware.tsTypeScript

```typescript
import { Get } from '@justscale/http';
import { auth } from '@justscale/auth';

// Async middleware — fetches additional data before the handler runs
Get('/dashboard')
  .use(auth)
  .use(async ({ user }) => {
    // Fetch preferences from an external API
    const res = await fetch(`https://api.example.com/prefs/${user.email}`);
    const preferences = await res.json();
    return { preferences };  // adds { preferences } to context
  })
  .handle(({ user, preferences, res }) => {
    // Both user (from auth) and preferences (from async middleware) available
    res.json({ user: user.email, theme: preferences.theme });
  });
```

## DI Middleware (MiddlewareDef)

For middleware that needs injected services, use `createMiddleware`. This lets middleware participate in the DI system:

Files

srcmiddlewareaudit-log.ts

controllersusage.ts

srcmiddlewareaudit-log.ts

controllersusage.ts

src/middleware/audit-log.tsTypeScript

```typescript
import { createMiddleware, Logger } from '@justscale/core';

// Middleware with DI — Logger is injected automatically
export const auditLog = createMiddleware({
  inject: { logger: Logger },
  handler: ({ logger }) => async (ctx: { user?: { email: string } }) => {
    const who = ctx.user?.email ?? 'anonymous';
    logger.info('Request', { user: who });
    return { auditedAt: new Date() };
  },
});
```

## The auth Middleware

The most common middleware is `auth` from `@justscale/auth`. It validates the session token and adds `user` to the context:

auth-usage.tsTypeScript

```typescript
import { Get, Post } from '@justscale/http';
import { auth } from '@justscale/auth';

// After .use(auth), the handler has { user: Persistent<User> }
Get('/tickets')
  .use(auth)
  .handle(({ user, res }) => {
    // user.email, user.name — typed fields from the User model
    res.json({ loggedInAs: user.email });
  });

// Without auth — no user in context
Get('/health').handle(({ res }) => {
  res.json({ status: 'ok' });
});
```

## The permissions Middleware

Stack `permissions` from `@justscale/permission` after `auth` when a route uses permission-scoped`.returns()`. It resolves the caller's principals, stores them for downstream query filtering, and wraps `res` with a `.permission` discriminant matching the rule that fired.

permissions-usage.tsTypeScript

```typescript
import { Get } from '@justscale/http';
import { auth } from '@justscale/auth';
import { permissions, assertNever } from '@justscale/permission';
import { Employee } from '../models/employee';
import { EmployeeFull, EmployeeLimited } from '../schemas/employee';

Get('/employees/:employee')
  .types({ Employee })
  .use(auth)
  .use(permissions)
  .guard(Employee.can.view)
  .returns(200, EmployeeFull,    Employee.can.fullAccess)
  .returns(200, EmployeeLimited, Employee.can.view)
  .handle(({ params, res }) => {
    switch (res.permission) {
      case 'fullAccess': res.json(params.employee); return;
      case 'view':       res.json({ name: params.employee.name }); return;
      default: assertNever(res);
    }
  });
```

See [Permissions](https://justscale.sh/docs/features/permissions) for the full picture — declaring rules on models and querying with them.

## Best Practices

- Middleware adds, never removes — each .use() extends the context. It can't remove properties added by previous middleware
- Order matters — later middleware can access earlier additions. .use(auth).use(loadProfile) works; the reverse doesn't
- Use DI middleware for shared logic — if middleware needs services, use createMiddleware instead of closures
- Throw to stop — if middleware throws, execution stops and the error is returned to the client

## Next Steps

- Guards
- Routes
- Controllers
