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)
kaneo는 "내 서버(또는 내 노트북)에 도커로 직접 띄워 쓰는, 군더더기를 덜어낸 오픈소스 프로젝트 관리 도구"다. 팀이 할 일을 칸반 보드에 카드로 붙이고, 백로그에 쌓고, 담당자·마감일·라벨·댓글을 달고, 워크스페이스 단위로 사람을 초대해 함께 쓰는 — 그 익숙한 일을 한다. 다른 점은 두 가지다. 첫째, 데이터가 남의 클라우드가 아니라 내 서버에 있다(자가호스팅). 둘째, "기능을 더 넣기"가 아니라 "안 쓰는 기능을 빼기"를 제품 원칙으로 삼는다.
대형 프로젝트 관리 도구들은 모든 팀의 모든 요구를 다 담으려다 설정 화면만 수십 개가 되곤 한다. 스위스 아미 나이프처럼 기능은 많지만, 정작 매일 하는 "카드 하나 옮기기"가 여러 클릭을 거친다.
kaneo는 반대 방향을 택했다. 칸반·백로그·워크플로우처럼 거의 모든 팀이 실제로 매일 쓰는 것만 남기고, 나머지는 과감히 뺀다. "가장 좋은 도구는 보이지 않는 도구"라는 철학 — 도구가 일을 방해하지 않고 뒤로 물러나 있어야 한다는 뜻이다.
그리고 이 식칼은 주방(서버)을 내가 소유한다. 남의 클라우드에 세 들어 사는 게 아니라, 도커 이미지 하나를 내 서버에 올리면 내 데이터는 내 디스크에 남는다. 월 구독료도, 데이터 주권 걱정도 없다.
기술적으로 더 정확히 말하면, kaneo는 하나의 도커 이미지 안에 "React로 만든 웹 앱(프론트)"과 "Hono로 만든 API 서버(백)"를 함께 담아 5173 포트 하나로 서빙하고, 상태는 전부 PostgreSQL 한 곳에 저장하는 풀스택 웹 애플리케이션이다. 실시간 협업(누가 카드를 옮기면 다른 사람 화면도 즉시 갱신)은 WebSocket으로, 이미지 첨부는 S3로, 팀·권한은 인증 라이브러리로 처리한다. 이 모든 조각이 타입스크립트 하나로, 앞뒤가 타입으로 연결된 채 굴러간다는 게 이 레포의 진짜 정체다.
첫째, 포지셔닝이 선명하다. kaneo는 마케팅 사이트에 아예 /jira-alternative, /trello-alternative, /linear-alternative 비교 페이지를 두고, 세 거인의 약점을 정확히 겨냥한다 — Jira는 무겁고 배우기 어렵다, Trello는 백로그·워크플로우가 유료 확장에 묶여 있다, Linear는 폐쇄형·클라우드 전용이라 자가호스팅이 안 된다. kaneo는 "오픈소스 + 자가호스팅 + 덜어낸 UX"라는 빈틈을 노린다.
| 항목 | kaneo | Jira / 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급으로 제공한다는 점이 시류를 탔다.
kaneo의 가치는 세 겹이다. 팀은 공짜로 데이터 주권까지 지키는 PM 도구를 얻고, 개발자는 2026년 풀스택 베스트 프랙티스의 완성된 예제를 얻고, AI를 붙이려는 사람은 MCP로 바로 자동화할 수 있는 대상을 얻는다. 어느 배경에서 와도 가져갈 게 있다는 점이 이 레포가 꾸준히 트렌딩에 오르는 이유다.
kaneo는 2024년 말에 시작된 비교적 젊은 프로젝트다. 별 약 6,850개로 성장세는 뚜렷하지만, Jira·Linear가 수년간 쌓은 고급 기능(정교한 권한 위임, 방대한 통합 마켓, 로드맵/스프린트 리포팅의 깊이)에는 아직 못 미친다. 핵심 유지보수 인력도 소수라, 대규모 조직이 미션 크리티컬하게 도입하려면 기능 공백과 운영 부담(백업·업데이트·가용성)을 스스로 감당해야 한다. "군더더기를 뺐다"는 강점은 뒤집으면 "아직 없는 기능이 많다"는 뜻이기도 하다. 소규모~중간 팀의 가볍고 빠른 보드, 또는 최신 풀스택 학습 교재로 볼 때 가장 빛나는 물건이다.
kaneo는 pnpm 워크스페이스 + Turborepo 모노레포다. 하나의 저장소 안에 여러 앱과 공유 패키지가 살고, Turborepo가 이들의 빌드/린트/테스트를 의존성 순서대로 캐시하며 돌린다. 패키지 매니저는 pnpm@10.32.1로 못 박혔고, Node는 >=20.19, 린터/포매터는 ESLint·Prettier가 아니라 Biome 2.5.4(Rust로 짠 초고속 통합 도구) 하나로 통일했다.
packages/*·apps/*를 한 묶음으로 보고 내부 패키지를 심볼릭 링크로 연결한다(@kaneo/libs를 앱들이 그냥 import). Turborepo는 그 위에서 "이미 빌드한 건 다시 안 한다"는 캐시·병렬 실행을 담당한다. kaneo가 "앞뒤 타입 공유"를 매끄럽게 하는 물리적 토대가 바로 이 구조다.| 앱 | 이름 | 역할 | 핵심 스택 |
|---|---|---|---|
| apps/api | @kaneo/api | 백엔드 REST/RPC + WebSocket | Node 위 Hono · Drizzle · Postgres |
| apps/web | @kaneo/web | 메인 대시보드(SPA) | React 19 · Vite 7 · TanStack |
| apps/site | @kaneo/site | 마케팅/비교 웹사이트 | Next.js 15 |
| apps/docs | — | 문서 | Mintlify(+ 생성된 OpenAPI) |
@kaneo/libs — 공유 Hono RPC 클라이언트(client를 export). 프론트가 백을 부를 때 쓰는 타입 연결의 핵심.@kaneo/permissions — better-auth 접근제어(RBAC) 역할·리소스 정의. 프론트·백이 같은 권한 정의를 공유.@kaneo/mcp — npm에 배포되는 CLI kaneo-mcp(v0.1.6). AI 에이전트용 MCP stdio 서버.@kaneo/email — React Email + nodemailer로 만든 트랜잭션 메일(초대·OTP 등).@kaneo/typescript-config — 공유 tsconfig 프리셋.| 영역 | 무엇을 쓰나 | 역할 |
|---|---|---|
| 프레임워크 | React 19.2 + React Compiler | UI. 컴파일러가 useMemo/useCallback을 자동 삽입 |
| 빌드 | Vite 7.3 | 초고속 개발 서버·번들러 |
| 라우팅 | TanStack Router 1.17(파일 기반) | 48개 라우트. 경로도 타입세이프 |
| 서버 상태 | TanStack Query 5.10 | API 데이터 캐싱·재검증·낙관적 업데이트 |
| 클라 상태 | Zustand 5 · nanostores · immer | UI 로컬 상태(모달·필터 등) |
| 스타일 | 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/* | 칸반 카드 끌어 옮기기 |
| 실시간·i18n | react-use-websocket · i18next | 실시간 갱신 · 다국어 |
| API 호출 | hono(RPC hc) + better-auth 클라이언트 | 타입세이프 백엔드 호출 |
| 영역 | 무엇을 쓰나 | 역할 |
|---|---|---|
| 런타임 | Node.js(@hono/node-server) · dev는 tsx, 빌드는 esbuild | Bun이 아니라 Node. 배포 안정성 우선 |
| 프레임워크 | Hono 4.12 + hono-openapi | 초경량 웹 프레임워크. OpenAPI 문서 자동 생성 |
| ORM | Drizzle ORM 0.45 + drizzle-kit | 타입세이프 SQL. 스키마=코드, 마이그레이션 생성 |
| DB | PostgreSQL(pg + node-postgres Pool) | 모든 상태의 단일 저장소 |
| 인증 | better-auth 1.6(+drizzle-adapter·api-key) | 세션·소셜·조직·RBAC·API키·디바이스 플로우를 한 번에 |
| 검증 | Valibot 1.4(주력) + Zod | 요청 검증. Valibot은 트리셰이킹에 유리한 경량 스키마 |
| ID | @paralleldrive/cuid2 | UUID 대신 정렬 친화·짧은 충돌저항 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년의 사조를 대표한다. 이 조합을 통째로 관찰할 수 있다는 것만으로도 학습 가치가 크다.
보통 풀스택에서 "프론트가 백 API를 호출"할 때 타입이 끊긴다. 백엔드가 { title: string }을 기대해도, 프론트의 fetch는 그냥 문자열 URL이라 오타·형변환 실수를 런타임에야 안다. 이걸 잇는 유명한 해법이 tRPC지만, kaneo는 Hono가 기본 제공하는 RPC로 같은 효과를 더 가볍게 얻는다.
AppType · hc 클라이언트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", // 쿠키 세션을 항상 함께 전송
}),
});
프론트는 API 호출을 컴포넌트에 직접 쓰지 않는다. 세 겹으로 나눈다. ① fetchers/는 연산 하나당 파일 하나로 위 client를 감싼다(예: createTask). ② hooks/는 그 fetcher를 TanStack Query의 useQuery/useMutation으로 감싸, 성공 시 관련 쿼리 키를 무효화(refetch)한다. ③ 컴포넌트는 훅만 부른다. 이 규율 덕에 "데이터를 어떻게 가져오고 캐시를 언제 갱신하는가"가 컴포넌트 밖 한곳에 모인다. 낙관적 업데이트(카드를 놓자마자 UI가 먼저 바뀌고 서버 확인은 뒤따르는)도 이 계층에서 처리된다.
백엔드는 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");
/* ... 컨트롤러 호출 ... */
},
);
실시간 협업의 설계가 특히 배울 만하다. 컨트롤러는 작업을 마치면 결과를 직접 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) 추적AsyncLocalStorage다(요청별 사물함 같은 것). kaneo는 여기에 요청을 일으킨 사용자 id를 담아 둔다. 그러면 "A가 카드를 옮겼다" 이벤트가 브로드캐스트될 때 A 자신에게는 다시 안 보내(이미 A 화면은 낙관적 업데이트로 바뀌었으니 중복·깜빡임 방지) 다른 접속자에게만 보낼 수 있다.많은 프로젝트가 로그인·세션·소셜·권한을 직접 구현하다 보안 구멍을 낸다. 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 플러그인) 나중에 조각만 갈아 끼운다. "지금은 단순하게, 하지만 커질 곳은 열어 두기" — 스타트업 규모에서 시작해 확장까지 버티는 설계의 좋은 표본이다.
읽는 순서를 추천하면: ①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개 도메인은 같은 패턴의 반복이라 빠르게 눈에 들어온다.
schema.ts). 이 선언 하나에서 ① 타입, ② 마이그레이션 SQL, ③ 쿼리 빌더가 전부 나온다. kaneo의 기본키는 UUID가 아니라 CUID2(짧고 정렬 친화적이며 추측이 어려운 ID)이고, 태스크 번호처럼 "프로젝트 안에서만 유일" 같은 제약은 부분 유니크 인덱스로 건다. 예를 들어 task 테이블은 (projectId, number) 조합을 유니크로 묶어 프로젝트별 1,2,3… 번호를 보장한다. "스키마가 곧 문서이자 타입"이라는 접근을 실물로 볼 수 있다.AppType export → hc<AppType> 클라이언트로 코드 생성 없이 앞뒤 타입을 잇는 패턴을 실제 규모에서 관찰한다. tRPC/GraphQL과 무엇이 같고 다른지, 왜 kaneo는 Hono RPC를 골랐는지를 비교하며 배우면 "API 경계 설계"의 안목이 크게 는다.
세션·소셜·조직·팀·RBAC·API키·디바이스 플로우를 직접 짜지 않고 플러그인으로 켜는 법. "워크스페이스 = 조직"으로 멀티테넌시를 표현하는 방식은, SaaS를 만들려는 사람이라면 그대로 베껴 쓸 수 있는 청사진이다.
32개 테이블을 TypeScript로 선언하고, CUID2 기본키·부분 유니크 인덱스·onDelete: cascade를 어떻게 쓰는지 실물로 익힌다. 스키마 하나에서 타입·마이그레이션·쿼리가 전부 파생되는 경험은 ORM을 보는 눈을 바꾼다.
컨트롤러는 이벤트만 쏘고, 전달은 교체 가능한 어댑터가 맡는다(인메모리 ↔ Redis). 단일 서버에서 시작해 수평 확장으로 가는 길을, 코드를 뒤엎지 않고 여는 방법을 배운다. AsyncLocalStorage로 "행위자 에코 억제"를 처리하는 디테일도 실전 감각을 준다.
fetchers → hooks → components 3단 분리, 뮤테이션 후 쿼리 키 무효화, 낙관적 업데이트. "데이터 가져오기·캐시 갱신을 컴포넌트 밖 한곳에 모으기"가 왜 유지보수를 살리는지 몸으로 안다.
"기능을 더하기"가 아니라 "덜어내기"를 규율로 삼는 제품 사고, 그리고 사람이 클릭하는 UI와 에이전트가 부르는 MCP를 동급으로 제공하는 설계. 2026년에 도구를 만든다는 것이 무엇인지를 보여 준다.
보통은 Hono·Drizzle·better-auth·TanStack을 각각 튜토리얼로 따로 배운다. kaneo는 이 조각들이 하나의 실제 제품 안에서 어떻게 서로 맞물리는지를 통째로 보여 준다. 어느 하나만 궁금해서 왔어도, 나머지가 어떻게 붙는지까지 덤으로 얻어 간다.
| 목적 | 필요한 것 | 비고 |
|---|---|---|
| 바로 띄워 보기 | 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.yml | api(1337)·web(5173)·docs(3421) 분리 실행. 마이그레이션은 서버 부팅 시 자동 |
AUTH_SECRET — 반드시 직접 지정openssl rand -hex 32로 만들어 환경변수에 넣는다. 이걸 비워 두면 부팅마다 임의 키가 생겨 재시작 때 모든 세션이 풀린다(전원 로그아웃). 그 외 POSTGRES_*(또는 DATABASE_URL), KANEO_CLIENT_URL/KANEO_API_URL, CORS_ORIGINS만 채우면 최소 구동이 된다. 소셜 로그인·GitHub 연동·메일은 필요할 때만 관련 키를 추가하는 점진적 설정이다.도커 한 방으로 뜨는 편의성 뒤에는 자가호스팅의 책임이 따른다. PostgreSQL 백업(데이터가 여기 전부 있다), 버전 업데이트 시 마이그레이션 확인, HTTPS·역방향 프록시 설정, 그리고 실시간을 여러 서버로 확장한다면 Redis 구성까지 스스로 해야 한다. 데모라면 compose.yml로 충분하지만, 실제 팀 데이터를 올린다면 백업과 업그레이드 절차를 먼저 정해 두는 게 안전하다. 이 부담을 지기 싫으면 관리형 cloud.kaneo.app이 대안이다.
compose.yml을 받아 docker compose up → 5173 포트로 접속해 워크스페이스·프로젝트·칸반을 만들어 본다. 그다음 브라우저 개발자도구 네트워크 탭에서 카드를 옮길 때 어떤 /api/* 요청과 WebSocket 프레임이 오가는지 관찰한다. "화면 동작 ↔ API 호출"의 대응을 눈으로 익히는 게 목표.
apps/api/src/index.ts에서 AppType이 export 되는 지점 → packages/libs/src/hono.ts의 hc<AppType> → apps/web의 어떤 fetchers/가 그 client를 쓰는지 순서대로 따라가 본다. 백엔드 라우트 하나의 응답 타입을 일부러 바꿔 보고, 프론트에서 빨간 줄이 어디에 뜨는지 확인하면 "타입 봉합"이 체감된다.
estimate 시간 추정)schema.ts의 task 테이블에 컬럼 추가 → drizzle-kit으로 마이그레이션 생성 → 해당 도메인의 컨트롤러·Valibot 스키마 수정 → 프론트 fetchers/hooks/폼(react-hook-form+Zod)까지 이어 본다. 하나의 필드가 앞뒤 몇 곳을 건드리는지 몸으로 아는, 풀스택 감각을 키우는 정석 과제.
기존 task/ 폴더를 본으로 삼아 새 서브 라우터(index.ts + controllers/)를 만들고 describeRoute·Valibot·권한 미들웨어를 붙인 뒤 apps/api/src/index.ts에 .route()로 mount 한다. 그러면 별도 작업 없이 프론트 client에 자동으로 타입이 생긴다. OpenAPI 문서에도 새 엔드포인트가 뜨는지 확인.
packages/mcp(kaneo-mcp)를 Claude Desktop이나 Cursor 등 MCP 클라이언트에 등록하고, 디바이스 인증으로 로그인한 뒤 "이번 스프린트 태스크 만들어 줘" 같은 자연어로 실제 보드를 바꿔 본다. 이어서 register.ts의 도구 목록을 읽고 나만의 MCP 도구 하나를 추가해, "UI ↔ API ↔ MCP"가 같은 백엔드를 공유하는 구조를 완성해 본다.
| 주차 | 주제 | 목표 · 산출물 |
|---|---|---|
| 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주가 알짜다.
hc<AppType>AppType)을 프론트로 import 해 서버를 함수처럼 호출하는 것. 코드 생성 없이 네트워크 경계를 타입이 건너간다(tRPC의 경량 대안).validator("param", ...)).createId()).publishEvent로 이벤트만 쏘고, WS 전달은 교체 가능한 어댑터가 담당. 인메모리(단일 서버)↔Redis Pub/Sub(다중 서버)로 갈아 끼워 확장.AsyncLocalStoragekaneo-mcp 서버를 배포해, 에이전트가 자연어로 프로젝트·태스크를 조작하게 한다.@kaneo/permissions에 코드로 선언.AppType export(타입 봉합의 출발점)hc<AppType> 공유 RPC 클라이언트kaneo-mcp 서버