Mandu Create API
SkillDev toolsREST API creation. CRUD/auth/upload endpoints. Automatically triggered on 'API', 'endpoint', 'CRUD'
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Mandu Create API skill
What this skill tells your AI
The instructions your AI receives, as published by konamgil/mandu in docs/archive/skills/package-v0/mandu-create-api/SKILL.md and read by ahel’s review.
REST API 엔드포인트를 생성하는 워크플로우. Route + Contract + Slot + Test 파이프라인으로 완전한 API를 스캐폴딩합니다.
Workflow: Route -> Contract -> Slot -> Test
Step 1: Route 생성
// app/api/posts/route.ts
import { Mandu } from "@mandujs/core";
export default Mandu.filling()
.get((ctx) => ctx.ok({ posts: [] }))
.post(async (ctx) => {
const body = await ctx.body<CreatePostInput>();
return ctx.created({ post: body });
});
Step 2: Contract 정의
// src/shared/contracts/post.contract.ts
import { z } from "zod";
export const CreatePostInput = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
tags: z.array(z.string()).optional(),
});
export const PostResponse = z.object({
id: z.string(),
title: z.string(),
content: z.string(),
createdAt: z.string().datetime(),
});
export type CreatePostInput = z.infer<typeof CreatePostInput>;
export type PostResponse = z.infer<typeof PostResponse>;
Step 3: Slot (서버 데이터 로더)
// spec/slots/posts.slot.ts
import { Mandu } from "@mandujs/core";
import { CreatePostInput } from "@/shared/contracts/post.contract";
export default Mandu.filling()
.guard(async (ctx) => {
const token = ctx.headers.get("authorization");
if (!token) return ctx.error(401, "Unauthorized");
ctx.set("userId", await verifyToken(token));
})
.post(async (ctx) => {
const body = await ctx.body<CreatePostInput>();
const validated = CreatePostInput.parse(body);
const post = await db.posts.create({ ...validated, userId: ctx.get("userId") });
return ctx.created({ post });
});
Step 4: Test 작성
// tests/api/posts.test.ts
import { describe, test, expect } from "bun:test";
describe("POST /api/posts", () => {
test("creates a post with valid input", async () => {
const res = await fetch("http://localhost:3333/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer test-token",
},
body: JSON.stringify({ title: "Test", content: "Hello" }),
});
expect(res.status).toBe(201);
const data = await res.json();
expect(data.post.title).toBe("Test");
});
test("returns 401 without auth", async () => {
const res = await fetch("http://localhost:3333/api/posts", {
method: "POST",
body: JSON.stringify({ title: "Test", content: "Hello" }),
});
expect(res.status).toBe(401);
});
});
CRUD Pattern
app/api/{resource}/route.ts GET (list), POST (create)
app/api/{resource}/[id]/route.ts GET (read), PATCH (update), DELETE (remove)
src/shared/contracts/{resource}.contract.ts
spec/slots/{resource}.slot.ts
tests/api/{resource}.test.ts
MCP Tools
| Tool | Purpose |
|---|---|
mandu_add_route | Create route file at correct path |
mandu_create_contract | Generate Zod contract schema |
mandu_negotiate | Analyze API requirements |
mandu_generate | Full scaffold generation |
Auth Patterns
// Bearer token guard
.guard(async (ctx) => {
const token = ctx.headers.get("authorization")?.replace("Bearer ", "");
if (!token) return ctx.error(401, "Missing token");
ctx.set("user", await verifyToken(token));
})
// API key guard
.guard((ctx) => {
const key = ctx.headers.get("x-api-key");
if (key !== process.env.API_KEY) return ctx.error(403, "Invalid API key");
})
Key Rules
- 모든 API route는
Mandu.filling()체인으로 export - 반드시 Contract를 먼저 정의하고 validation에 사용
.guard()로 인증/인가 처리 (인라인 체크 금지)- 에러는
ctx.error(status, message)사용 ctx.body()는 반드시 await
See also
mandu-mcp-create-flow— Track C (single route) 레시피로 이 워크플로우를 MCP 도구 순서로 정리mandu-mcp-verify— API 추가 직후ate.auto_pipeline+guard_check병렬 검증
Signals
- GitHub stars
- 46
- Forks
- 3
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
mandu-create-api- Source
- github.com/konamgil/mandu