Skip to main content

creating-api-routes

Scaffold new Next.js API route handlers with auth guards, Zod validation, error handling, and integration tests. Use when creating new API endpoints, adding CRUD operations, or building route handlers.

来源信息

仓库
TheSoftwareHouse/copilot-ai-usage-dashboard
最近来源活动
2026年3月15日 16:40
检测到的 SKILL.md 语言
英语
星标
13
分支
1

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
creating-api-routes
description
Scaffold new Next.js API route handlers with auth guards, Zod validation, error handling, and integration tests. Use when creating new API endpoints, adding CRUD operations, or building route handlers.
# Creating API Routes Scaffolds complete API route handlers following the project's request lifecycle pattern, including validation schemas and integration tests. ## Creation Process Use the checklist below and track your progress: ``` Progress: - [ ] Step 1: Create the validation schema - [ ] Step 2: Create the route handler - [ ] Step 3: Create the integration test - [ ] Step 4: Verify the route works ``` **Step 1: Create the validation schema** Create a Zod schema in `src/lib/validations/{entity}.ts`: ```ts import { z } from "zod"; import { nameFieldSchema } from "@/lib/validations/shared"; export const createTeamSchema = z.object({ name: nameFieldSchema, }); export type CreateTeamInput = z.infer<typeof createTeamSchema>; ``` - Check `src/lib/validations/shared.ts` for reusable field schemas before defining new ones - Naming: `create{Entity}Schema`, `update{Entity}Schema` - Always extract the TypeScript type: `export type {Action}{Entity}Input = z.infer<typeof schema>` **Step 2: Create the route handler** Create the route file at `src/app/api/{resource}/route.ts` (or `src/app/api/{resource}/[id]/route.ts` for single-resource routes). Every handler follows this exact skeleton: ```ts import { NextResponse } from "next/server"; import { requireAuth, isAuthFailure } from "@/lib/api-auth"; import { validateBody, isValidationError, handleRouteError } from "@/lib/api-helpers"; import { getDb } from "@/lib/db"; import { EntityNameEntity } from "@/entities/entity-name.entity"; import { createEntitySchema } from "@/lib/validations/entity-name"; export async function GET() { const auth = await requireAuth(); if (isAuthFailure(auth)) return auth; try { const dataSource = await getDb(); const repo = dataSource.getRepository(EntityNameEntity); const items = await repo.find(); return NextResponse.json(items); } catch (error) { return handleRouteError(error, "GET /api/resource"); } } export async function POST(request: Request) { const auth = await requireAuth(); if (isAuthFailure(auth)) return auth; const parsed = await validateBody(request, createEntitySchema); if (isValidationError(parsed)) return parsed; try { const dataSource = await getDb(); const repo = dataSource.getRepository(EntityNameEntity); const entity = repo.create(parsed.data); const saved = await repo.save(entity); return NextResponse.json(saved, { status: 201 }); } catch (error) { return handleRouteError(error, "POST /api/resource", { uniqueViolationMessage: "Resource with this name already exists", }); } } ``` For routes with URL params (`[id]`): ```ts type RouteContext = { params: Promise<{ id: string }> }; export async function GET(request: Request, context: RouteContext) { const auth = await requireAuth(); if (isAuthFailure(auth)) return auth; const { id } = await context.params; const entityId = parseEntityId(id); if (entityId === null) return invalidIdResponse("resource"); try { const dataSource = await getDb(); const repo = dataSource.getRepository(EntityNameEntity); const item = await repo.findOneBy({ id: entityId }); if (!item) throw new NotFoundError("Resource not found"); return NextResponse.json(item); } catch (error) { return handleRouteError(error, "GET /api/resource/:id"); } } ``` **Step 3: Create the integration test** Create the test file at `src/app/api/{resource}/__tests__/route.test.ts`: ```ts import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from "vitest"; import { DataSource } from "typeorm"; import { getTestDataSource, destroyTestDataSource, cleanDatabase } from "@/test/db-helpers"; import { EntityNameEntity } from "@/entities/entity-name.entity"; let testDs: DataSource; let mockCookieStore: Record<string, string> = {}; vi.mock("next/headers", () => ({ cookies: async () => ({ get: (name: string) => { const value = mockCookieStore[name]; return value !== undefined ? { value } : undefined; }, }), })); vi.mock("@/lib/db", () => ({ getDb: async () => testDs })); const { GET, POST } = await import("@/app/api/resource/route"); beforeAll(async () => { testDs = await getTestDataSource(); }); afterAll(async () => { await destroyTestDataSource(); }); beforeEach(async () => { await cleanDatabase(testDs); mockCookieStore = {}; }); async function seedAuthSession(): Promise<void> { const { hashPassword, createSession, SESSION_COOKIE_NAME } = await import("@/lib/auth"); const { UserEntity } = await import("@/entities/user.entity"); const userRepo = testDs.getRepository(UserEntity); const user = await userRepo.save({ username: "testadmin", passwordHash: await hashPassword("testpass"), }); const token = await createSession(user.id); mockCookieStore[SESSION_COOKIE_NAME] = token; } describe("GET /api/resource", () => { it("returns 401 without auth", async () => { const response = await GET(); expect(response.status).toBe(401); }); it("returns items", async () => { await seedAuthSession(); // seed test data... const response = await GET(); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveLength(1); }); }); describe("POST /api/resource", () => { it("creates a new item", async () => { await seedAuthSession(); const request = new Request("http://localhost/api/resource", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Test" }), }); const response = await POST(request); expect(response.status).toBe(201); }); }); ``` Key rules: - `vi.mock()` before `await import()` for correct mock resolution - Test both 401 (no auth) and happy path - Test validation errors (400) and unique violations (409) where applicable - Define local seed functions — not shared across test files **Step 4: Verify the route works** 1. Run the integration test: `npx vitest run src/app/api/{resource}/__tests__/route.test.ts` 2. Check types: `npx tsc --noEmit` 3. Check linting: `npm run lint` ## Connected Skills - `creating-entities` — create the entity before creating the API route - `writing-integration-tests` — detailed test patterns beyond the basics covered here - `writing-e2e-tests` — add E2E coverage for the new endpoint's UI
在 GitHub 查看