GITHUB 레포 딥다이브 · 2026-08-04 · USEKANEO/KANEO · 군더더기 없는 오픈소스 프로젝트 관리 도구 — 그리고 2026년 풀스택 TypeScript의 교과서

kaneo 딥다이브
"필요한 것만, 군더더기 없이" : 자가호스팅 칸반 뒤에 숨은 타입세이프 풀스택의 표본

usekaneo/kaneo는 직접 서버에 올려 쓰는(self-hosted) 오픈소스 프로젝트 관리 도구다. 칸반 보드·백로그·작업(task)·워크플로우 같은 걸 다루는, Jira·Trello·Linear 자리에 놓을 수 있는 물건이다. 제품이 내세우는 한 문장은 "All you need. Nothing you don't" — 필요한 건 다 있고, 필요 없는 건 없다. 즉 기능이 모자라서가 아니라 너무 많아서 생기는 피로를 겨냥한, "덜어내기"가 컨셉인 도구다.

하지만 이 딥다이브가 kaneo를 고른 진짜 이유는 제품이 예뻐서가 아니다. kaneo의 소스코드 자체가 "2026년에 풀스택 앱을 어떻게 짜는가"에 대한 살아 있는 모범답안이기 때문이다. React 19 + Vite 7 프론트, Hono + Drizzle + PostgreSQL 백엔드, better-auth 인증, 그리고 tRPC 없이도 네트워크 경계를 넘어 타입이 그대로 흐르는 Hono RPC까지 — 요즘 TypeScript 생태계에서 "좋다"고 꼽히는 조각들이 하나의 실제 제품 안에서 어떻게 맞물리는지를 통째로 보여 준다.

구조도 배울 거리다. pnpm 워크스페이스 + Turborepo 모노레포 안에 API·웹·마케팅 사이트·문서가 앱으로 나뉘고, 공유 RPC 클라이언트·MCP 서버·권한(RBAC) 정의가 패키지로 분리돼 있다. 자가호스팅은 도커 이미지 하나 + PostgreSQL 하나면 끝날 만큼 단순한데, 그 단순함 뒤에는 이벤트 버스·WebSocket 브로드캐스트 어댑터(인메모리 ↔ Redis)로 수평 확장까지 염두에 둔 설계가 숨어 있다. 심지어 AI 에이전트가 보드를 조작할 수 있는 공식 MCP 서버(kaneo-mcp)까지 기본 탑재다.

