# Key Architectural Patterns Used in Kaneo: A Deep Dive into the Codebase

> Explore Kaneo's feature-based modular architecture, TanStack Query for state, and event-driven system. Understand its codebase's key architectural patterns.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: architecture
- Published: 2026-08-08

---

**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`](https://github.com/usekaneo/kaneo/blob/main/index.ts) file that registers controllers and middleware. For example, task-related endpoints live in [`apps/api/src/task/index.ts`](https://github.com/usekaneo/kaneo/blob/main/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.

```typescript
// 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.

```typescript
// 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`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts) and [`apps/api/src/database/relations.ts`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts).
- **Event-driven architecture** uses `publishEvent()` from [`apps/api/src/events/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/events/index.ts) to 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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/relations.ts) while schemas live in [`apps/api/src/database/schema.ts`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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.