api-endpoint Skill

Create production-ready RESTful API endpoints with validation, error handling, and proper responses.

Overview#

The api-endpoint skill generates complete API endpoint code following REST best practices.

Usage#

Use the api-endpoint skill to create a POST /api/posts endpoint for creating blog posts.

Parameters#

ParameterTypeRequiredDescription
methodstringYesHTTP method: GET, POST, PUT, PATCH, DELETE
pathstringYesEndpoint path
resourcestringNoResource name (inferred from path)
fieldsarrayNoFields for request body
authbooleanNoRequire authentication (default: true)

Generated Output#

GET Endpoint (List)#

1// app/api/posts/route.ts 2import { NextResponse } from 'next/server'; 3import { prisma } from '@/lib/prisma'; 4import { auth } from '@/lib/auth'; 5 6export async function GET(request: Request) { 7 try { 8 // Authentication 9 const session = await auth(); 10 if (!session) { 11 return NextResponse.json( 12 { error: 'Unauthorized' }, 13 { status: 401 } 14 ); 15 } 16 17 // Parse query parameters 18 const { searchParams } = new URL(request.url); 19 const page = parseInt(searchParams.get('page') ?? '1'); 20 const perPage = parseInt(searchParams.get('perPage') ?? '20'); 21 const search = searchParams.get('search'); 22 23 // Build query 24 const where = search 25 ? { title: { contains: search, mode: 'insensitive' } } 26 : {}; 27 28 // Fetch data with pagination 29 const [posts, total] = await Promise.all([ 30 prisma.post.findMany({ 31 where, 32 skip: (page - 1) * perPage, 33 take: perPage, 34 orderBy: { createdAt: 'desc' }, 35 }), 36 prisma.post.count({ where }), 37 ]); 38 39 return NextResponse.json({ 40 data: posts, 41 meta: { 42 page, 43 perPage, 44 total, 45 totalPages: Math.ceil(total / perPage), 46 }, 47 }); 48 } catch (error) { 49 console.error('Failed to fetch posts:', error); 50 return NextResponse.json( 51 { error: 'Failed to fetch posts' }, 52 { status: 500 } 53 ); 54 } 55}

POST Endpoint (Create)#

1// app/api/posts/route.ts 2import { NextResponse } from 'next/server'; 3import { z } from 'zod'; 4import { prisma } from '@/lib/prisma'; 5import { auth } from '@/lib/auth'; 6 7const createPostSchema = z.object({ 8 title: z.string().min(1).max(200), 9 content: z.string().min(1), 10 published: z.boolean().default(false), 11}); 12 13export async function POST(request: Request) { 14 try { 15 // Authentication 16 const session = await auth(); 17 if (!session) { 18 return NextResponse.json( 19 { error: 'Unauthorized' }, 20 { status: 401 } 21 ); 22 } 23 24 // Parse and validate body 25 const body = await request.json(); 26 const validated = createPostSchema.parse(body); 27 28 // Create resource 29 const post = await prisma.post.create({ 30 data: { 31 ...validated, 32 authorId: session.user.id, 33 }, 34 }); 35 36 return NextResponse.json( 37 { data: post }, 38 { status: 201 } 39 ); 40 } catch (error) { 41 if (error instanceof z.ZodError) { 42 return NextResponse.json( 43 { 44 error: 'Validation failed', 45 details: error.errors.map(e => ({ 46 field: e.path.join('.'), 47 message: e.message, 48 })), 49 }, 50 { status: 400 } 51 ); 52 } 53 54 console.error('Failed to create post:', error); 55 return NextResponse.json( 56 { error: 'Failed to create post' }, 57 { status: 500 } 58 ); 59 } 60}

GET Endpoint (Single)#

1// app/api/posts/[id]/route.ts 2import { NextResponse } from 'next/server'; 3import { prisma } from '@/lib/prisma'; 4import { auth } from '@/lib/auth'; 5 6export async function GET( 7 request: Request, 8 { params }: { params: { id: string } } 9) { 10 try { 11 const session = await auth(); 12 if (!session) { 13 return NextResponse.json( 14 { error: 'Unauthorized' }, 15 { status: 401 } 16 ); 17 } 18 19 const post = await prisma.post.findUnique({ 20 where: { id: params.id }, 21 include: { author: true }, 22 }); 23 24 if (!post) { 25 return NextResponse.json( 26 { error: 'Post not found' }, 27 { status: 404 } 28 ); 29 } 30 31 return NextResponse.json({ data: post }); 32 } catch (error) { 33 console.error('Failed to fetch post:', error); 34 return NextResponse.json( 35 { error: 'Failed to fetch post' }, 36 { status: 500 } 37 ); 38 } 39}

PUT/PATCH Endpoint (Update)#

1// app/api/posts/[id]/route.ts 2const updatePostSchema = z.object({ 3 title: z.string().min(1).max(200).optional(), 4 content: z.string().min(1).optional(), 5 published: z.boolean().optional(), 6}); 7 8export async function PATCH( 9 request: Request, 10 { params }: { params: { id: string } } 11) { 12 try { 13 const session = await auth(); 14 if (!session) { 15 return NextResponse.json( 16 { error: 'Unauthorized' }, 17 { status: 401 } 18 ); 19 } 20 21 // Check ownership 22 const existing = await prisma.post.findUnique({ 23 where: { id: params.id }, 24 }); 25 26 if (!existing) { 27 return NextResponse.json( 28 { error: 'Post not found' }, 29 { status: 404 } 30 ); 31 } 32 33 if (existing.authorId !== session.user.id) { 34 return NextResponse.json( 35 { error: 'Forbidden' }, 36 { status: 403 } 37 ); 38 } 39 40 // Validate and update 41 const body = await request.json(); 42 const validated = updatePostSchema.parse(body); 43 44 const post = await prisma.post.update({ 45 where: { id: params.id }, 46 data: validated, 47 }); 48 49 return NextResponse.json({ data: post }); 50 } catch (error) { 51 if (error instanceof z.ZodError) { 52 return NextResponse.json( 53 { error: 'Validation failed', details: error.errors }, 54 { status: 400 } 55 ); 56 } 57 58 console.error('Failed to update post:', error); 59 return NextResponse.json( 60 { error: 'Failed to update post' }, 61 { status: 500 } 62 ); 63 } 64}

DELETE Endpoint#

1export async function DELETE( 2 request: Request, 3 { params }: { params: { id: string } } 4) { 5 try { 6 const session = await auth(); 7 if (!session) { 8 return NextResponse.json( 9 { error: 'Unauthorized' }, 10 { status: 401 } 11 ); 12 } 13 14 const existing = await prisma.post.findUnique({ 15 where: { id: params.id }, 16 }); 17 18 if (!existing) { 19 return NextResponse.json( 20 { error: 'Post not found' }, 21 { status: 404 } 22 ); 23 } 24 25 if (existing.authorId !== session.user.id) { 26 return NextResponse.json( 27 { error: 'Forbidden' }, 28 { status: 403 } 29 ); 30 } 31 32 await prisma.post.delete({ 33 where: { id: params.id }, 34 }); 35 36 return new NextResponse(null, { status: 204 }); 37 } catch (error) { 38 console.error('Failed to delete post:', error); 39 return NextResponse.json( 40 { error: 'Failed to delete post' }, 41 { status: 500 } 42 ); 43 } 44}

Features Included#

  • Authentication check
  • Input validation with Zod
  • Proper HTTP status codes
  • Pagination support
  • Error handling
  • Type safety

Customization Options#

Use the api-endpoint skill with: - method: "POST" - path: "/api/posts" - auth: false (for public endpoints) - fields: [ { name: "title", type: "string", required: true }, { name: "content", type: "string", required: true } ]