Agentuity routes are built on Hono, giving you access to Hono's middleware system. Use middleware to add authentication, logging, CORS, rate limiting, and other shared request processing.
Middleware works on HTTP, WebSocket, SSE, Cron, and Stream routes.
Basic Middleware
Create middleware with createMiddleware from Hono:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = new Hono<Env>();
// Middleware that logs all requests
const loggerMiddleware = createMiddleware(async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
c.var.logger.info('Request completed', {
method: c.req.method,
path: c.req.path,
duration,
});
});
// Apply to specific route
router.get('/data', loggerMiddleware, (c) => {
return c.json({ data: 'value' });
});
export default router;Authentication Patterns
API Key Authentication
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = new Hono<Env>();
const apiKeyAuth = createMiddleware(async (c, next) => {
const apiKey = c.req.header('X-API-Key');
if (!apiKey) {
return c.json({ error: 'API key required' }, 401);
}
// Validate against stored keys
const keyData = await c.var.kv.get('api-keys', apiKey);
if (!keyData.exists) {
return c.json({ error: 'Invalid API key' }, 401);
}
// Add user info to context for downstream handlers
c.set('apiKeyOwner', keyData.data.ownerId);
await next();
});
router.get('/protected', apiKeyAuth, (c) => {
const ownerId = c.var.apiKeyOwner;
return c.json({ message: 'Access granted', ownerId });
});
export default router;Bearer Token (JWT)
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import { verify } from 'hono/jwt';
const router = new Hono<Env>();
const jwtAuth = createMiddleware(async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return c.json({ error: 'Bearer token required' }, 401);
}
const token = authHeader.slice(7);
try {
const payload = await verify(token, process.env.JWT_SECRET!);
c.set('user', payload);
await next();
} catch {
return c.json({ error: 'Invalid token' }, 401);
}
});
router.get('/me', jwtAuth, (c) => {
const user = c.var.user;
return c.json({ userId: user.sub, email: user.email });
});
export default router;Authentication Middleware
The @agentuity/auth package provides authentication middleware:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createSessionMiddleware } from '@agentuity/auth';
import { auth } from '../auth';
const router = new Hono<Env>();
router.get('/public', (c) => {
return c.json({ message: 'Public endpoint' });
});
// Protected route requiring authentication
router.get('/protected', createSessionMiddleware(auth), async (c) => {
const user = await c.var.auth.getUser();
return c.json({
userId: user.id,
email: user.email,
name: user.name,
});
});
export default router;The @agentuity/auth package provides authentication middleware that integrates with your routes and frontend auth hooks.
For complete setup including client-side configuration, see Adding Authentication.
Request Validation
Use agent.validator() for type-safe request validation:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import agent from './agent';
const router = new Hono<Env>();
// Validates using agent's input schema
router.post('/items', agent.validator(), async (c) => {
const data = c.req.valid('json'); // Fully typed from schema
await c.var.kv.set('items', crypto.randomUUID(), data);
return c.json({ created: true, item: data });
});
export default router;Custom Schema Override
Pass a schema to override the agent's default:
import { z } from 'zod';
const customSchema = z.object({
name: z.string().min(1).max(100),
price: z.number().positive(),
category: z.enum(['electronics', 'clothing', 'food']),
});
router.post('/custom',
agent.validator({ input: customSchema }),
async (c) => {
const data = c.req.valid('json');
return c.json(data);
}
);With Valibot
The validator works with any StandardSchema-compatible library:
import * as v from 'valibot';
const itemSchema = v.object({
name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
price: v.pipe(v.number(), v.minValue(0)),
category: v.picklist(['electronics', 'clothing', 'food']),
});
router.post('/items',
agent.validator({ input: itemSchema }),
async (c) => {
const data = c.req.valid('json');
return c.json(data);
}
);See Schema Libraries for more on Valibot and ArkType.
Route-Level Middleware
Apply middleware to all routes in a router with router.use():
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = new Hono<Env>();
// Apply to all routes in this file
router.use('/*', createMiddleware(async (c, next) => {
c.var.logger.info('API request', { path: c.req.path });
await next();
}));
// Apply to specific path prefix
router.use('/admin/*', adminAuthMiddleware);
router.get('/public', (c) => c.text('Anyone can access'));
router.get('/admin/users', (c) => c.json({ users: [] })); // Requires admin auth
export default router;Combining Middleware
Chain multiple middleware for complex requirements:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import agent from './agent';
const router = new Hono<Env>();
const rateLimiter = createMiddleware(async (c, next) => {
const ip = c.req.header('x-forwarded-for') || 'unknown';
const key = `ratelimit:${ip}`;
const result = await c.var.kv.get<number>('ratelimit', key);
const count = result.exists ? result.data : 0;
if (count >= 100) {
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.var.kv.set('ratelimit', key, count + 1, { ttl: 60 });
await next();
});
const authMiddleware = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')?.slice(7);
if (!token) {
return c.json({ error: 'Unauthorized' }, 401);
}
c.set('userId', await validateToken(token));
await next();
});
// Chain: rate limit → auth → validation → handler
router.post('/message',
rateLimiter,
authMiddleware,
agent.validator(),
async (c) => {
const userId = c.var.userId;
const { message } = c.req.valid('json');
return c.json({ received: true, userId, message });
}
);
export default router;Error Handling
Handle errors gracefully in middleware:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = new Hono<Env>();
const errorHandler = createMiddleware(async (c, next) => {
try {
await next();
} catch (error) {
c.var.logger.error('Unhandled error', {
error: error instanceof Error ? error.message : String(error),
path: c.req.path,
});
return c.json(
{ error: 'Internal server error' },
500
);
}
});
router.use('/*', errorHandler);
router.get('/risky', (c) => {
// If this throws, errorHandler catches it
throw new Error('Something went wrong');
});
export default router;Built-in Middleware
Agentuity provides middleware for common needs, exported from @agentuity/runtime.
CORS
Apply CORS headers to routes:
import { Hono } from 'hono';
import { type Env, createCorsMiddleware } from '@agentuity/runtime';
const router = new Hono<Env>();
// Apply CORS to all API routes
router.use('/api/*', createCorsMiddleware());
// Or with custom options
router.use('/api/*', createCorsMiddleware({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
credentials: true,
maxAge: 3600,
}));
export default router;CORS can also be configured globally via createApp(). See App Configuration.
Compression
Compress responses with gzip/deflate based on the Accept-Encoding header:
import { Hono } from 'hono';
import { type Env, createCompressionMiddleware } from '@agentuity/runtime';
const router = new Hono<Env>();
// Apply compression to all routes
router.use('/*', createCompressionMiddleware());
// Or with custom threshold (only compress responses > 2KB)
router.use('/*', createCompressionMiddleware({
threshold: 2048,
}));
export default router;Compression automatically bypasses:
- WebSocket upgrade requests
- Requests without
Accept-Encodingheaders - SSE and binary streams
Compression can also be configured globally via createApp({ compression: { ... } }). See App Configuration.
Best Practices
- Order matters: Middleware runs in registration order. Put error handling first, then auth, then validation.
- Early returns: Return immediately on failure (don't call
next()). - Share data via context: Use
c.set('key', value)to pass data to downstream handlers, access withc.var.key. - Keep middleware focused: Each middleware should do one thing.
Next Steps
- HTTP Routes: Complete routing reference
- Calling Agents from Routes: Import and invoke agents with validation