Kaneo Backend Technology Stack: Node.js, TypeScript, Hono, and Drizzle ORM Explained

Kaneo's backend runs on Node.js 20+ with TypeScript, using the Hono web framework, Drizzle ORM with PostgreSQL, and optional Redis for real-time WebSocket broadcasting.

Kaneo is an open-source project management platform built as a modern, self-hostable monorepo. This article breaks down the core technology stack for Kaneo backend based on the actual source code in the usekaneo/kaneo repository, examining how each layer works together to power the API.

Runtime and Language Foundation

Node.js 20 as ES Modules

The Kaneo backend executes as an ES module on Node.js 20 or later. This modern runtime provides the JavaScript engine, standard library, and native support for import syntax without transpilation overhead in production.

In apps/api/package.json, the configuration explicitly declares the module type:

{
  "type": "module",
  "engines": { "node": ">=20" }
}

TypeScript with esbuild

TypeScript provides static typing across the entire backend. Rather than the traditional tsc compiler, Kaneo uses esbuild for fast bundling. This choice minimizes build times while preserving type safety through editor integration and CI checks.

Web Framework: Hono

Why Hono?

The Hono framework (hono, @hono/node-server, @hono/node-ws) serves as the HTTP and WebSocket layer. Hono offers:

  • Minimal overhead — lighter than Express or Fastify
  • First-class TypeScript — end-to-end type inference
  • Middleware ecosystem — built-in CORS, compression, and caching
  • WebSocket support — via @hono/node-ws for real-time features

Server Bootstrap in index.ts

The main application entry point in apps/api/src/index.ts demonstrates the complete Hono setup:

import { OpenAPIHono } from "@hono/zod-openapi";
import { serve } from "@hono/node-server";
import { createNodeWebSocket } from "@hono/node-ws";

const app = new OpenAPIHono();

// Middleware stack
app.use(compress());
app.use(cors());
app.use(secureHeaders());

// WebSocket upgrade handling
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });

serve(app, (info) => {
  console.log(`Server running on ${info.address}:${info.port}`);
});

This single file configures routing, OpenAPI documentation, compression, CORS headers, and the WebSocket upgrade path.

API Design: OpenAPI and Zod Validation

@hono/zod-openapi Integration

Kaneo generates OpenAPI 3.x specifications automatically from route definitions using @hono/zod-openapi. Each endpoint declares its request/response schemas with Zod, enabling:

  • Runtime validation of request bodies, query params, and path segments
  • Auto-generated API documentation
  • Type inference from schemas to handler contexts

Example route with full schema definition:

import { OpenAPIHono, z } from "@hono/zod-openapi";

const api = new OpenAPIHono();

api.get(
  "/projects/:projectId",
  {
    request: {
      params: z.object({ projectId: z.string().uuid() }),
    },
    responses: {
      200: {
        description: "Project details",
        content: {
          "application/json": {
            schema: z.object({
              id: z.string().uuid(),
              name: z.string(),
              createdAt: z.string().datetime(),
            }),
          },
        },
      },
      404: { description: "Project not found" },
    },
  },
  async (c) => {
    const { projectId } = c.req.valid("param");
    const project = await getProject(projectId);
    return project ? c.json(project) : c.notFound();
  }
);

The c.req.valid("param") call provides typed, validated parameters—no manual parsing required.

Authentication Layer

better-auth for Session and API Key Management

better-auth (better-auth, @better-auth/api-key) handles all authentication concerns:

  • Session management with secure cookies
  • API key authentication for programmatic access
  • Password hashing via bcryptjs
  • OAuth provider integrations (GitHub, Gitea, Discord, Slack, Telegram)

The modular design allows Kaneo to enable only the authentication methods needed per deployment.

Database: PostgreSQL with Drizzle ORM

Drizzle as the Query Builder

Drizzle ORM provides a strongly-typed SQL-like query builder that compiles to efficient PostgreSQL queries. Unlike heavyweight ORMs, Drizzle stays close to SQL semantics while offering complete type safety.

Connection and Schema Setup

In apps/api/src/database/index.ts, the database layer initializes:

import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
});

export const db = drizzle(pool, { schema });

// Lazy-loaded proxy for hot-reload compatibility
export const getDb = () => db;

Query Example with Type Inference

import { eq, and } from "drizzle-orm";
import { taskTable, projectTable } from "./database/schema";

export async function getTasksByProject(projectId: string, status?: string) {
  const conditions = [eq(taskTable.projectId, projectId)];
  
  if (status) {
    conditions.push(eq(taskTable.status, status));
  }

  return await db
    .select()
    .from(taskTable)
    .where(and(...conditions))
    .orderBy(taskTable.createdAt);
}

The drizzle-orm import provides SQL operators (eq, and, or, inArray, etc.) that map directly to PostgreSQL operators.

Migrations with drizzle-kit

Database schema changes are managed via drizzle-kit, which generates and runs migration files. The schema definitions in apps/api/src/database/schema.ts serve as the single source of truth.