(저장소 usekaneo/kaneo · 라이선스 MIT · 언어 TypeScript(모노레포, 약 889개 소스 파일) · 버전 v2.12.1 · 첫 공개 2024-12-31 · 최신 커밋 2026-08-02 · ★ 약 6,850 · 제작 Andrej Acevski 외 · 자가호스팅 = 도커 + PostgreSQL 16 · TrendShift Daily #17)
목차
  1. 한 줄 정체
  2. 왜 주목받는가 — 기능 경쟁이 아니라 "덜어내기"로
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — Hono RPC·이벤트 버스·better-auth
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 하드웨어 / 시스템 요구사항 (자가호스팅)
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 로드맵 (주차별)
  10. 핵심 키워드 사전
  11. 참고 링크

1한 줄 정체

이 프로젝트가 정확히 무엇인지 한 문장으로

kaneo는 "내 서버(또는 내 노트북)에 도커로 직접 띄워 쓰는, 군더더기를 덜어낸 오픈소스 프로젝트 관리 도구"다. 팀이 할 일을 칸반 보드에 카드로 붙이고, 백로그에 쌓고, 담당자·마감일·라벨·댓글을 달고, 워크스페이스 단위로 사람을 초대해 함께 쓰는 — 그 익숙한 일을 한다. 다른 점은 두 가지다. 첫째, 데이터가 남의 클라우드가 아니라 내 서버에 있다(자가호스팅). 둘째, "기능을 더 넣기"가 아니라 "안 쓰는 기능을 빼기"를 제품 원칙으로 삼는다.

한 장의 비유

"50가지 기능 스위스 아미 나이프" 대신 "손에 딱 맞는 잘 벼린 식칼 한 자루"

대형 프로젝트 관리 도구들은 모든 팀의 모든 요구를 다 담으려다 설정 화면만 수십 개가 되곤 한다. 스위스 아미 나이프처럼 기능은 많지만, 정작 매일 하는 "카드 하나 옮기기"가 여러 클릭을 거친다.

kaneo는 반대 방향을 택했다. 칸반·백로그·워크플로우처럼 거의 모든 팀이 실제로 매일 쓰는 것만 남기고, 나머지는 과감히 뺀다. "가장 좋은 도구는 보이지 않는 도구"라는 철학 — 도구가 일을 방해하지 않고 뒤로 물러나 있어야 한다는 뜻이다.

그리고 이 식칼은 주방(서버)을 내가 소유한다. 남의 클라우드에 세 들어 사는 게 아니라, 도커 이미지 하나를 내 서버에 올리면 내 데이터는 내 디스크에 남는다. 월 구독료도, 데이터 주권 걱정도 없다.

용어
자가호스팅(self-hosting) · 칸반(Kanban) · 백로그(backlog)
자가호스팅은 SaaS 업체 서버가 아니라 내가 관리하는 서버에 소프트웨어를 직접 설치해 운영하는 것이다. 데이터 소유권·프라이버시·비용 통제가 이유다. 칸반은 "할 일 / 진행 중 / 완료" 같은 열(column)에 카드를 붙여 일의 흐름을 눈으로 보는 방식(도요타 생산방식에서 유래). 백로그는 아직 착수하지 않았지만 언젠가 할 일을 쌓아 두는 대기열이다. kaneo는 이 셋을 한 화면 안에서 다룬다.

기술적으로 더 정확히 말하면, kaneo는 하나의 도커 이미지 안에 "React로 만든 웹 앱(프론트)"과 "Hono로 만든 API 서버(백)"를 함께 담아 5173 포트 하나로 서빙하고, 상태는 전부 PostgreSQL 한 곳에 저장하는 풀스택 웹 애플리케이션이다. 실시간 협업(누가 카드를 옮기면 다른 사람 화면도 즉시 갱신)은 WebSocket으로, 이미지 첨부는 S3로, 팀·권한은 인증 라이브러리로 처리한다. 이 모든 조각이 타입스크립트 하나로, 앞뒤가 타입으로 연결된 채 굴러간다는 게 이 레포의 진짜 정체다.

2왜 주목받는가 — 기능 경쟁이 아니라 "덜어내기"로

비슷한 오픈소스 PM 도구가 수십 개인데, kaneo가 상위에 오른 차별점

첫째, 포지셔닝이 선명하다. kaneo는 마케팅 사이트에 아예 /jira-alternative, /trello-alternative, /linear-alternative 비교 페이지를 두고, 세 거인의 약점을 정확히 겨냥한다 — Jira는 무겁고 배우기 어렵다, Trello는 백로그·워크플로우가 유료 확장에 묶여 있다, Linear는 폐쇄형·클라우드 전용이라 자가호스팅이 안 된다. kaneo는 "오픈소스 + 자가호스팅 + 덜어낸 UX"라는 빈틈을 노린다.

항목kaneoJira / Trello / Linear
가격자가호스팅 무료(MIT) · 클라우드 월 $4~사용자당 유료, 규모가 커지면 급증
데이터 소유내 서버 = 내 데이터업체 클라우드에 귀속
오픈소스전체 공개(MIT)Linear·Trello 폐쇄, Jira 부분
설치도커 이미지 1개 + Postgres 1개SaaS 가입(자가호스팅 제한/고가)
철학기능을 덜어낸다기능을 더한다

둘째, 기술 스택이 "지금 가장 트렌디한 조합"이다. React 19(React Compiler 포함)·Vite 7·TanStack Router/Query·Tailwind 4·Hono·Drizzle·better-auth·Valibot — 2026년 TypeScript 커뮤니티에서 "새 프로젝트라면 이걸로"라고 자주 거론되는 것들이 한자리에 모여 있다. 그래서 개발자들에게 kaneo는 단순한 할 일 앱이 아니라 "이 스택을 실제 제품에서 어떻게 엮는지 보여 주는 참조 아키텍처(reference architecture)"로 읽힌다. 별을 누르는 손가락의 상당수는 "나도 이렇게 짜고 싶다"는 개발자다.

셋째, AI 시대에 맞는 훅이 있다. kaneo는 kaneo-mcp라는 공식 MCP(Model Context Protocol) 서버를 npm에 배포한다. 덕분에 Claude·Cursor 같은 AI 에이전트가 "3번 프로젝트에 '로그인 버그 수정' 태스크 만들어 줘" 같은 자연어로 실제 보드를 조작할 수 있다. 프로젝트 관리 도구가 "사람이 클릭하는 UI"를 넘어 "에이전트가 호출하는 API"까지 1급으로 제공한다는 점이 시류를 탔다.

이 레포의 진짜 강점
"쓸 만한 제품" + "그대로 베껴도 되는 최신 아키텍처" + "AI 친화적 설계"

kaneo의 가치는 세 겹이다. 팀은 공짜로 데이터 주권까지 지키는 PM 도구를 얻고, 개발자는 2026년 풀스택 베스트 프랙티스의 완성된 예제를 얻고, AI를 붙이려는 사람은 MCP로 바로 자동화할 수 있는 대상을 얻는다. 어느 배경에서 와도 가져갈 게 있다는 점이 이 레포가 꾸준히 트렌딩에 오르는 이유다.

과장 주의 (정직한 경계)
"Jira 대체"는 방향이지 아직 완결된 현실이 아니다

kaneo는 2024년 말에 시작된 비교적 젊은 프로젝트다. 별 약 6,850개로 성장세는 뚜렷하지만, Jira·Linear가 수년간 쌓은 고급 기능(정교한 권한 위임, 방대한 통합 마켓, 로드맵/스프린트 리포팅의 깊이)에는 아직 못 미친다. 핵심 유지보수 인력도 소수라, 대규모 조직이 미션 크리티컬하게 도입하려면 기능 공백과 운영 부담(백업·업데이트·가용성)을 스스로 감당해야 한다. "군더더기를 뺐다"는 강점은 뒤집으면 "아직 없는 기능이 많다"는 뜻이기도 하다. 소규모~중간 팀의 가볍고 빠른 보드, 또는 최신 풀스택 학습 교재로 볼 때 가장 빛나는 물건이다.

3기술 스택 전체 지도

모노레포 하나에 프론트·백·인프라가 전부 — 그리고 그 전부가 TypeScript

kaneo는 pnpm 워크스페이스 + Turborepo 모노레포다. 하나의 저장소 안에 여러 앱과 공유 패키지가 살고, Turborepo가 이들의 빌드/린트/테스트를 의존성 순서대로 캐시하며 돌린다. 패키지 매니저는 pnpm@10.32.1로 못 박혔고, Node는 >=20.19, 린터/포매터는 ESLint·Prettier가 아니라 Biome 2.5.4(Rust로 짠 초고속 통합 도구) 하나로 통일했다.

용어
모노레포(monorepo) · pnpm 워크스페이스 · Turborepo
모노레포는 여러 프로젝트(앱·라이브러리)를 하나의 저장소에 함께 두는 방식이다. API와 웹이 같은 타입을 공유해야 할 때, 저장소가 나뉘어 있으면 버전 맞추기가 지옥이 된다. pnpm 워크스페이스packages/*·apps/*를 한 묶음으로 보고 내부 패키지를 심볼릭 링크로 연결한다(@kaneo/libs를 앱들이 그냥 import). Turborepo는 그 위에서 "이미 빌드한 건 다시 안 한다"는 캐시·병렬 실행을 담당한다. kaneo가 "앞뒤 타입 공유"를 매끄럽게 하는 물리적 토대가 바로 이 구조다.

앱 (apps/) — 4개

이름역할핵심 스택
apps/api@kaneo/api백엔드 REST/RPC + WebSocketNode 위 Hono · Drizzle · Postgres
apps/web@kaneo/web메인 대시보드(SPA)React 19 · Vite 7 · TanStack
apps/site@kaneo/site마케팅/비교 웹사이트Next.js 15
apps/docs문서Mintlify(+ 생성된 OpenAPI)

공유 패키지 (packages/) — 5개

프론트엔드 (apps/web) — 정확한 버전으로

영역무엇을 쓰나역할
프레임워크React 19.2 + React CompilerUI. 컴파일러가 useMemo/useCallback을 자동 삽입
빌드Vite 7.3초고속 개발 서버·번들러
라우팅TanStack Router 1.17(파일 기반)48개 라우트. 경로도 타입세이프
서버 상태TanStack Query 5.10API 데이터 캐싱·재검증·낙관적 업데이트
클라 상태Zustand 5 · nanostores · immerUI 로컬 상태(모달·필터 등)
스타일Tailwind CSS 4.3 + CVA · clsx유틸리티 CSS. 디자인 토큰
UI 부품Radix UI · base-ui · lucide · cmdk · sonner · framer-motion접근성 갖춘 원시 컴포넌트 + 커맨드 팔레트 + 토스트 + 애니메이션
react-hook-form 7 + Zod 4폼 상태 + 스키마 검증
에디터TipTap 3(확장 ~20개)태스크 설명의 리치 텍스트(표·체크리스트·코드블록)
드래그앤드롭@dnd-kit/*칸반 카드 끌어 옮기기
실시간·i18nreact-use-websocket · i18next실시간 갱신 · 다국어
API 호출hono(RPC hc) + better-auth 클라이언트타입세이프 백엔드 호출

백엔드 (apps/api) — 정확한 버전으로

영역무엇을 쓰나역할
런타임Node.js(@hono/node-server) · dev는 tsx, 빌드는 esbuildBun이 아니라 Node. 배포 안정성 우선
프레임워크Hono 4.12 + hono-openapi초경량 웹 프레임워크. OpenAPI 문서 자동 생성
ORMDrizzle ORM 0.45 + drizzle-kit타입세이프 SQL. 스키마=코드, 마이그레이션 생성
DBPostgreSQL(pg + node-postgres Pool)모든 상태의 단일 저장소
인증better-auth 1.6(+drizzle-adapter·api-key)세션·소셜·조직·RBAC·API키·디바이스 플로우를 한 번에
검증Valibot 1.4(주력) + Zod요청 검증. Valibot은 트리셰이킹에 유리한 경량 스키마
ID@paralleldrive/cuid2UUID 대신 정렬 친화·짧은 충돌저항 ID
실시간@hono/node-ws(WebSocket)보드 변경을 접속자에게 즉시 푸시
확장(선택)ioredis(Pub/Sub) · croner(스케줄러)다중 인스턴스 WS 브로드캐스트 · 마감 알림 크론
저장/통합AWS S3 SDK · octokit(GitHub App) · Slack/Discord/Telegram 웹훅이미지 업로드 · 외부 서비스 연동
AI@modelcontextprotocol/sdk에이전트가 부를 수 있는 내장 MCP 라우트
스택에서 배우는 것
"하나의 타입 언어 + 경량 최신 도구"로 앞뒤를 꿰뚫는다

kaneo 스택의 관통 주제는 TypeScript 단일 언어 + 무겁지 않은 최신 도구다. 프레임워크는 Express가 아닌 Hono, ORM은 무거운 Prisma가 아닌 Drizzle, 검증은 필요에 따라 Valibot, 인증은 직접 짠 JWT가 아닌 better-auth. 하나같이 "직접 다 만들지 말고, 잘 만든 경량 조각을 타입으로 이어 붙여라"는 2026년의 사조를 대표한다. 이 조합을 통째로 관찰할 수 있다는 것만으로도 학습 가치가 크다.

4아키텍처 심화 — Hono RPC·이벤트 버스·better-auth

이 레포의 진짜 알맹이. 타입이 어떻게 네트워크를 건너가고, 실시간이 어떻게 확장되는가

전체 그림

┌──────────────────────────────────────────────────────────────────────┐ │ kaneo 시스템 구조 (자가호스팅 1대 기준) │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ [브라우저 SPA] apps/web (React 19 + Vite) │ │ │ components → hooks(TanStack Query) → fetchers │ │ │ │ │ │ ① hc<AppType> : 타입이 그대로 흐르는 Hono RPC 호출 │ │ │ ② 쿠키 세션 (credentials: include) │ │ ▼ │ │ ═══════════════ HTTP /api/* + WebSocket /ws ═══════════════ │ │ ▼ │ │ [API 서버] apps/api (Hono on Node, 포트 1337 / 통합 5173) │ │ ├ /api/auth/* → better-auth 핸들러 (세션·소셜·조직·RBAC) │ │ ├ /api/tasks.. → 도메인 라우터 39개 (describeRoute + Valibot) │ │ │ └ controllers/ (순수 async 함수) → Drizzle │ │ ├ 이벤트 버스 (Node EventEmitter) ── publishEvent("task.created") │ │ │ └ WS 구독자 → BroadcastAdapter ─┐ │ │ └ croner 스케줄러 (마감 알림) │ │ │ │ │ │ │ ▼ ▼ │ │ [PostgreSQL 16] 32개 테이블 [인메모리 or Redis Pub/Sub] │ │ (Drizzle 스키마=코드, CUID2 PK) (여러 서버로 확장 시 Redis) │ │ │ │ (선택) S3 = 이미지 업로드 · SMTP = 메일 · GitHub App = 이슈 연동 │ └──────────────────────────────────────────────────────────────────────┘

축 1 — Hono RPC: tRPC 없이 앞뒤 타입을 잇는다 (이 레포의 하이라이트)

보통 풀스택에서 "프론트가 백 API를 호출"할 때 타입이 끊긴다. 백엔드가 { title: string }을 기대해도, 프론트의 fetch는 그냥 문자열 URL이라 오타·형변환 실수를 런타임에야 안다. 이걸 잇는 유명한 해법이 tRPC지만, kaneo는 Hono가 기본 제공하는 RPC로 같은 효과를 더 가볍게 얻는다.

용어
Hono RPC · AppType · hc 클라이언트
Hono 앱은 라우트를 정의하면 그 입력·출력 타입을 모두 담은 하나의 타입을 만들 수 있다. kaneo는 API의 모든 라우트를 합친 이 타입을 AppType이라는 이름으로 export한다. 그러면 프론트는 hc<AppType>(url)로 클라이언트를 만들고, 서버 코드를 함수처럼 호출한다 — client.tasks[":projectId"].$get(...). 경로·본문·응답 타입이 전부 자동 추론되어, 백엔드 스키마를 고치면 프론트가 컴파일 단계에서 빨간 줄로 알려 준다. 별도 코드 생성 없이, import 하나로 네트워크 경계를 타입이 건너간다.

공유 패키지 @kaneo/libs가 그 클라이언트를 만들어 앱 전체에 나눠 준다:

// packages/libs/src/hono.ts — 프론트 전체가 쓰는 타입세이프 클라이언트
import type { AppType } from "@kaneo/api";   // 백엔드 타입을 그대로 import
import { hc } from "hono/client";

export const client = hc<AppType>(apiUrl, {
  fetch: (input, init) =>
    fetch(input, {
      ...init,
      headers: { ...init?.headers, "Content-Type": "application/json" },
      credentials: "include",   // 쿠키 세션을 항상 함께 전송
    }),
});

축 2 — 3단 프론트 데이터 계층: fetchers → hooks → components

프론트는 API 호출을 컴포넌트에 직접 쓰지 않는다. 세 겹으로 나눈다. ① fetchers/는 연산 하나당 파일 하나로 위 client를 감싼다(예: createTask). hooks/는 그 fetcher를 TanStack Query의 useQuery/useMutation으로 감싸, 성공 시 관련 쿼리 키를 무효화(refetch)한다. ③ 컴포넌트는 훅만 부른다. 이 규율 덕에 "데이터를 어떻게 가져오고 캐시를 언제 갱신하는가"가 컴포넌트 밖 한곳에 모인다. 낙관적 업데이트(카드를 놓자마자 UI가 먼저 바뀌고 서버 확인은 뒤따르는)도 이 계층에서 처리된다.

축 3 — API 계층화: 도메인 폴더 → 서브 라우터 → 컨트롤러

백엔드는 task/·project/·column/·workspace/처럼 도메인마다 폴더(약 39개)를 둔다. 각 폴더의 index.ts는 Hono 서브 라우터로, OpenAPI 설명(describeRoute) + Valibot 검증 + 접근제어 미들웨어를 선언하고, 실제 로직은 controllers/의 순수 async 함수에 위임한다. 라우트 정의만 봐도 그 엔드포인트의 문서·검증·권한이 한눈에 들어온다:

// apps/api/src/task/index.ts — 라우트 = 문서 + 검증 + 권한 + 로직
const task = new Hono<{ Variables: { userId: string } }>()
  .get(
    "/tasks/:projectId",
    describeRoute({ operationId: "listTasks", tags: ["Tasks"],
      description: "Get all tasks for a specific project" }),
    validator("param", v.object({ projectId: v.string() })),  // Valibot 검증
    workspaceAccess.fromProject("projectId"),                      // 권한 미들웨어
    async (c) => {
      const { projectId } = c.req.valid("param");
      /* ... 컨트롤러 호출 ... */
    },
  );

