Using Middleware
Middleware lets you run code before and after every request your service handles. Authentication checks, response caching, request logging, payload validation -- these are all common middleware use cases. This guide covers how to install, register, configure, and order middleware in your services.
For how the middleware system works conceptually, see Middleware Model. For how to build your own middleware, see Writing Middleware.
Installing Middleware
Strata publishes first-party middleware as separate npm packages under the @strata-js scope:
npm install @strata-js/mw-cache
npm install @strata-js/mw-message-logging
npm install @strata-js/mw-payload-validationEach package exports a class (or factory) that you instantiate with options and then register on your service, context, or operation.
Registering Middleware
Middleware can be registered at three levels. The level determines the scope -- which requests the middleware applies to.
Global (Service-level)
Global middleware runs on every request the service processes, across all contexts and operations. Use this for cross-cutting concerns like authentication or request logging.
import { StrataService } from '@strata-js/strata';
import { MessageLoggingMiddleware } from '@strata-js/mw-message-logging';
const service = new StrataService(config);
// Runs on every request in every context
service.useMiddleware(new MessageLoggingMiddleware());You can also register multiple middleware at once by passing an array:
service.useMiddleware([
new AuthMiddleware(),
new MessageLoggingMiddleware(),
]);Context-level
Context-level middleware runs on every operation within a specific context. Use this when you need middleware that only applies to a subset of your operations.
import { StrataContext } from '@strata-js/strata';
import { PayloadValidationMiddleware } from '@strata-js/mw-payload-validation';
const users = new StrataContext('users');
// Runs on every operation in the 'users' context
users.useMiddleware(new PayloadValidationMiddleware(userSchemas));Operation-level
Operation-level middleware runs on a single operation only. Pass it as the fourth argument to registerOperation().
import { CacheMiddleware } from '@strata-js/mw-cache';
users.registerOperation('get', async (request) =>
{
return { user: await db.findUser(request.payload.userId) };
}, [], [ new CacheMiddleware({ ttl: 60000 }) ]);The third argument ([]) is for operation function parameters. The fourth is the middleware array.
Configuring Middleware
Each middleware package defines its own configuration options, passed to the constructor. Consult the specific middleware documentation for details:
// Cache middleware with a 60-second TTL
const cache = new CacheMiddleware({ ttl: 60000 });
// Message logging that only logs failures
const logging = new MessageLoggingMiddleware({ logLevel: 'warn', onlyFailures: true });Since middleware is just a class instance, you can also configure it dynamically:
const env = process.env.ENVIRONMENT ?? 'local';
const cache = new CacheMiddleware({
ttl: env === 'production' ? 300000 : 5000,
});Middleware Ordering
The order in which middleware is registered matters. Middleware executes in an outside-in / inside-out pattern:
beforeRequest() -- Outside-in
For the beforeRequest() hook, middleware runs in registration order, from broadest scope to narrowest:
- Global middleware (in registration order)
- Context middleware (in registration order)
- Operation middleware (in array order)
success() / failure() -- Inside-out
For the success() and failure() hooks, the order is reversed:
- Operation middleware
- Context middleware
- Global middleware
This means global middleware wraps everything -- it sees the raw request first and the final response last.
flowchart LR
G["Global"] -->|"beforeRequest()"| C["Context"]
C -->|"beforeRequest()"| O["Operation"]
O -->|"Handler"| H["Operation Handler"]
H -->|"Result"| O2["Operation"]
O2 -->|"success() / failure()"| C2["Context"]
C2 -->|"success() / failure()"| G2["Global"]Practical Example
If you register middleware like this:
// Global
service.useMiddleware(new AuthMiddleware()); // 1st global
service.useMiddleware(new LoggingMiddleware()); // 2nd global
// Context
users.useMiddleware(new ValidationMiddleware()); // 1st context
// Operation
users.registerOperation('create', handler, [], [
new CacheMiddleware(), // 1st operation
]);Then for a request to users/create:
| Phase | Order |
|---|---|
beforeRequest() | Auth -> Logging -> Validation -> Cache |
success() / failure() | Cache -> Validation -> Logging -> Auth |
Short-circuiting
If any beforeRequest() hook calls request.succeed() or request.fail(), the remaining beforeRequest() hooks and the operation handler are skipped. Execution jumps directly to success() or failure(). This is how auth middleware rejects unauthorized requests without hitting the handler.
The success() and failure() hooks always run -- they cannot be skipped. This ensures cleanup middleware (like logging or metrics) always executes.
Common Patterns
Authentication
Register auth middleware globally so every request is checked:
service.useMiddleware(new AuthMiddleware({
headerField: 'auth',
validateToken: async (token) => verifyJWT(token),
}));Caching
Register cache middleware on specific operations that benefit from it:
users.registerOperation('get', handler, [], [
new CacheMiddleware({ ttl: 60000 }),
]);Logging
Register logging middleware globally to capture all traffic:
service.useMiddleware(new MessageLoggingMiddleware());Payload Validation
Register validation middleware on a context to validate all operations within it:
users.useMiddleware(new PayloadValidationMiddleware({
create: createSchema,
get: getSchema,
list: listSchema,
}));Next Steps
- Writing Middleware -- build your own custom middleware.
- Middleware Model -- the conceptual model behind the three hooks.
- Cache Middleware -- response caching middleware reference.
- Message Logging Middleware -- request/response logging middleware reference.
- Payload Validation Middleware -- schema validation middleware reference.