Routing Architecture
This document describes the routing implementation for both frontend and backend applications in the AgentPress monorepo.
Overview
AgentPress uses a dual routing architecture:
- Frontend: TanStack React Router with Vite
- Backend: Hono with modular route organization
Frontend Routing (TanStack React Router)
Applications
| App | Port | Purpose | Route File |
|---|---|---|---|
| Console | 3000 | Admin dashboard | apps/console/src/routes.tsx |
| Portal | 3002 | User-facing chat interface | apps/portal/src/routes.tsx |
| Drop-in Chat | 3003 | Embeddable iframe widget | apps/dropin-chat/src/routes.tsx |
Router Initialization
All frontend apps follow the same initialization pattern in main.tsx:
import { createRouter, RouterProvider } from "@tanstack/react-router";
import { routeTree } from "./routes";
const router = createRouter({ routeTree });
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
ReactDOM.createRoot(document.getElementById("root")!).render(
<RouterProvider router={router} />
);Root Route Pattern
Each application wraps routes in providers:
const rootRoute = createRootRoute({
component: () => (
<AgentPressProvider>
<Outlet />
<ReactQueryDevtools />
<Toaster />
</AgentPressProvider>
),
});
export const routeTree = rootRoute.addChildren(
createAppRoutes(rootRoute) as any,
);Configuration
Routes require configuration to be set before creating the route tree:
const config = {
apiHost: import.meta.env.VITE_API_HOST || "http://localhost:3001",
toolsUI,
voiceTranscription: true,
appType: EAppType.CONSOLE, // or PORTAL
};
setAgentPressConfig(config); // Must be called BEFORE creating routesConsole Route Structure
The console app uses dynamic route generation via createAppRoutes():
/ -> redirects to /dashboard/agents
Public Routes (PublicLayout - no sidebar):
/login
/invite?token=...
/reset-password?token=...
Protected Routes (ConsoleLayout - with sidebar):
/dashboard/
/agents
/agents/create
/agents/$id
/analytics
/conversations
/conversations/$threadId
/evaluations
/evaluations/$id
/evaluations/$id/edit
/feedback
/knowledge
/knowledge/$documentId
/mcps
/personas
/playground
/settings
/telemetry
/tools
/tools/create
/tools/$id
/users
/tasksPortal Route Structure
The portal app supports extensive customization via query parameters:
const chatSearchSchema = z.object({
agentId: z.string().optional().default(""),
showThreadList: z.boolean().optional().default(true),
showUsername: z.boolean().optional().default(true),
showAgentSelector: z.boolean().optional().default(true),
enableUpload: z.boolean().optional().default(true),
sidebarOpen: z.boolean().optional().default(true),
showNewThreadButton: z.boolean().optional().default(true),
showFeedback: z.boolean().optional().default(true),
showInputField: z.boolean().optional().default(true),
showUserHeaders: z.boolean().optional().default(false),
showTaskButton: z.boolean().optional().default(true),
initialUserMessage: z.string().optional().default(""),
authToken: z.string().optional().default(""),
providerName: z.string().optional().default(""),
threadId: z.string().optional().default(""),
});
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/",
validateSearch: chatSearchSchema,
component: ChatPage,
});Route Guards
Routes use before-load hooks for authentication:
// Public routes redirect authenticated users
beforeLoad: async ({ location }) => {
await redirectIfAuthenticated();
}
// Protected routes require authentication
beforeLoad: async ({ location }) => {
await requireAuth();
}Dynamic Routes
Dynamic route parameters use the $ prefix:
// Route: /agents/$id
const agentRoute = createRoute({
getParentRoute: () => dashboardRoute,
path: "agents/$id",
component: AgentDetailPage,
});
// Access parameter in component
function AgentDetailPage() {
const { id } = Route.useParams();
}Backend Routing (Hono)
Architecture
The backend uses Hono 4.x with a modular route organization pattern optimized for edge deployment.
Server Entry Point
File: apps/api/src/index.ts
export async function startServer({
agentsPath,
toolsPath,
externalApp,
seedAdminUser,
}) {
const app = createAgentPressApp({ externalApp });
// Initialize registries
await agentRegistry.initialize(agentsPath);
await mcpRegistry.initialize();
// Set up dependency injection
container.setDependencies({
toolRegistry,
agentRegistry,
pluginRegistry,
mcpRegistry,
taskRegistry,
});
// Start server
serve(app);
}Route Registration
File: packages/api/src/hono/app.ts
Routes are organized as modular Hono apps:
export const createAgentPressApp = () => {
const app = new Hono();
// Apply global middleware
app.use(logger());
app.use(cors());
// Mount routers with prefixes
app.route("/auth", authRoutes);
app.route("/users", usersRoutes);
app.route("/agents", agentsRoutes);
app.route("/chat", chatRoutes);
app.route("/threads", threadsRoutes);
app.route("/orgs", orgsRoutes);
return app;
};Route Factory Pattern
All routes export a Hono app:
// packages/api/src/hono/routes/agents.ts
export const agentsRoutes = new Hono<AppContext>();
agentsRoutes.use(requireAuth());
agentsRoutes.get("/", async (c) => {
const agents = await agentRegistry.getAllAgents();
return c.json(agents);
});
agentsRoutes.get("/:id", async (c) => {
const { id } = c.req.param();
const agent = await agentRegistry.getAgent(id);
return c.json(agent.getAgentPressConfig());
});Request Validation
Use Zod schemas with Honoās zValidator:
import { zValidator } from "@hono/zod-validator";
const CreateAgentSchema = z.object({
name: z.string().min(1),
description: z.string().optional(),
});
agentsRoutes.post(
"/",
zValidator("json", CreateAgentSchema),
async (c) => {
const data = c.req.valid("json");
// data is validated and typed
return c.json({ success: true });
}
);API Endpoints Reference
Authentication (/auth)
GET /auth/github- GitHub OAuth initiationGET /auth/github/callback- GitHub OAuth callbackGET /auth/google- Google OAuth initiationGET /auth/google/callback- Google OAuth callbackPOST /auth/login/credentials- Email/password loginPOST /auth/logout- User logout
Agents (/agents)
GET /agents- List all agentsGET /agents/:id- Get agent detailsPOST /agents- Create agentPUT /agents/:id- Update agentDELETE /agents/:id- Delete agent
Chat (/chat)
POST /chat- Send message and get streaming responseGET /chat/tools- Get available tools
Threads (/threads)
GET /threads- Get threads with paginationPOST /threads- Create new threadDELETE /threads/:threadId- Delete thread
Middleware
Authentication Middleware
// Require Clerk authentication
app.use(clerkAuth());
// Require org role
app.use(requireOrgRole(EOrgMemberRole.ADMIN, EOrgMemberRole.OWNER));
// Optional - extract if present
app.use(optionalAuth());Rate Limiting
import { rateLimiter } from "hono-rate-limiter";
// Global rate limit (15 min window, 100 requests)
app.use(
rateLimiter({
windowMs: 900000,
max: 100,
})
);Best Practices
- Hono Apps as Routers: Export Hono apps from route files for composition
- Middleware Composition: Chain middleware with
.use()for clarity - Zod Validation: Use
zValidatorfor all request data validation - Context Types: Define typed context for better IDE support
- Before-Load Hooks: Use for authentication guards in frontend routes