How API Routes Are Organized in Kaneo: A Modular Hono Architecture

Kaneo organizes its HTTP API into feature-based modules using Hono and @hono/zod-openapi, where each domain (workspace, task, project) maintains its own router in apps/api/src/ and registers routes via createRoute with automatic OpenAPI documentation, then mounts them in apps/api/src/index.ts for a flat URL hierarchy.

Kaneo is an open-source project management platform built with modern TypeScript tooling. Understanding how API routes are organized in Kaneo reveals a clean, modular architecture that leverages the Hono web framework combined with Zod-based OpenAPI validation. This design separates concerns by feature while maintaining type safety and automatic API documentation generation.

Project Structure and Routing Philosophy

Kaneo’s API follows a feature-based directory structure where each logical domain lives in its own subdirectory under apps/api/src/. The architecture rests on three core principles:

  • Declarative route definitions using the createRoute helper from apps/api/src/openapi.ts, which bundles HTTP methods, paths, OpenAPI metadata, request validation, and response schemas
  • Feature isolation with each module (workspace, task, label, project, auth) exporting its own router created via apiRouter<BaseVariables>()
  • Flat URL hierarchy achieved by mounting feature routers at specific prefixes in the main entry point, resulting in endpoints like /api/workspace/{workspaceId}/members and /api/task/{id}

The createRoute Abstraction

All route definitions in Kaneo rely on createRoute, a thin wrapper defined in apps/api/src/openapi.ts that standardizes endpoint configuration. This utility accepts an object specifying the HTTP method, operation ID, tags, summary, description, optional middleware, request validation, and response schemas.

// Conceptual schema from apps/api/src/openapi.ts
createRoute({
  method: "get",
  path: "/{workspaceId}/ping",
  operationId: "pingWorkspace",
  tags: ["Workspaces"],
  summary: "Health-check for a workspace",
  middleware: [workspaceAccess.fromParam("workspaceId")],
  responses: {
    200: jsonResponse("Workspace is reachable", z.object({ ok: z.boolean() })),
    400: errorResponse("Workspace ID could not be determined"),
  },
});

The createRoute function returns a route definition that is then attached to a feature router using .openapi(route, handler).

Feature-Based Router Organization

Each feature module exports a router created with apiRouter<BaseVariables>() or apiRouter<BaseVariables & { workspaceId: string }>() when workspace validation is required. These routers chain .openapi() calls to register multiple endpoints.

Workspace Router Implementation

The workspace feature demonstrates how to implement protected routes with workspace validation. Located at apps/api/src/workspace/index.ts, this router uses workspaceAccess middleware to resolve workspace IDs and check permissions before handlers execute.

// apps/api/src/workspace/index.ts
import { apiRouter, createRoute, jsonResponse, errorResponse } from "../openapi";
import { workspaceAccess } from "../utils/workspace-access-middleware";

const pingRoute = createRoute({
  method: "get",
  operationId: "pingWorkspace",
  path: "/{workspaceId}/ping",
  tags: ["Workspaces"],
  summary: "Health-check for a workspace",
  description: "Returns a simple OK response for the supplied workspace.",
  middleware: [workspaceAccess.fromParam("workspaceId")] as const,
  responses: {
    200: jsonResponse("Workspace is reachable", z.object({ ok: z.boolean() })),
    400: errorResponse("Workspace ID could not be determined"),
    403: errorResponse("No access to the workspace"),
  },
});

const workspace = apiRouter<BaseVariables & { workspaceId: string }>()
  .openapi(pingRoute, async (c) => {
    // The middleware already ensures the caller can see the workspace.
    return c.json({ ok: true }, 200);
  });

export default workspace;

Task Router Pattern

For larger feature sets, examine apps/api/src/task/index.ts. This file illustrates a complete CRUD implementation with multiple routes, validation schemas, and middleware chains. The task router uses apiRouter<BaseVariables & { workspaceId: string }>() to ensure all endpoints within the task domain validate workspace access consistently.

