Kaneo Development Best Practices: A Complete Guide to Contributing to the Self-Hosted Project Management Platform
The best practices for developing with Kaneo include maintaining the pnpm monorepo structure, using the controller pattern for API logic, validating inputs with Valibot, consuming data via TanStack Query fetchers on the frontend, and enforcing code quality through Biome linting and Conventional Commits.
Kaneo is a self-hosted project management platform built as a modern TypeScript monorepo. Whether you're extending the API, building new UI features, or deploying to production, following the established conventions in the usekaneo/kaneo repository ensures your contributions remain maintainable and consistent with the codebase. This guide covers the architectural patterns, tooling, and workflows that govern development across the API, web frontend, and shared packages.
Monorepo Structure and Package Management
Kaneo organizes code into a pnpm workspace orchestrated by TurboRepo. Understanding this layout is essential before making any changes.
Workspace Organization
The repository root contains three primary directories:
apps/– Runnable services (API, web, documentation)packages/– Reusable libraries shared across appscharts/– Helm charts for Kubernetes deployment
This structure is documented in [CLAUDE.md](https://github.com/usekaneo/kaneo/blob/main/CLAUDE.md) and enforced by [pnpm-workspace.yaml](https://github.com/usekaneo/kaneo/blob/main/pnpm-workspace.yaml), which defines workspace boundaries.
Command Orchestration
Always run commands from the repository root so TurboRepo can parallelize builds and cache results:
pnpm dev # Launches API and web in watch mode
pnpm lint # Runs Biome linting with auto-fix
pnpm typecheck # Type-checks every package
pnpm build # Produces production bundles
API Development Patterns
The Kaneo API in apps/api follows a controller pattern that separates business logic from route handling, with strict input validation and automatic OpenAPI documentation.
Controller-Based Architecture
Place business logic in apps/api/src/<feature>/controllers/ and keep route files thin. For example, [apps/api/src/workspace/controllers/get-workspace-members.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/workspace/controllers/get-workspace-members.ts) demonstrates a clean database query pattern:
// Controllers contain pure business logic
const getWorkspaceMembers = async (workspaceId: string) => {
// Database interaction via Drizzle ORM
return db.query.workspaceMembers.findMany({
where: eq(workspaceMembers.workspaceId, workspaceId),
});
};
Route handlers apply validation, authentication, and OpenAPI metadata:
// apps/api/src/example/index.ts
import { Hono } from "hono";
import { describeRoute, validator } from "hono-openapi";
import * as v from "valibot";
import getExample from "./controllers/get-example";
const example = new Hono<{ Variables: { userId: string } }>()
.get(
"/:id",
describeRoute({
operationId: "getExample",
tags: ["Example"],
description: "Fetch an example resource by ID",
}),
validator("param", v.object({ id: v.string() })),
async (c) => {
const { id } = c.req.valid("param");
const result = await getExample(id);
return c.json(result);
},
);
export default example;
Validation and Documentation
- Valibot (preferred) or Zod for schema validation via the
validatorhelper fromhono-openapi - OpenAPI specs generated automatically through
describeRoutecalls; the generator lives in [apps/api/src/utils/openapi-spec.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/openapi-spec.ts)
Authentication and Database
Access the current user in request handlers via c.get("user"), provided by Better Auth as configured in [apps/api/src/auth.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts).
Database models use Drizzle ORM in [apps/api/src/database/schema.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts), with conventions including:
- CUID2 identifiers for all primary keys
createdAtandupdatedAttimestamps on every table
Frontend Development with React and TanStack Query
The Kaneo web app (apps/web) follows predictable patterns for data fetching, state management, and component structure.
Data Fetching Architecture
Encapsulate API calls in fetchers under apps/web/src/fetchers/, then consume them through custom hooks in apps/web/src/hooks/queries/:
// apps/web/src/fetchers/example/get-example.ts
import { apiUrl } from "@/lib/api-url";
export async function getExample(id: string) {
const res = await fetch(`${apiUrl}/example/${id}`, {
credentials: "include",
});
if (!res.ok) throw new Error("Failed to fetch example");
return res.json();
}
// apps/web/src/hooks/queries/example/useExample.ts
import { useQuery } from "@tanstack/react-query";
import { getExample } from "@/fetchers/example/get-example";
export function useExample(id: string) {
return useQuery(["example", id], () => getExample(id));
}
See [apps/web/src/fetchers/workspace/get-workspaces.ts](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/fetchers/workspace/get-workspaces.ts) for a production fetcher example.
State Management and UI
- Zustand for global UI state, following patterns in
apps/web/src/lib/ - Radix UI primitives with Tailwind CSS v4 for accessible components
- PascalCase naming convention; place components in
apps/web/src/components/
Internationalization Requirements
All user-visible strings must use react-i18next. Add keys to [i18n/en-US.json](https://github.com/usekaneo/kaneo/blob/main/i18n/en-US.json) first, then reference via the t() function:
// apps/web/src/components/TaskGreeting.tsx
import { useTranslation } from "react-i18next";
export function TaskGreeting({ name }: { name: string }) {
const { t } = useTranslation();
return <p>{t("tasks:greeting", { name })}</p>;
}
The i18n workflow is detailed in [CONTRIBUTING.md](https://github.com/usekaneo/kaneo/blob/main/CONTRIBUTING.md).
Environment Configuration
Kaneo uses a single .env file at the repository root for both API and web services. Required variables include:
| Variable | Purpose |
|---|---|
KANEO_CLIENT_URL |
Web app origin for redirects and CORS |
KANEO_API_URL |
Public API endpoint |
AUTH_SECRET |
Encryption key for Better Auth |
| PostgreSQL credentials | Database connection |
Complete documentation appears in [ENVIRONMENT_SETUP.md](https://github.com/usekaneo/kaneo/blob/main/ENVIRONMENT_SETUP.md).
CORS and Scaling
- CORS: Leave
CORS_ORIGINSempty for local development; set to the exact web origin in production (read fromapps/api/src/config/index.ts) - Redis: Optional for horizontal scaling; configure single mode, Sentinel, or Cluster via
REDIS_URL. WebSocket broadcast adapters live inapps/api/src/ws/—compare [in-memory-broadcast-adapter.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/in-memory-broadcast-adapter.ts) and [redis-broadcast-adapter.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/redis-broadcast-adapter.ts)
Code Quality and Testing Standards
Linting and Formatting with Biome
Biome is the sole tool for formatting and linting. Key rules enforced:
- Double quotes everywhere
- Semicolons required
- Import ordering: external → internal (
@/alias) → relative
Run pnpm lint before committing; a pre-commit hook enforces this automatically.
Conventional Commits
Follow the Conventional Commits specification (feat:, fix:, docs:, etc.). The commit-msg hook in .husky/commit-msg validates format.
Testing Strategy
| Test Type | Location | Command |
|---|---|---|
| Unit tests | Alongside source (*.test.ts) |
pnpm test |
| Integration tests | tests/api-integration/ |
pnpm test:integration |
Each app maintains its own Vitest configuration: apps/api/vitest.config.ts and apps/web/vitest.config.ts. Integration tests use real PostgreSQL instances; see [tests/api-integration/project.test.ts](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/project.test.ts) for an example.
Key Files and References
Summary
- Preserve monorepo structure: Use pnpm and TurboRepo commands from the repository root
- Separate concerns: Controllers for business logic, thin Hono routes for validation and auth, fetchers with TanStack Query on the frontend
- Validate rigorously: Valibot schemas on all inputs, OpenAPI documentation on all routes
- Share code appropriately: Place reusable utilities in
packages/libs - Configure environments correctly: Single
.envfile, proper CORS origins, optional Redis for scaling - Maintain quality standards: Biome formatting, Conventional Commits, comprehensive test coverage
Frequently Asked Questions
What package manager does Kaneo require?
Kaneo requires pnpm. The pnpm-workspace.yaml file defines workspace boundaries, and all development commands (pnpm dev, pnpm lint, pnpm build) must be run from the repository root so TurboRepo can orchestrate the monorepo correctly.
How should I structure a new API endpoint in Kaneo?
Create a controller file in apps/api/src/<feature>/controllers/ containing your database logic, then wire it into a thin Hono route in apps/api/src/<feature>/index.ts. Apply Valibot validation via the validator helper and document the route with describeRoute for automatic OpenAPI generation.
What is the recommended pattern for fetching data in the Kaneo frontend?
Encapsulate API calls in fetcher functions under apps/web/src/fetchers/, then consume them through custom hooks in apps/web/src/hooks/queries/ using TanStack Query. This separation keeps data fetching logic reusable and caching behavior explicit.
How do I add translations to the Kaneo interface?
Add new keys to [i18n/en-US.json](https://github.com/usekaneo/kaneo/blob/main/i18n/en-US.json) following the nested namespace structure, then reference them via t("namespace:key") from the useTranslation hook. All user-visible strings must go through react-i18next—hardcoded text is not permitted.
What testing is required before submitting a Kaneo contribution?
Run pnpm test for unit tests and pnpm test:integration for integration tests. Unit tests live alongside source files, while integration tests requiring PostgreSQL reside in tests/api-integration/. The pre-commit hook also ensures Biome linting passes and commit messages follow Conventional Commits.
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 →