Real-Time Layer: WebSockets with Optional Redis

Broadcast Architecture

Kaneo supports real-time updates through WebSockets, with a pluggable broadcast system for scaling:

  • In-memory adapter — for single-instance deployments
  • Redis adapter — for horizontal scaling across multiple API instances

In-Memory Broadcast Adapter

apps/api/src/ws/in-memory-broadcast-adapter.ts maintains a local Map of connected clients:

const clients = new Map<string, WebSocket>();

export function broadcast(event: string, payload: unknown) {
  const message = JSON.stringify({ event, payload });
  for (const ws of clients.values()) {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(message);
    }
  }
}

Redis Broadcast Adapter

apps/api/src/ws/redis-broadcast-adapter.ts uses ioredis to subscribe to a Redis channel and forward messages to local WebSocket clients:

import Redis from "ioredis";

const pub = new Redis(process.env.REDIS_URL);
const sub = new Redis(process.env.REDIS_URL);

sub.subscribe("kaneo:events");
sub.on("message", (channel, message) => {
  const { event, payload } = JSON.parse(message);
  // Forward to all local WebSocket connections
  localBroadcast(event, payload);
});

export function broadcast(event: string, payload: unknown) {
  pub.publish("kaneo:events", JSON.stringify({ event, payload }));
}

This design lets operators start with a simple deployment and add Redis only when scaling demands it.

Observability and External Services

Sentry for Error Tracking

Production deployments use Sentry (@sentry/node, @sentry/profiling-node) to capture unhandled exceptions and performance profiles. Integration is initialized early in the application bootstrap.

AWS S3 for File Storage

Task attachments and other assets upload directly to S3-compatible storage using the AWS SDK (@aws-sdk/client-s3, @aws-sdk/s3-request-presigner). The backend generates presigned URLs rather than proxying file content.

Additional Integrations

The apps/api/package.json includes specialized libraries for specific features:

Library Purpose
Octokit GitHub and Gitea API integration
@modelcontextprotocol/sdk Model Context Protocol support
croner Scheduled job execution
creem Payment processing
@oslojs/crypto Additional cryptographic utilities

Testing with Vitest

The backend test suite runs on Vitest, covering:

  • Unit tests for utility functions and schema validation
  • Integration tests for API routes with in-memory PostgreSQL
  • WebSocket broadcast logic verification

Tests co-locate with source files or reside in __tests__ directories, following the project's convention.

File-by-File Stack Reference

File Responsibility
apps/api/package.json Dependency declarations and engine requirements
apps/api/src/index.ts Hono server bootstrap, middleware chain, WebSocket setup
apps/api/src/database/index.ts PostgreSQL pool, Drizzle instance, lazy DB proxy
apps/api/src/database/schema.ts Table definitions with Drizzle ORM
apps/api/src/ws/in-memory-broadcast-adapter.ts Single-instance WebSocket broadcasting
apps/api/src/ws/redis-broadcast-adapter.ts Distributed WebSocket broadcasting via Redis

Summary

  • Kaneo's backend technology stack centers on Node.js 20, TypeScript, and Hono for a lightweight, type-safe HTTP API
  • Drizzle ORM with PostgreSQL provides strongly-typed database access without ORM overhead
  • @hono/zod-openapi generates OpenAPI specs and validates requests at runtime
  • better-auth handles sessions, API keys, and OAuth in a modular package
  • WebSocket real-time updates scale from in-memory (single instance) to Redis-backed (multi-instance)
  • Sentry, AWS S3 SDK, and targeted libraries complete the production-ready architecture

This stack prioritizes developer experience (TypeScript everywhere, fast builds), operational simplicity (single Docker image possible), and scalability (Redis, connection pooling, optional service separation).

Frequently Asked Questions

What web framework does Kaneo use for its backend API?

Kaneo uses Hono, a lightweight TypeScript-first web framework. Hono handles routing, middleware (CORS, compression), OpenAPI documentation, and WebSocket upgrades through the @hono/node-server and @hono/node-ws packages. The framework choice keeps bundle sizes small while maintaining full type inference across the application.

How does Kaneo validate API requests?

Kaneo validates requests using Zod schemas integrated through @hono/zod-openapi. Route handlers receive validated, typed parameters via c.req.valid()—no manual parsing needed. This same schema automatically generates OpenAPI documentation and TypeScript types, ensuring the API contract stays synchronized across code and docs.

Can Kaneo scale beyond a single server instance?

Yes. The WebSocket layer supports both in-memory broadcasting (default, single instance) and Redis-backed broadcasting (multi-instance). When REDIS_URL is configured, Kaneo uses ioredis to distribute events across all connected API instances. The database layer uses pg connection pooling, and the stateless HTTP design allows horizontal scaling behind any load balancer.

What database does Kaneo require?

Kaneo requires PostgreSQL as its primary datastore. The backend connects via the pg driver and queries through Drizzle ORM, a type-safe SQL-like query builder. Schema migrations run via drizzle-kit. No other database engines are currently supported in the core backend implementation.

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 →