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
آخر نشاط في المصدر
١٥ مارس ٢٠٢٦ في ١٦:٤٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٣
التفرعات
١

خيارات التثبيت

يُحدَّد 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