원클릭으로
api-route
Generate a new Next.js API route with project boilerplate (auth, validation, sanitize, Prisma). Use when creating new API endpoints.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Generate a new Next.js API route with project boilerplate (auth, validation, sanitize, Prisma). Use when creating new API endpoints.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Launch, build, run, start, screenshot, or smoke-test the Librariarr Next.js webapp end-to-end. Use when asked to run librariarr, bring up the dev stack, take a screenshot of the dashboard or any UI page, verify a change in the running app, or check that the app boots cleanly.
Generate an integration test for an API route with all required mocks and boilerplate. Use when creating tests for API endpoints.
Quick reference for all Librariarr project conventions and patterns. Consult when writing or reviewing code to verify correct patterns.
Create a Prisma migration file for schema changes. Required for production — db push only works in dev. Use after modifying schema.prisma.
Scaffold a complete new feature end-to-end (Prisma model, migration, schemas, API routes, tests). Use for new CRUD resources or features that span multiple layers.
Add a new Zod validation schema to src/lib/validation.ts. All schemas MUST live in this file using zod/v4. Use when creating validation for new API endpoints.
| name | api-route |
| description | Generate a new Next.js API route with project boilerplate (auth, validation, sanitize, Prisma). Use when creating new API endpoints. |
| argument-hint | <route-path> [HTTP-methods] [prisma-model] |
Generate a new API route at src/app/api/$ARGUMENTS.
import { NextRequest, NextResponse } from "next/server";
import { getSession } from "@/lib/auth/session";
import { prisma } from "@/lib/db"; // NEVER @/lib/prisma
import { validateRequest, schemaName } from "@/lib/validation"; // schemas ONLY in this file
import { sanitize, sanitizeErrorDetail } from "@/lib/api/sanitize";
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { data, error } = await validateRequest(request, schemaName);
if (error) return error;
The Zod schema MUST be defined in src/lib/validation.ts using zod/v4, NOT inline in the route file.
sanitize()sanitizeErrorDetail()fileSize fields must be serialized: fileSize: item.fileSize?.toString() ?? nullwhere: { userId: session.userId! }where: { library: { mediaServer: { userId: session.userId } } }findFirst with userId filter, NOT bare findUnique({ where: { id } })export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
// ...
}
// For external service failures:
return NextResponse.json(
{ error: "Failed to connect to Service", detail: sanitizeErrorDetail(result.error) },
{ status: 400 }
);
// For not found (always verify ownership):
const existing = await prisma.model.findFirst({
where: { id, userId: session.userId! },
});
if (!existing) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
route.ts — no dynamic segment):export async function GET() {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const items = await prisma.model.findMany({
where: { userId: session.userId! },
orderBy: { createdAt: "desc" },
});
return NextResponse.json({ items: sanitize(items) });
}
export async function POST(request: NextRequest) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { data, error } = await validateRequest(request, createSchema);
if (error) return error;
const item = await prisma.model.create({
data: { userId: session.userId!, ...data },
});
return NextResponse.json({ item: sanitize(item) }, { status: 201 });
}
[id]/route.ts):export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const { data, error } = await validateRequest(request, updateSchema);
if (error) return error;
const existing = await prisma.model.findFirst({
where: { id, userId: session.userId! },
});
if (!existing) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const updated = await prisma.model.update({ where: { id }, data });
return NextResponse.json({ item: sanitize(updated) });
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const existing = await prisma.model.findFirst({
where: { id, userId: session.userId! },
});
if (!existing) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
await prisma.model.delete({ where: { id } });
return NextResponse.json({ success: true });
}
export async function GET(request: NextRequest) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") ?? "1");
const rawLimit = parseInt(searchParams.get("limit") ?? "50");
const limit = rawLimit === 0 ? 0 : Math.min(rawLimit, 100);
const where = { userId: session.userId! };
const items = await prisma.model.findMany({
where,
...(limit > 0 ? { skip: (page - 1) * limit, take: limit + 1 } : {}),
orderBy: { createdAt: "desc" },
});
const hasMore = limit > 0 && items.length > limit;
if (hasMore) items.pop();
return NextResponse.json({
items: sanitize(items),
pagination: { page, limit, hasMore },
});
}
import { resolveServerFilter } from "@/lib/dedup/server-filter";
// Inside handler:
const sf = await resolveServerFilter(session.userId!, serverId, "MOVIE");
if (!sf) {
return NextResponse.json({ items: [], pagination: { page, limit, hasMore: false } });
}
const where: Prisma.MediaItemWhereInput = {
library: { mediaServerId: { in: sf.serverIds } },
type: "MOVIE",
};
if (!sf.isSingleServer) where.dedupCanonical = true;
src/lib/validation.ts using zod/v4src/app/api/<route-path>/route.ts using the patterns above[id], use the Next.js 16 async params patternpnpm lint to verify no lint errors