Middleware and Cross-Cutting Concerns

Kaneo extracts reusable authentication and authorization logic into dedicated middleware utilities:

Mounting Routes in the Main Application

The root API server in apps/api/src/index.ts assembles all feature routers and applies global middleware. It imports each feature router and mounts it on a path prefix using api.route("<prefix>", router).

// apps/api/src/index.ts (excerpt)
import workspace from "./workspace";
import task from "./task";
import project from "./project";
import label from "./label";
import auth from "./auth";

// Global middleware applied to all routes
api.use("*", authenticateApiRequest);

// Mount feature routers
api.route("/workspace", workspace);
api.route("/task", task);
api.route("/project", project);
api.route("/label", label);
api.route("/auth", auth);

This mounting strategy produces the flat URL hierarchy observed in the API:

  • /api/workspace/{workspaceId}/members
  • /api/task/{id}
  • /api/project/{projectId}
  • /api/auth/get-session

Adding a New Feature Router

To extend the API with a new domain, create a subdirectory under apps/api/src/ following the established pattern:

  1. Create the directory and index file:
// apps/api/src/example/index.ts
import { apiRouter, createRoute, jsonResponse } from "../openapi";
import { z } from "zod";

const helloRoute = createRoute({
  method: "get",
  operationId: "sayHello",
  path: "/hello",
  tags: ["Example"],
  summary: "Demo endpoint",
  description: "Returns a friendly greeting.",
  responses: { 
    200: jsonResponse("Greeting", z.object({ message: z.string() })) 
  },
});

const example = apiRouter()
  .openapi(helloRoute, (c) => c.json({ message: "Hello from Kaneo!" }, 200));

export default example;
  1. Mount the router in the main entry point:
// apps/api/src/index.ts
import example from "./example";

api.route("/example", example);

The new endpoint is immediately available at GET /api/example/hello with full OpenAPI documentation generated automatically.

Summary

  • Kaneo uses Hono with @hono/zod-openapi to build type-safe, self-documenting API routes in apps/api/src/
  • Feature-based organization places each domain (workspace, task, auth) in its own subdirectory with its own router instance
  • Declarative route definitions use createRoute from apps/api/src/openapi.ts to bundle metadata, validation, and handlers
  • Flat URL structure is achieved by mounting routers at specific prefixes in apps/api/src/index.ts
  • Middleware composition handles cross-cutting concerns like authentication (authenticateApiRequest) and workspace validation (workspaceAccess) separately from business logic

Frequently Asked Questions

What framework does Kaneo use for its API routing?

Kaneo uses Hono, a lightweight, edge-ready web framework for TypeScript, extended with @hono/zod-openapi to provide automatic OpenAPI documentation and runtime validation. According to the Kaneo source code, the apiRouter function wraps Hono's router with OpenAPI capabilities.

How does Kaneo handle authentication across API routes?

Authentication is implemented via the authenticateApiRequest middleware defined in apps/api/src/utils/authenticate-api-request.ts. This middleware validates sessions or API keys and is applied globally to all routes in apps/api/src/index.ts using api.use("*", authenticateApiRequest), ensuring every endpoint requires valid credentials unless explicitly excluded.

Where are the OpenAPI schemas defined in Kaneo?

OpenAPI schemas are defined inline within each route using the createRoute helper exported from apps/api/src/openapi.ts. This function accepts Zod schemas for request parameters and bodies, along with response definitions via helpers like jsonResponse and errorResponse, generating the complete OpenAPI specification automatically.

How do I add a workspace-scoped endpoint to an existing feature in Kaneo?

Create a new route constant using createRoute with workspaceAccess middleware (e.g., workspaceAccess.fromParam("workspaceId")), then attach it to the feature router using .openapi(route, handler). Ensure the router uses the correct type parameter: apiRouter<BaseVariables & { workspaceId: string }>() to maintain type safety for the workspace context.

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 →