Back to catalog

nextjs-api-guidelines

This repository contains a complete **Cursor (Claude AI)** infrastructure that provides **auto-activating skills**, **specialized agents**, and **powerful commands** to help you build better applications faster.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

# Next.js API Routes Guidelines

**Skill Version:** 1.0.0  
**Stack:** Next.js 14+ App Router, TypeScript  
**Purpose:** Guidelines for building Next.js API routes and server actions

## Overview

This skill provides comprehensive guidelines for creating API routes and server actions in Next.js using the App Router.

## When to Use This Skill

Load this skill when working on:
- API route handlers
- Server actions
- Middleware
- Backend logic
- Database operations via API
- Authentication flows

## Core Guidelines

### 1. API Route Structure

#### Basic Route Handler
```typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  try {
    // Your logic here
    const users = []; // fetch from database
    
    return NextResponse.json({ data: users }, { status: 200 });
  } catch (error) {
    console.error('Error fetching users:', error);
    return NextResponse.json(
      { error: 'Failed to fetch users' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    
    // Validate input
    if (!body.name || !body.email) {
      return NextResponse.json(
        { error: 'Name and email are required' },
        { status: 400 }
      );
    }
    
    // Your logic here
    const newUser = {}; // create in database
    
    return NextResponse.json({ data: newUser }, { status: 201 });
  } catch (error) {
    console.error('Error creating user:', error);
    return NextResponse.json(
      { error: 'Failed to create user' },
      { status: 500 }
    );
  }
}
```

#### Dynamic Route Handler
```typescript
// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface RouteParams {
  params: { id: string };
}

export async function GET(
  request: NextRequest,
  { params }: RouteParams
) {
  try {
    const { id } = params;
    
    // Fetch user from database
    const user = {}; // await getUser(id);
    
    if (!user) {
      return NextResponse.json(
        { error: 'User not found' },
        { status: 404 }
      );
    }
    
    return NextResponse.json({ data: user });
  } catch (error) {
    console.error('Error fetching user:', error);
    return NextResponse.json(
      { error: 'Failed to fetch user' },
      { status: 500 }
    );
  }
}

export async function PUT(
  request: NextRequest,
  { params }: RouteParams
) {
  try {
    const { id } = params;
    const body = await request.json();
    
    // Update user in database
    const updatedUser = {}; // await updateUser(id, body);
    
    return NextResponse.json({ data: updatedUser });
  } catch (error) {
    console.error('Error updating user:', error);
    return NextResponse.json(
      { error: 'Failed to update user' },
      { status: 500 }
    );
  }
}

export async function DELETE(
  request: NextRequest,
  { params }: RouteParams
) {
  try {
    const { id } = params;
    
    // Delete user from database
    // await deleteUser(id);
    
    return NextResponse.json({ message: 'User deleted' });
  } catch (error) {
    console.error('Error deleting user:', error);
    return NextResponse.json(
      { error: 'Failed to delete user' },
      { status: 500 }
    );
  }
}
```

### 2. Server Actions

```typescript
// app/actions/users.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function createUser(formData: FormData) {
  try {
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;
    
    // Validate
    if (!name || !email) {
      return { error: 'Name and email are required' };
    }
    
    // Create user in database
    const user = {}; // await createUserInDb({ name, email });
    
    // Revalidate the users page
    revalidatePath('/users');
    
    return { success: true, data: user };
  } catch (error) {
    console.error('Error creating user:', error);
    return { error: 'Failed to create user' };
  }
}

export async function updateUser(id: string, formData: FormData) {
  try {
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;
    
    // Update user in database
    const user = {}; // await updateUserInDb(id, { name, email });
    
    revalidatePath('/users');
    revalidatePath(`/users/${id}`);
    
    return { success: true, data: user };
  } catch (error) {
    console.error('Error updating user:', error);
    return { error: 'Failed to update user' };
  }
}

export async function deleteUser(id: string) {
  try {
    // Delete user from database
    // await deleteUserFromDb(id);
    
    revalidatePath('/users');
    
    return { success: true };
  } catch (error) {
    console.error('Error deleting user:', error);
    return { error: 'Failed to delete user' };
  }
}
```

#### Using Server Actions in Components
```typescript
'use client';

import { useFormStatus } from 'react-dom';
import { createUser } from '@/app/actions/users';

function SubmitButton() {
  const { pending } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Creating...' : 'Create User'}
    </button>
  );
}

export function CreateUserForm() {
  return (
    <form action={createUser}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <SubmitButton />
    </form>
  );
}
```

### 3. Authentication & Authorization

