Middleware
Runtime validation middleware for Express, Next.js, and other frameworks
Overview
Middleware automatically validates AI responses before they reach users, providing a hard safety gate in production APIs.
Request → AI → Validator Middleware → Response
Express.js Middleware
Basic Usage
import express from 'express';
import { governorValidator } from '@the-governor-hq/constitution-core';
const app = express();
app.use(express.json());
// Apply validator middleware
app.post('/api/chat',
governorValidator({ domain: 'wearables', onViolation: 'block' }),
async (req, res) => {
const aiResponse = await callLLM(req.body.message);
// Response auto-validated by middleware
res.json({ message: aiResponse });
}
);
app.listen(3000);What happens:
- User sends request
- Your handler generates AI response
- Middleware validates before sending
- Unsafe content blocked automatically
Configuration
app.post('/api/insights',
governorValidator({
domain: 'wearables',
onViolation: 'block', // Block unsafe content
strictMode: true, // Use LLM judge
useLLMJudge: true, // For edge cases
apiKey: process.env.OPENAI_API_KEY,
defaultSafeMessage: 'Unable to generate safe insight.'
}),
async (req, res) => {
// Your handler
}
);Custom Violation Handler
app.post('/api/chat',
governorValidator({
domain: 'wearables',
onViolation: (violations, original) => {
// Log violations to monitoring
logger.error('Safety violation', { violations, original });
// Return custom safe message
return {
message: 'I can help with general wellness questions.',
violations: violations.map(v => v.id)
};
}
}),
async (req, res) => {
// Handler
}
);Next.js API Routes
App Router (Next.js 13+)
// app/api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createValidator } from '@the-governor-hq/constitution-core';
const validator = createValidator({
domain: 'wearables',
onViolation: 'block'
});
export async function POST(request: NextRequest) {
const { message } = await request.json();
const aiResponse = await callLLM(message);
// Validate before returning
const result = await validator.validate(aiResponse);
if (!result.safe) {
return NextResponse.json({
message: result.safeAlternative,
blocked: true,
violations: result.violations.map(v => v.id)
}, { status: 200 });
}
return NextResponse.json({ message: result.output });
}Pages Router (Next.js 12)
// pages/api/chat.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { createValidator } from '@the-governor-hq/constitution-core';
const validator = createValidator({ domain: 'wearables' });
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { message } = req.body;
const aiResponse = await callLLM(message);
const result = await validator.validate(aiResponse);
if (!result.safe) {
return res.json({
message: result.safeAlternative,
blocked: true
});
}
res.json({ message: result.output });
}tRPC Middleware
import { TRPCError } from '@trpc/server';
import { createValidator } from '@the-governor-hq/constitution-core';
const validator = createValidator({ domain: 'wearables' });
const governorMiddleware = t.middleware(async ({ next }) => {
const result = await next();
if (typeof result.data === 'string') {
const validation = await validator.validate(result.data);
if (!validation.safe) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: validation.safeAlternative
});
}
}
return result;
});
export const protectedProcedure = t.procedure.use(governorMiddleware);GraphQL Middleware
import { createValidator } from '@the-governor-hq/constitution-core';
const validator = createValidator({ domain: 'wearables' });
const resolvers = {
Query: {
getInsight: async (_, { userId }) => {
const insight = await generateInsight(userId);
const result = await validator.validate(insight);
return {
content: result.safe ? result.output : result.safeAlternative,
safe: result.safe,
violations: result.violations
};
}
}
};Streaming Responses
For Server-Sent Events or streaming AI responses:
import { createValidator } from '@the-governor-hq/constitution-core';
const validator = createValidator({ domain: 'wearables' });
app.post('/api/stream', async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const stream = await callStreamingLLM(req.body.message);
let buffer = '';
for await (const chunk of stream) {
buffer += chunk;
// Validate accumulated buffer periodically
if (buffer.length % 100 === 0) {
const result = await validator.validate(buffer);
if (!result.safe) {
// Abort stream
res.write(`data: ${JSON.stringify({ error: 'Content blocked' })}\n\n`);
res.end();
return;
}
}
res.write(`data: ${chunk}\n\n`);
}
// Final validation
const final = await validator.validate(buffer);
if (!final.safe) {
res.write(`data: ${JSON.stringify({ blocked: true })}\n\n`);
}
res.end();
});WebSocket Middleware
import { Server } from 'socket.io';
import { createValidator } from '@the-governor-hq/constitution-core';
const io = new Server(server);
const validator = createValidator({ domain: 'wearables' });
io.on('connection', (socket) => {
socket.on('message', async (data) => {
const aiResponse = await callLLM(data.message);
const result = await validator.validate(aiResponse);
socket.emit('response', {
message: result.safe ? result.output : result.safeAlternative,
safe: result.safe,
violations: result.safe ? [] : result.violations.map(v => v.id)
});
});
});Custom Middleware Factory
Create reusable middleware for your stack:
import { createValidator, type ValidatorConfig } from '@the-governor-hq/constitution-core';
export function createGovernorMiddleware(config: ValidatorConfig) {
const validator = createValidator(config);
return async (req: any, res: any, next: any) => {
// Store original json method
const originalJson = res.json.bind(res);
// Override json method to validate responses
res.json = async (data: any) => {
if (data.message) {
const result = await validator.validate(data.message);
if (!result.safe) {
data.message = result.safeAlternative;
data.blocked = true;
data.violations = result.violations.map(v => v.id);
}
}
return originalJson(data);
};
next();
};
}
// Usage
app.use(createGovernorMiddleware({ domain: 'wearables' }));Error Handling
app.post('/api/chat',
governorValidator({ domain: 'wearables' }),
async (req, res, next) => {
try {
const aiResponse = await callLLM(req.body.message);
res.json({ message: aiResponse });
} catch (error) {
// Middleware catches validation errors
next(error);
}
}
);
// Error handler
app.use((err, req, res, next) => {
if (err.type === 'GOVERNOR_VALIDATION') {
return res.status(200).json({
message: err.safeAlternative,
blocked: true,
violations: err.violations
});
}
next(err);
});Performance Optimization
Caching
import { createValidator } from '@the-governor-hq/constitution-core';
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 3600 });
const validator = createValidator({ domain: 'wearables' });
async function validateWithCache(text: string) {
const cached = cache.get(text);
if (cached) return cached;
const result = await validator.validate(text);
cache.set(text, result);
return result;
}Async Validation (Log Only)
For analytics without blocking:
app.post('/api/chat', async (req, res) => {
const aiResponse = await callLLM(req.body.message);
// Send response immediately
res.json({ message: aiResponse });
// Validate asynchronously for logging
validator.validate(aiResponse).then(result => {
if (!result.safe) {
logger.warn('Unsafe content sent', {
violations: result.violations,
message: aiResponse
});
}
});
});Monitoring & Metrics
import { createValidator } from '@the-governor-hq/constitution-core';
const validator = createValidator({
domain: 'wearables',
onViolation: (violations, original) => {
// Send to monitoring
metrics.increment('governor.violations', {
count: violations.length,
rules: violations.map(v => v.id).join(',')
});
// Log for debugging
logger.warn('Validation failed', {
violations,
original,
timestamp: new Date()
});
// Return safe alternative
return 'Unable to generate safe response.';
}
});Testing Middleware
import request from 'supertest';
import app from './app';
describe('Governor Middleware', () => {
it('blocks medical diagnosis', async () => {
const response = await request(app)
.post('/api/chat')
.send({ message: 'What does my HRV mean?' });
expect(response.body.message).not.toContain('disease');
expect(response.body.message).not.toContain('diagnosis');
});
it('allows safe observations', async () => {
const response = await request(app)
.post('/api/chat')
.send({ message: 'Tell me about my sleep patterns' });
expect(response.status).toBe(200);
expect(response.body.blocked).toBeUndefined();
});
});Summary
Middleware provides:
- ✅ Automatic validation in API responses
- ✅ Framework integration (Express, Next.js, tRPC, GraphQL)
- ✅ Streaming support
- ✅ Custom error handling
- ✅ Performance optimization
- ✅ Monitoring integration
Best practices:
- Use
blockmode in production - Cache validation results
- Monitor violations
- Test edge cases
- Handle errors gracefully
Next: CLI Tools for development-time validation