Build/Routes

Middleware & Authentication

Add authentication, validation, and request processing to your routes

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.

Works Everywhere

Middleware works on all route types in src/api/. The patterns below apply to HTTP, WebSocket, SSE, and email routes.

Middleware Compatibility

Middleware is supported on: HTTP routes, WebSocket, SSE, Email, and Stream routes. SMS and Cron routes do not support middleware.

Basic Middleware

Create middleware with createMiddleware from Hono:

import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
 
const router = createRouter();
 
// 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 { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
 
const router = createRouter();
 
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 { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import { verify } from 'hono/jwt';
 
const router = createRouter();
 
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;

Third-Party Auth Providers

Services like Clerk, Auth0, and Supabase Auth provide drop-in authentication. Here's an example with Clerk:

import { createRouter } from '@agentuity/runtime';
import { clerkMiddleware, getAuth } from '@hono/clerk-auth';
 
const router = createRouter();
 
// Apply Clerk middleware
router.use('/*', clerkMiddleware());
 
router.get('/public', (c) => {
  return c.json({ message: 'Public endpoint' });
});
 
router.get('/protected', (c) => {
  const auth = getAuth(c);
 
  if (!auth?.userId) {
    return c.json({ error: 'Unauthorized' }, 401);
  }
 
  return c.json({ userId: auth.userId });
});
 
export default router;

Using Other Providers

For other auth providers, check if they have Hono middleware available, or use JWT validation with their tokens.

Request Validation

Use agent.validator() for type-safe request validation:

import { createRouter } from '@agentuity/runtime';
import agent from './agent';
 
const router = createRouter();
 
// 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 { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
 
const router = createRouter();
 
// 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 { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import agent from './agent';
 
const router = createRouter();
 
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 { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
 
const router = createRouter();
 
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;

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 with c.var.key.
  • Keep middleware focused: Each middleware should do one thing.

Next Steps

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!