축 4 — 이벤트 버스 + 브로드캐스트 어댑터: 실시간을 확장 가능하게

실시간 협업의 설계가 특히 배울 만하다. 컨트롤러는 작업을 마치면 결과를 직접 WebSocket으로 쏘지 않고, 프로세스 내부의 이벤트 버스(Node EventEmitter)에 publishEvent("task.created", ...)로 알리기만 한다. WS 구독자가 그 이벤트를 받아 접속자에게 브로드캐스트한다. 핵심은 이 브로드캐스트가 교체 가능한 어댑터라는 점 — 서버가 1대면 인메모리 어댑터로 충분하고, 여러 대로 늘리면 Redis Pub/Sub 어댑터로 바꿔 서버 간에 이벤트를 퍼뜨린다. 코드 대부분은 그대로 두고 한 조각만 갈아 끼워 단일 서버 → 수평 확장으로 간다.

// apps/api/src/events/index.ts — 행위자(actor)를 추적하는 인프로세스 버스
const EVENTS = new EventEmitter();
export const eventContext = new AsyncLocalStorage<{ initiatorId: string }>();

export async function publishEvent(eventType: string, data: unknown) {
  const store = eventContext.getStore();
  // 누가 일으킨 이벤트인지 실어 보낸다 → 그 사람에겐 WS 에코를 안 보냄
  const enhanced = { ...(data as object), initiatorId: store?.initiatorId };
  EVENTS.emit(eventType, { type: eventType, data: enhanced, timestamp: new Date().toISOString() });
}
용어
AsyncLocalStorage · 행위자(initiator) 추적
웹 서버는 요청 수천 개를 동시에 처리한다. "지금 이 코드가 누구의 요청 안에서 도는가"를 매개변수로 일일이 넘기지 않고 알아내는 Node의 장치가 AsyncLocalStorage다(요청별 사물함 같은 것). kaneo는 여기에 요청을 일으킨 사용자 id를 담아 둔다. 그러면 "A가 카드를 옮겼다" 이벤트가 브로드캐스트될 때 A 자신에게는 다시 안 보내(이미 A 화면은 낙관적 업데이트로 바뀌었으니 중복·깜빡임 방지) 다른 접속자에게만 보낼 수 있다.

