Project · 2026
Kira
A self-hosted project management tool with boards, sprints, epics, and automation-friendly workflows.
- TypeScript
- React
- Node.js
- Express
- SQLite
- Prisma
- Tailwind CSS
Project snapshot
- Status
- Private self-hosted deployment for project tracking and automation.
- Scope
- Organizations, projects, tickets, epics, sprints, milestones, comments, attachments, documents, and audit logs.
- Deployment
- Private deployment serving the API and compiled React application together.
- Agent surface
- A JSON-RPC tool server exposes ticket, comment, sprint, and document operations for automated agents.
Technical proof
- TypeScript monorepo split into API, web app, and shared type packages.
- JWT access tokens with refresh rotation, Google OAuth, and authenticated file delivery.
- React Query, dnd-kit Kanban interactions, server-sent events, and Playwright E2E coverage.
What it is
Kira is a self-hosted project management application modeled loosely on tools like Jira or Linear. It handles the full lifecycle of software work: tickets, epics, sprints, boards, milestones, and reporting, all within a multi-project, multi-organization structure.
Architecture tour
The codebase is a TypeScript monorepo with three packages. The API package owns business logic and persistence, the web app is a React single-page application, and a shared package holds types, enums, and validation schemas used by both sides. In production the API serves the compiled web app as static files, so the whole thing ships as one deployable unit rather than two services that have to agree on a contract at runtime.
Layers and data flow
Inside the API, a request moves through a fixed stack: route file, controller, service, repository, then Prisma against SQLite. There are 53 route files, one per resource (tickets, epics, sprints, boards, milestones, comments, work logs, webhooks, and so on), 61 service modules, and 41 repository modules. The split is boring on purpose. Controllers parse and respond, services hold the rules, repositories are the only code that talks to the database. When something is wrong at 11pm I know which of the three layers to open first.
Two decisions I’d defend
The interesting design problem was giving automation tools API-level access without giving them a second, parallel API. The answer was a single JSON-RPC endpoint (mcp.routes.ts, backed by an McpServer in src/mcp/server.ts) that exposes a fixed set of tools, ticket creation and updates, comments, sprint operations, document writes, over the same services the REST routes call. Because that endpoint carries many small tool calls instead of a handful of REST verbs, the normal per-request audit middleware doesn’t fit it well, so write tools get their own audit hook at the dispatch layer that logs the tool name and target IDs and deliberately drops the payload.
The other one I’d point to is refresh token rotation with token families, added specifically to detect reuse of a stolen refresh token rather than just expiring it. I know it mattered because I have two migrations for it a few seconds apart in the history, the first one didn’t fully account for concurrent logins and I had to patch it before it ever reached anyone.
How it’s tested
The API package has 127 test files (*.test.ts) covering services, repositories, and route handlers. The web app adds its own unit and component tests plus 28 Playwright specs under e2e/, covering things like board.spec.ts for drag-and-drop, accessibility.spec.ts, mobile-route-guard.spec.ts, and docs-structure.spec.ts for the document tree. Migrations are the other record of the project’s history: 80 of them, tracking the schema from a single-project tool into one with organizations, custom fields, label groups, and agent sessions.
Technical highlights
Authentication supports local email/password and Google OAuth via a server-side popup flow. The API uses JWT access tokens with refresh token rotation. File uploads are served through an authenticated static middleware so attachments are not publicly accessible.
The frontend uses @tanstack/react-query for data fetching, dnd-kit for the Kanban board drag-and-drop, react-markdown with remark-gfm for document rendering, and server-sent events for real-time board updates.