Kaneo Project Structure Explained: A Deep Dive into the Monorepo Architecture

Kaneo is organized as a pnpm monorepo managed by TurboRepo, dividing code into three main directories—apps/ for runnable applications, packages/ for shared libraries, and charts/ for Kubernetes deployment assets.

The Kaneo repository follows a clean architectural separation between backend services and frontend interfaces while maintaining strict type safety across the entire stack. This open-source project demonstrates modern TypeScript monorepo patterns by isolating concerns into distinct workspaces that share common tooling and configuration.

Monorepo Layout and Directory Structure

Kaneo adopts a standard Turborepo-style organization where functionality is split between applications, shared packages, and infrastructure definitions.

Apps Directory (apps/)

The apps/ folder contains three runnable applications that form the complete system:

  • apps/api/ – Backend service built with Hono, PostgreSQL (via Drizzle ORM), and Better Auth. Feature routes live under apps/api/src/{feature}/, with each module exposing thin Hono handlers that delegate to controllers.
  • apps/web/ – Front-end Single Page Application using React 19+, Vite, TanStack Router, and TanStack Query. UI components reside in apps/web/src/components/ and follow a file-based routing convention under apps/web/src/routes/.
  • apps/docs/ – Documentation site powered by Next.js.

Packages Directory (packages/)

Shared libraries used across applications live here:

  • packages/email/ – Helper functions for sending emails.
  • packages/libs/ – Reusable code including type definitions and utility functions exported from packages/libs/src/index.ts.
  • packages/typescript-config/ – Centralized tsconfig files ensuring consistent TypeScript settings across the monorepo.

Charts Directory (charts/)

The charts/kaneo/ path contains Helm charts for deploying Kaneo on Kubernetes, with Chart.yaml defining the deployment metadata.

Backend Architecture and API Structure

The Kaneo API follows a strict feature-based organization pattern that promotes modularity and clear separation of concerns.

Feature-Based Route Organization

Routes are grouped by domain (e.g., apps/api/src/label/), each containing a Hono router that validates inputs using Valibot schemas and documents endpoints via hono-openapi decorators. Controllers handle business logic in separate controllers/ subdirectories.

Here is the skeleton for a feature module:

// apps/api/src/label/index.ts
import { Hono } from "hono";
import { describeRoute, validator } from "hono-openapi";
import * as v from "valibot";
import getLabel from "./controllers/get-label";

const label = new Hono<{ Variables: { userId: string } }>()
  .get("/:id",
    describeRoute({
      operationId: "getLabel",
      tags: ["Label"],
      description: "Fetch a label by its ID",
    }),
    validator("param", v.object({ id: v.string() })),
    async (c) => {
      const { id } = c.req.valid("param");
      const label = await getLabel(id);
      return c.json(label);
    }
  );
export default label;

The main entry point at apps/api/src/index.ts wires together Hono, CORS, authentication, and registers all feature routers.

Database Schema with Drizzle ORM

Database definitions are centralized in apps/api/src/database/schema.ts using Drizzle ORM. Primary keys use CUID2 for collision-resistant identifiers, and every table includes createdAt and updatedAt timestamps. Relations are declared separately in apps/api/src/database/relations.ts.

Example table definition:

// apps/api/src/database/schema.ts
export const exampleTable = pgTable("example", {
  id: text("id").$defaultFn(() => createId()).primaryKey(),
  projectId: text("project_id")
    .notNull()
    .references(() => projectTable.id, { onDelete: "cascade", onUpdate: "cascade" }),
  title: text("title").notNull(),
  createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
  updatedAt: timestamp("updated_at", { mode: "date" })
    .defaultNow()
    .$onUpdate(() => new Date())
    .notNull(),
}, (t) => [index("example_projectId_idx").on(t.projectId)]);

Frontend Architecture and React Patterns

The web application uses modern React patterns with strict TypeScript integration across the monorepo boundaries.

File-Based Routing and Data Fetching

Routing lives under apps/web/src/routes/ and is handled by TanStack Router. Data fetching is abstracted into feature-specific fetchers located in apps/web/src/fetchers/{feature}/, while caching and server-state management use TanStack Query.

Example fetcher and corresponding hook:

// apps/web/src/fetchers/label/get-label.ts
import { apiClient } from "@/lib/api-client";

export async function getLabel(labelId: string) {
  const resp = await apiClient.get(`/label/${labelId}`);
  return resp.json();
}

// apps/web/src/hooks/queries/label/use-label.ts
import { useQuery } from "@tanstack/react-query";
import { getLabel } from "@/fetchers/label/get-label";

export function useLabel(labelId: string) {
  return useQuery(["label", labelId], () => getLabel(labelId));
}

State Management and UI Components

Client-side state management uses Zustand, while UI components are built on Radix UI primitives and styled with Tailwind CSS v4. This combination provides accessible, unstyled components with flexible styling capabilities.

Shared Packages and Configuration

The packages/libs/ workspace exports shared utilities and TypeScript types used by both apps/api and apps/web, ensuring type safety across the API boundary. The packages/typescript-config/ workspace provides standardized tsconfig.json files that enforce strict compiler settings across all applications.

Summary

  • Kaneo uses a pnpm monorepo managed by TurboRepo with clear separation between apps/, packages/, and charts/.
  • The backend (apps/api/) organizes code by feature, using Hono for HTTP handling, Valibot for validation, and Drizzle ORM for database operations with CUID2 primary keys.
  • The frontend (apps/web/) uses React 19 with TanStack Router for file-based routing, TanStack Query for server state, and Zustand for client state.
  • Shared packages provide centralized TypeScript configurations and reusable utilities accessible across all applications.
  • Helm charts in charts/kaneo/ provide Kubernetes deployment specifications.

Frequently Asked Questions

What build tool manages the Kaneo monorepo?

Kaneo uses Turborepo to orchestrate builds, tests, and development tasks across the pnpm workspace. This enables efficient caching and parallel execution of tasks defined in the turbo.json configuration, ensuring that changes in shared packages trigger rebuilds in dependent applications.

How does Kaneo handle database migrations and schema changes?

Schema definitions live in apps/api/src/database/schema.ts using Drizzle ORM. The project uses Drizzle Kit to generate and apply migrations based on changes to the TypeScript schema definitions. Relations are maintained separately in apps/api/src/database/relations.ts to keep the schema file focused on table structures.

Where are authentication and authorization implemented in the Kaneo project structure?

Authentication logic resides in apps/api/src/index.ts, where Better Auth is integrated with the Hono application. Middleware applied at the router level injects userId into the context variables (as seen in Hono<{ Variables: { userId: string } }>), making user identification available to all downstream feature controllers without repeating auth logic in each route file.

How do I run the complete Kaneo stack locally?

Use pnpm to install dependencies and start both applications simultaneously:


# Install all dependencies across the monorepo

pnpm install

# Start API and Web in development mode

pnpm dev

This command leverages Turborepo's pipeline to run the API server and Vite dev server in parallel, with hot reloading enabled for both backend and frontend code.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →