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.

Quellinformationen

Repository
TheSoftwareHouse/copilot-ai-usage-dashboard
Letzte Quellaktivität
15. März 2026 um 16:40
Erkannte Sprache von SKILL.md
Englisch
Sterne
13
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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
Auf GitHub ansehen