```typescript
// lib/auth.ts
import { NextRequest } from 'next/server';
import { adminAuth } from '@/lib/firebase/admin';

export async function verifyAuth(request: NextRequest): Promise<string | null> {
  try {
    const token = request.headers.get('Authorization')?.split('Bearer ')[1];
    
    if (!token) {
      return null;
    }
    
    const decodedToken = await adminAuth.verifyIdToken(token);
    return decodedToken.uid;
  } catch (error) {
    console.error('Error verifying auth:', error);
    return null;
  }
}

// app/api/protected/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyAuth } from '@/lib/auth';

export async function GET(request: NextRequest) {
  const userId = await verifyAuth(request);
  
  if (!userId) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }
  
  // Your protected logic here
  return NextResponse.json({ data: 'Protected data', userId });
}
```

### 4. Error Handling

```typescript
// lib/api-error.ts
export class APIError extends Error {
  constructor(
    message: string,
    public statusCode: number = 500,
    public code?: string
  ) {
    super(message);
    this.name = 'APIError';
  }
}

// lib/api-handler.ts
import { NextRequest, NextResponse } from 'next/server';
import { APIError } from './api-error';

type Handler = (
  request: NextRequest,
  context?: any
) => Promise<NextResponse>;

export function withErrorHandler(handler: Handler): Handler {
  return async (request: NextRequest, context?: any) => {
    try {
      return await handler(request, context);
    } catch (error) {
      console.error('API Error:', error);
      
      if (error instanceof APIError) {
        return NextResponse.json(
          { error: error.message, code: error.code },
          { status: error.statusCode }
        );
      }
      
      return NextResponse.json(
        { error: 'Internal server error' },
        { status: 500 }
      );
    }
  };
}

// Usage
// app/api/example/route.ts
import { withErrorHandler } from '@/lib/api-handler';
import { APIError } from '@/lib/api-error';

export const GET = withErrorHandler(async (request) => {
  // Your logic
  throw new APIError('Not found', 404, 'USER_NOT_FOUND');
});
```

### 5. Request Validation

```typescript
// lib/validation.ts
import { z } from 'zod';
import { NextRequest } from 'next/server';

export async function validateRequest<T>(
  request: NextRequest,
  schema: z.ZodSchema<T>
): Promise<T> {
  const body = await request.json();
  return schema.parse(body);
}

// app/api/users/route.ts
import { z } from 'zod';
import { validateRequest } from '@/lib/validation';
import { APIError } from '@/lib/api-error';

const createUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().min(18).optional(),
});

export async function POST(request: NextRequest) {
  try {
    const data = await validateRequest(request, createUserSchema);
    
    // Create user with validated data
    const user = {}; // await createUser(data);
    
    return NextResponse.json({ data: user }, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Validation failed', details: error.errors },
        { status: 400 }
      );
    }
    throw error;
  }
}
```

### 6. CORS Configuration

```typescript
// lib/cors.ts
import { NextResponse } from 'next/server';

export function corsHeaders() {
  return {
    'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGIN || '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  };
}

// app/api/users/route.ts
import { corsHeaders } from '@/lib/cors';

export async function OPTIONS() {
  return NextResponse.json({}, { headers: corsHeaders() });
}

export async function GET(request: NextRequest) {
  const data = {}; // your logic
  
  return NextResponse.json(
    { data },
    { headers: corsHeaders() }
  );
}
```

### 7. Rate Limiting

```typescript
// lib/rate-limit.ts
import { NextRequest } from 'next/server';

const rateLimitMap = new Map<string, { count: number; resetTime: number }>();

export function checkRateLimit(
  request: NextRequest,
  maxRequests: number = 10,
  windowMs: number = 60000
): boolean {
  const ip = request.ip || 'unknown';
  const now = Date.now();
  
  const record = rateLimitMap.get(ip);
  
  if (!record || now > record.resetTime) {
    rateLimitMap.set(ip, { count: 1, resetTime: now + windowMs });
    return true;
  }
  
  if (record.count >= maxRequests) {
    return false;
  }
  
  record.count++;
  return true;
}

// app/api/limited/route.ts
import { checkRateLimit } from '@/lib/rate-limit';

export async function GET(request: NextRequest) {
  if (!checkRateLimit(request, 10, 60000)) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429 }
    );
  }
  
  // Your logic
  return NextResponse.json({ data: 'success' });
}
```

## Best Practices

### 1. Response Format
Always use consistent response format:
```typescript
// Success
{ data: any, message?: string }

// Error
{ error: string, code?: string, details?: any }
```

### 2. Status Codes
- 200: Success (GET, PUT, PATCH)
- 201: Created (POST)
- 204: No Content (DELETE)
- 400: Bad Request
- 401: Unauthorized
- 403: Forbidden
- 404: Not Found
- 429: Too Many Requests
- 500: Internal Server Error

### 3. Security
- ✅ Always validate input
- ✅ Use environment variables for secrets
- ✅ Implement authentication for protected routes
- ✅ Use HTTPS in production
- ✅ Sanitize user input
- ✅ Implement rate limiting

### 4. Performance
- ✅ Use database indexes
- ✅ Implement caching where appropriate
- ✅ Use connection pooling
- ✅ Optimize database queries
- ✅ Enable compression

---

**Last Updated:** 2025-01-10  
**Maintained By:** Development Team