축 5 — better-auth: 인증을 직접 짜지 않는다

많은 프로젝트가 로그인·세션·소셜·권한을 직접 구현하다 보안 구멍을 낸다. kaneo는 /api/auth/* 전체를 better-auth에 넘긴다. 이메일+비밀번호(bcrypt), 소셜 로그인(GitHub·Google·Discord), 매직링크·OTP·익명 게스트, 조직(organization) 플러그인으로 "워크스페이스 = 조직", 팀·동적 역할까지, 그리고 API 키·디바이스 인증(MCP/CLI용)이 전부 플러그인 조립으로 켜진다. 권한은 별도 패키지 @kaneo/permissions가 리소스·액션·역할을 코드로 선언해 프론트·백이 같은 정의를 공유한다.

// packages/permissions/src/index.ts — 권한을 코드로 선언(프론트·백 공유)
export const statement = {
  project: ["create", "read", "update", "delete", "share"],
  task:    ["create", "read", "update", "delete", "assign"],
  label:   ["create", "read", "update", "delete"],
  workspace: ["read", "update", "delete", "manage_settings"],
} as const;
export const member = ac.newRole({  // 예: 일반 멤버 역할
  project: ["create", "read"], task: ["create", "read", "update"], workspace: ["read"],
});
아키텍처에서 배우는 것
"경계를 타입으로 잇고, 확장점은 어댑터로 남긴다"

kaneo 아키텍처의 두 기둥은 명확하다. ① 네트워크·권한 같은 경계를 타입으로 봉합해(Hono RPC, 공유 권한 정의) 실수를 컴파일 단계로 끌어오고, ② 미래에 바뀔 곳은 어댑터/플러그인으로 남겨(WS 브로드캐스트 인메모리↔Redis, better-auth 플러그인) 나중에 조각만 갈아 끼운다. "지금은 단순하게, 하지만 커질 곳은 열어 두기" — 스타트업 규모에서 시작해 확장까지 버티는 설계의 좋은 표본이다.

5디렉토리 구조 해부

모노레포 어디에 무엇이 있나 — 읽는 순서가 딸린 지도
kaneo/ ├── apps/ │ ├── api/ ◀── 백엔드 (Hono on Node) │ │ └── src/ │ │ ├── index.ts ★ 모든 도메인 라우터를 /api/* 로 조립 + AppType export │ │ ├── auth.ts ★ better-auth 설정(소셜·조직·플러그인) │ │ ├── database/ │ │ │ ├── schema.ts ★ Drizzle 스키마 = 32개 테이블 (1,072줄) │ │ │ ├── relations.ts 테이블 관계 정의 │ │ │ └── index.ts pg Pool 연결 │ │ ├── task/ project/ column/ workspace/ ... ◀── 도메인 39개 │ │ │ ├── index.ts 서브 라우터 (describeRoute + Valibot + 미들웨어) │ │ │ └── controllers/ 순수 async 함수 (실제 로직) │ │ ├── events/index.ts ★ 인프로세스 이벤트 버스 │ │ └── ws/ , middlewares/ , integrations/(github·slack·...) │ │ └── drizzle/ 36개 SQL 마이그레이션 │ ├── web/ ◀── 프론트 (React 19 + Vite) │ │ └── src/ │ │ ├── main.tsx ★ Query·Router·Auth·Theme·i18n 프로바이더 조립 │ │ ├── routes/ 48개 파일 기반 라우트 (+ routeTree.gen.ts) │ │ ├── fetchers/ client 를 감싼 연산 단위 │ │ ├── hooks/ TanStack Query 훅 (캐시 무효화) │ │ └── components/ , store/(zustand) │ ├── site/ Next.js 15 마케팅·비교 페이지 │ └── docs/ Mintlify 문서 (+ 생성된 openapi.json) │ ├── packages/ │ ├── libs/ ★ hc<AppType> 공유 RPC 클라이언트 │ ├── permissions/ ★ RBAC 리소스·역할 정의 (프론트·백 공유) │ ├── mcp/ kaneo-mcp — AI 에이전트용 MCP 서버(npm 배포) │ ├── email/ React Email + nodemailer │ └── typescript-config/ 공유 tsconfig │ ├── charts/kaneo/ Kubernetes Helm 차트 (Deployment·HPA·Ingress...) ├── compose.yml 자가호스팅용 (postgres + kaneo 이미지 1개) ├── Dockerfile.kaneo 멀티스테이지: api-builder → web-builder → runtime ├── turbo.json · pnpm-workspace.yaml · biome.json └── CLAUDE.md (이 레포도 AI 협업 규칙 문서를 둔다)

읽는 순서를 추천하면: apps/api/src/index.ts(라우터가 어떻게 합쳐지고 AppType이 어디서 나오는지 = 계약서) → packages/libs/src/hono.ts(그 타입이 프론트로 건너가는 지점) → apps/api/src/database/schema.ts(데이터 모델 전체) → apps/api/src/task/(한 도메인이 라우터+컨트롤러로 어떻게 쪼개지는지) → apps/web/src/hooks/+fetchers/(프론트가 그걸 어떻게 소비하는지) 순이다. 한 도메인(예: task)을 앞뒤로 관통해 읽으면 나머지 38개 도메인은 같은 패턴의 반복이라 빠르게 눈에 들어온다.

용어
Drizzle 스키마 = 코드 · CUID2 · 부분 유니크 인덱스
Drizzle은 DB 테이블을 TypeScript 파일로 선언한다(schema.ts). 이 선언 하나에서 ① 타입, ② 마이그레이션 SQL, ③ 쿼리 빌더가 전부 나온다. kaneo의 기본키는 UUID가 아니라 CUID2(짧고 정렬 친화적이며 추측이 어려운 ID)이고, 태스크 번호처럼 "프로젝트 안에서만 유일" 같은 제약은 부분 유니크 인덱스로 건다. 예를 들어 task 테이블은 (projectId, number) 조합을 유니크로 묶어 프로젝트별 1,2,3… 번호를 보장한다. "스키마가 곧 문서이자 타입"이라는 접근을 실물로 볼 수 있다.

6학습 포인트 — 기술별 배울 것

이 레포 한 편으로 건드릴 수 있는, 요즘 풀스택의 알짜들

① 타입세이프 풀스택 — Hono RPC로 경계를 봉합

AppType export → hc<AppType> 클라이언트로 코드 생성 없이 앞뒤 타입을 잇는 패턴을 실제 규모에서 관찰한다. tRPC/GraphQL과 무엇이 같고 다른지, 왜 kaneo는 Hono RPC를 골랐는지를 비교하며 배우면 "API 경계 설계"의 안목이 크게 는다.

② 인증을 사지 말고 조립하기 — better-auth

세션·소셜·조직·팀·RBAC·API키·디바이스 플로우를 직접 짜지 않고 플러그인으로 켜는 법. "워크스페이스 = 조직"으로 멀티테넌시를 표현하는 방식은, SaaS를 만들려는 사람이라면 그대로 베껴 쓸 수 있는 청사진이다.

③ 스키마=코드 — Drizzle로 데이터 모델링

32개 테이블을 TypeScript로 선언하고, CUID2 기본키·부분 유니크 인덱스·onDelete: cascade를 어떻게 쓰는지 실물로 익힌다. 스키마 하나에서 타입·마이그레이션·쿼리가 전부 파생되는 경험은 ORM을 보는 눈을 바꾼다.

④ 실시간을 확장 가능하게 — 이벤트 버스 + 어댑터

컨트롤러는 이벤트만 쏘고, 전달은 교체 가능한 어댑터가 맡는다(인메모리 ↔ Redis). 단일 서버에서 시작해 수평 확장으로 가는 길을, 코드를 뒤엎지 않고 여는 방법을 배운다. AsyncLocalStorage로 "행위자 에코 억제"를 처리하는 디테일도 실전 감각을 준다.

⑤ 프론트 데이터 계층의 규율 — TanStack Query

fetchers → hooks → components 3단 분리, 뮤테이션 후 쿼리 키 무효화, 낙관적 업데이트. "데이터 가져오기·캐시 갱신을 컴포넌트 밖 한곳에 모으기"가 왜 유지보수를 살리는지 몸으로 안다.

⑥ 제품 철학 · AI 친화 — 덜어내기와 MCP

"기능을 더하기"가 아니라 "덜어내기"를 규율로 삼는 제품 사고, 그리고 사람이 클릭하는 UI와 에이전트가 부르는 MCP를 동급으로 제공하는 설계. 2026년에 도구를 만든다는 것이 무엇인지를 보여 준다.

한 줄 요약
"요즘 TypeScript 풀스택의 좋은 조각들"이 한 제품에서 맞물린다

보통은 Hono·Drizzle·better-auth·TanStack을 각각 튜토리얼로 따로 배운다. kaneo는 이 조각들이 하나의 실제 제품 안에서 어떻게 서로 맞물리는지를 통째로 보여 준다. 어느 하나만 궁금해서 왔어도, 나머지가 어떻게 붙는지까지 덤으로 얻어 간다.

7하드웨어 / 시스템 요구사항 (자가호스팅)

그냥 써 볼지, 팀에 올릴지, 코드를 뜯어볼지에 따라 다르다
목적필요한 것비고
바로 띄워 보기Docker + Docker Compose. compose.yml 하나로 실행Postgres + kaneo 이미지 2개 컨테이너가 전부. 몇 분이면 5173 포트에서 열림
소규모 팀 운영2 vCPU / 2~4GB RAM VPS + PostgreSQL 16가벼운 편. Redis/SMTP/S3는 선택(각각 다중서버·메일·이미지용)
여러 서버로 확장위 + Redis/Valkey + 로드밸런서WS 브로드캐스트를 Redis Pub/Sub로. Helm 차트에 HPA 오토스케일 포함
코드 개발Node ≥20.19 · pnpm 10.32 · Postgres. compose.local.ymlapi(1337)·web(5173)·docs(3421) 분리 실행. 마이그레이션은 서버 부팅 시 자동
필수 설정 하나
AUTH_SECRET — 반드시 직접 지정
better-auth가 세션을 서명할 비밀키다. openssl rand -hex 32로 만들어 환경변수에 넣는다. 이걸 비워 두면 부팅마다 임의 키가 생겨 재시작 때 모든 세션이 풀린다(전원 로그아웃). 그 외 POSTGRES_*(또는 DATABASE_URL), KANEO_CLIENT_URL/KANEO_API_URL, CORS_ORIGINS만 채우면 최소 구동이 된다. 소셜 로그인·GitHub 연동·메일은 필요할 때만 관련 키를 추가하는 점진적 설정이다.
자가호스팅 시 흔한 함정
"띄우는 건 쉽지만, 운영은 내 몫"

도커 한 방으로 뜨는 편의성 뒤에는 자가호스팅의 책임이 따른다. PostgreSQL 백업(데이터가 여기 전부 있다), 버전 업데이트 시 마이그레이션 확인, HTTPS·역방향 프록시 설정, 그리고 실시간을 여러 서버로 확장한다면 Redis 구성까지 스스로 해야 한다. 데모라면 compose.yml로 충분하지만, 실제 팀 데이터를 올린다면 백업과 업그레이드 절차를 먼저 정해 두는 게 안전하다. 이 부담을 지기 싫으면 관리형 cloud.kaneo.app이 대안이다.

8직접 해볼 수 있는 실습 과제

클릭 한 번으로 띄우는 것부터, 앞뒤를 관통해 기능을 추가하는 것까지 — 난이도별
난이도 ★☆☆ — 띄우고 관찰

1. compose로 5분 만에 실행하고 구조 파악하기

compose.yml을 받아 docker compose up → 5173 포트로 접속해 워크스페이스·프로젝트·칸반을 만들어 본다. 그다음 브라우저 개발자도구 네트워크 탭에서 카드를 옮길 때 어떤 /api/* 요청과 WebSocket 프레임이 오가는지 관찰한다. "화면 동작 ↔ API 호출"의 대응을 눈으로 익히는 게 목표.

난이도 ★☆☆ — 코드 읽기

2. 타입이 네트워크를 건너가는 경로 추적하기

apps/api/src/index.ts에서 AppType이 export 되는 지점 → packages/libs/src/hono.tshc<AppType>apps/web의 어떤 fetchers/가 그 client를 쓰는지 순서대로 따라가 본다. 백엔드 라우트 하나의 응답 타입을 일부러 바꿔 보고, 프론트에서 빨간 줄이 어디에 뜨는지 확인하면 "타입 봉합"이 체감된다.

난이도 ★★☆ — 스키마→UI 관통

3. 태스크에 필드 하나 추가하기 (예: estimate 시간 추정)

schema.tstask 테이블에 컬럼 추가 → drizzle-kit으로 마이그레이션 생성 → 해당 도메인의 컨트롤러·Valibot 스키마 수정 → 프론트 fetchers/hooks/폼(react-hook-form+Zod)까지 이어 본다. 하나의 필드가 앞뒤 몇 곳을 건드리는지 몸으로 아는, 풀스택 감각을 키우는 정석 과제.

난이도 ★★★ — 엔드포인트 신설

4. 새 도메인 라우트를 타입세이프하게 추가하기

기존 task/ 폴더를 본으로 삼아 새 서브 라우터(index.ts + controllers/)를 만들고 describeRoute·Valibot·권한 미들웨어를 붙인 뒤 apps/api/src/index.ts.route()로 mount 한다. 그러면 별도 작업 없이 프론트 client에 자동으로 타입이 생긴다. OpenAPI 문서에도 새 엔드포인트가 뜨는지 확인.

난이도 ★★★★ — AI 연동

5. MCP 서버를 에이전트에 연결해 보드를 자연어로 조작

packages/mcp(kaneo-mcp)를 Claude Desktop이나 Cursor 등 MCP 클라이언트에 등록하고, 디바이스 인증으로 로그인한 뒤 "이번 스프린트 태스크 만들어 줘" 같은 자연어로 실제 보드를 바꿔 본다. 이어서 register.ts의 도구 목록을 읽고 나만의 MCP 도구 하나를 추가해, "UI ↔ API ↔ MCP"가 같은 백엔드를 공유하는 구조를 완성해 본다.

9관련 기술 심화 로드맵 (주차별)

kaneo를 온전히 소화하기 위한 8주 경로 — 배경이 없어도 따라올 수 있게
주차주제목표 · 산출물
1주TypeScript 심화 + 모노레포제네릭·타입 추론·as const. pnpm 워크스페이스로 미니 모노레포(공유 패키지 1개) 만들기
2주Hono 기초라우팅·미들웨어·검증. 작은 REST API를 Hono로 작성하고 OpenAPI 문서 자동생성
3주Hono RPC(타입 봉합)AppType export → hc 클라이언트. 앞뒤 타입이 이어지는 미니 앱 완성
4주Drizzle + PostgreSQL스키마=코드·관계·마이그레이션. kaneo의 task/project 모델을 스스로 재현
5주better-auth세션·소셜·조직·RBAC 플러그인 조립. "워크스페이스=조직" 멀티테넌시 흉내 내기
6주TanStack Query + Router서버 상태 캐싱·무효화·낙관적 업데이트, 파일 기반 라우팅. fetchers→hooks 계층 구현
7주실시간(WebSocket) & 확장WS 기초 + 이벤트 버스 + 브로드캐스트 어댑터. 인메모리→Redis Pub/Sub로 갈아 끼우기
8주배포 & MCP도커 멀티스테이지·compose·Helm 개념. kaneo-mcp를 에이전트에 붙여 자연어로 조작

배경이 이미 있다면 3주(Hono RPC) → 7주(실시간·확장)만 골라 읽어도 kaneo의 두 하이라이트(타입 봉합 + 확장 가능한 실시간)를 잡을 수 있다. 백엔드가 궁금하면 2·3·4·5주, 프론트가 궁금하면 6주가 알짜다.

10핵심 키워드 사전

이 문서와 README를 읽을 때 걸리는 용어들 — 한 줄 정의
키워드
자가호스팅 (self-hosting)
SaaS 서버가 아니라 내가 관리하는 서버에 직접 설치·운영하는 것. 데이터 소유·프라이버시·비용 통제가 이유. kaneo는 도커 이미지 1개 + Postgres로 가능.
키워드
모노레포 (monorepo) / Turborepo
여러 앱·패키지를 한 저장소에 두는 방식. pnpm 워크스페이스가 내부 패키지를 연결하고, Turborepo가 빌드 캐시·병렬 실행을 담당. 앞뒤 타입 공유의 토대.
키워드
Hono
초경량·고속 웹 프레임워크(Express 대체). 어느 런타임에서나 돌고, kaneo는 Node 위에서 사용. OpenAPI 문서와 RPC 클라이언트를 기본 지원.
키워드
Hono RPC / hc<AppType>
백엔드 라우트 타입(AppType)을 프론트로 import 해 서버를 함수처럼 호출하는 것. 코드 생성 없이 네트워크 경계를 타입이 건너간다(tRPC의 경량 대안).
키워드
Drizzle ORM
DB 스키마를 TypeScript로 선언하는 타입세이프 ORM. 스키마 하나에서 타입·마이그레이션·쿼리 빌더가 파생. Prisma보다 가볍고 SQL에 가깝다.
키워드
better-auth
세션·소셜·조직·RBAC·API키·디바이스 인증을 플러그인으로 제공하는 TS 인증 라이브러리. kaneo는 인증을 직접 짜지 않고 이걸 조립해 쓴다.
키워드
Valibot
Zod 계열의 스키마 검증 라이브러리인데 트리셰이킹에 유리해 번들이 작다. kaneo API의 주력 요청 검증기(validator("param", ...)).
키워드
CUID2
UUID 대신 쓰는 짧고 정렬 친화적이며 추측이 어려운 충돌저항 ID. kaneo의 모든 테이블 기본키 생성기(createId()).
키워드
TanStack Query / Router
Query=서버 데이터의 캐싱·재검증·낙관적 업데이트를 다루는 라이브러리. Router=파일 기반·타입세이프 라우팅. kaneo 프론트 데이터 계층의 두 축.
키워드
이벤트 버스 / BroadcastAdapter
컨트롤러가 publishEvent로 이벤트만 쏘고, WS 전달은 교체 가능한 어댑터가 담당. 인메모리(단일 서버)↔Redis Pub/Sub(다중 서버)로 갈아 끼워 확장.
키워드
AsyncLocalStorage
"지금 이 코드가 누구의 요청 안에서 도는지"를 매개변수 없이 알아내는 Node 장치. kaneo는 행위자 id를 담아 자기 자신에게 WS 에코를 보내지 않게 한다.
키워드
MCP (Model Context Protocol)
AI 에이전트가 외부 도구를 호출하는 표준 프로토콜. kaneo는 kaneo-mcp 서버를 배포해, 에이전트가 자연어로 프로젝트·태스크를 조작하게 한다.
키워드
RBAC (역할 기반 접근제어)
리소스(project·task…)마다 액션(create·read…)을 정의하고, 역할(owner·admin·member·viewer)에 액션을 묶어 권한을 관리. kaneo는 @kaneo/permissions에 코드로 선언.
키워드
워크스페이스 = 조직(organization)
멀티테넌시를 표현하는 방식. better-auth의 조직 개념을 그대로 "워크스페이스"로 매핑해 팀·멤버·역할·초대를 관리. 세션이 활성 조직/팀 id를 실어 나른다.

11참고 링크

원본을 직접 확인하고 싶을 때