Key Architectural Patterns Used in Kaneo: A Deep Dive into the Codebase
Kaneo implements a feature-based modular architecture with strict separation between route definitions and business logic, TanStack Query for frontend state management, and an event-driven system for side effects.
The Kaneo codebase (usekaneo/kaneo) demonstrates production-grade architectural decisions that prioritize type safety, separation of concerns, and maintainability. Understanding the key architectural patterns used in Kaneo reveals how the platform handles complex domain logic while keeping the codebase extensible for future features.
Modular Backend API Architecture
Kaneo's backend follows a domain-driven structure where routes are grouped by feature under apps/api/src/<feature>/. This organization keeps related endpoints, controllers, and validation logic co-located, making the codebase navigable and reducing cross-module dependencies.
Feature-Based Route Organization
Each feature module exposes its routes through an index.ts file that registers controllers and middleware. For example, task-related endpoints live in apps/api/src/task/index.ts, while authentication logic resides in apps/api/src/auth/. This pattern ensures that adding new capabilities requires creating a new directory rather than modifying existing files.
Controller Layer and Validation
Business logic is strictly separated from route handlers through a dedicated controller layer located in <feature>/controllers/. Routes use OpenAPI decorators (describeRoute) for automatic documentation and Valibot schemas for runtime validation. According to the Kaneo source code, this combination provides end-to-end type safety from API specification to database query.
// apps/api/src/task/index.ts
import { Hono } from "hono";
import { describeRoute, validator } from "hono-openapi";
import * as v from "valibot";
import getItem from "./controllers/get-item";
const task = new Hono<{ Variables: { userId: string } }>()
.get("/:id",
describeRoute({
operationId: "getItem",
tags: ["Task"],
description: "Get task by ID"
}),
validator("param", v.object({ id: v.string() })),
async (c) => {
const { id } = c.req.valid("param");
const item = await getItem(id);
return c.json(item);
}
);
Modern Frontend Data Flow
The web application in apps/web/ implements a layered data architecture that separates UI components from API communication through dedicated abstraction layers.
File-Based Routing and Fetchers
The frontend uses file-based routing under apps/web/src/routes/, where the directory structure mirrors the URL hierarchy. Data fetching is abstracted into fetchers located in apps/web/src/fetchers/<feature>/, which handle HTTP client configuration, error parsing, and response typing. This pattern prevents API implementation details from leaking into React components.
TanStack Query Integration
Server state management relies on TanStack Query hooks organized into apps/web/src/hooks/queries/ and apps/web/src/hooks/mutations/. These hooks consume the fetcher layer, providing caching, background refetching, and optimistic updates without cluttering component code.
// apps/web/src/hooks/queries/task/use-item.ts
import { useQuery } from "@tanstack/react-query";
import { getItem } from "@/fetchers/task/get-item";
export function useItem(itemId: string) {
return useQuery({
queryKey: ["item", itemId],
queryFn: () => getItem(itemId),
});
}
Database Schema Conventions
Kaneo enforces strict conventions in apps/api/src/database/schema.ts and apps/api/src/database/relations.ts to ensure consistency across all tables.
CUID2 Identifiers and Timestamp Management
All tables use CUID2 identifiers generated via createId() rather than sequential integers or standard UUIDs, providing globally unique keys that are sortable and URL-safe. Every schema includes createdAt and updatedAt timestamps with database-level defaults, eliminating the need for application-layer timestamp management.
Indexing and Cascade Rules
Foreign key relationships explicitly declare cascade rules for referential integrity, and indexes are strategically added to frequently queried columns. This design, as implemented in usekaneo/kaneo, optimizes query performance while maintaining data consistency during deletions.
Authentication and Session Management
Authentication is handled by Better Auth, integrated into the Hono middleware context. The context exposes c.get("userId"), c.get("user"), and c.get("session") to downstream handlers, providing type-safe access to session data. API authentication supports Bearer tokens for programmatic access, while the frontend interacts with the auth client through @/lib/auth-client.
Event-Driven Side Effects
Domain events decouple primary operations from secondary concerns like notifications and activity tracking. The publishEvent() utility, located in apps/api/src/events/index.ts, allows controllers to emit events without knowing which handlers will process them. This pattern keeps business logic focused on core domain operations while enabling extensible side-effect processing.
Summary
- Feature-based modularity organizes backend code into domain-specific directories under
apps/api/src/<feature>/, with controllers handling business logic separately from route definitions. - Type-safe API contracts use OpenAPI decorators and Valibot validation to ensure runtime and compile-time consistency.
- Layered frontend architecture separates concerns through fetchers (
apps/web/src/fetchers/) and TanStack Query hooks (apps/web/src/hooks/queries/). - Strict database conventions enforce CUID2 primary keys, automatic timestamps, and explicit cascade rules in
apps/api/src/database/schema.ts. - Event-driven architecture uses
publishEvent()fromapps/api/src/events/index.tsto handle side effects asynchronously.
Frequently Asked Questions
What framework does Kaneo use for its backend API?
Kaneo uses Hono as its web framework, paired with hono-openapi for documentation and Valibot for schema validation. This stack provides high-performance request handling with automatic OpenAPI specification generation based on the describeRoute decorators and validators defined in route files like apps/api/src/task/index.ts.
How does Kaneo handle database relations and cascading?
All database relations are defined in apps/api/src/database/relations.ts while schemas live in apps/api/src/database/schema.ts. Foreign keys explicitly declare cascade rules (such as onDelete: 'cascade') to maintain referential integrity, and frequently queried columns receive dedicated indexes to optimize read performance.
Why does Kaneo use CUID2 instead of UUIDs or auto-incrementing IDs?
Kaneo uses CUID2 via the createId() function because these identifiers are globally unique, sortable by creation time, and URL-safe without being guessable. Unlike auto-incrementing integers, they prevent enumeration attacks; unlike standard UUIDs, they provide better database locality and sorting characteristics.
Where is business logic separated from route handlers in Kaneo?
Business logic resides in controller files located under apps/api/src/<feature>/controllers/. Route handlers in apps/api/src/<feature>/index.ts (such as apps/api/src/task/index.ts) are responsible only for HTTP-specific concerns like parsing requests, calling controllers, and formatting responses. This separation ensures that domain logic remains framework-agnostic and easily testable.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →