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#
| Parameter | Type | Required | Description |
|---|---|---|---|
method | string | Yes | HTTP method: GET, POST, PUT, PATCH, DELETE |
path | string | Yes | Endpoint path |
resource | string | No | Resource name (inferred from path) |
fields | array | No | Fields for request body |
auth | boolean | No | Require 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 }
]
Related Skills#
- validation - Advanced validation patterns
- error-handling - Error management
- prisma-crud - Database operations