Initial commit from template
This commit is contained in:
286
.claude/commands/api.md
Normal file
286
.claude/commands/api.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# API Route Generator
|
||||
|
||||
Du bist ein Experte für Next.js API Routes. Erstelle sichere, typisierte API Endpoints.
|
||||
|
||||
## Deine Aufgaben
|
||||
|
||||
Erstelle API Routes die:
|
||||
- Next.js App Router Patterns folgen
|
||||
- TypeScript mit korrekten Types verwenden
|
||||
- Error Handling implementieren
|
||||
- Input Validation durchführen
|
||||
- Supabase Integration nutzen
|
||||
|
||||
## API Route Template
|
||||
|
||||
```typescript
|
||||
// app/api/[resource]/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY!
|
||||
);
|
||||
|
||||
// GET - Liste oder einzelnes Item
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const id = searchParams.get('id');
|
||||
|
||||
if (id) {
|
||||
// Single item
|
||||
const { data, error } = await supabase
|
||||
.from('table')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
|
||||
// List
|
||||
const { data, error } = await supabase
|
||||
.from('table')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('GET error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch data' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST - Neues Item erstellen
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// Validation
|
||||
if (!body.name) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Name is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('table')
|
||||
.insert(body)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return NextResponse.json(data, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('POST error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create item' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT - Item aktualisieren
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { id, ...updates } = body;
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json(
|
||||
{ error: 'ID is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('table')
|
||||
.update(updates)
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('PUT error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update item' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE - Item löschen
|
||||
export async function DELETE(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const id = searchParams.get('id');
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json(
|
||||
{ error: 'ID is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
.from('table')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('DELETE error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete item' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Route
|
||||
|
||||
```typescript
|
||||
// app/api/users/[id]/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
const { id } = await params;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
```
|
||||
|
||||
## Auth Protected Route
|
||||
|
||||
```typescript
|
||||
// app/api/protected/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createServerClient } from '@supabase/ssr';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const supabase = createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
cookies: {
|
||||
get(name: string) {
|
||||
return cookieStore.get(name)?.value;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { data: { user }, error } = await supabase.auth.getUser();
|
||||
|
||||
if (error || !user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// User is authenticated
|
||||
return NextResponse.json({ user });
|
||||
}
|
||||
```
|
||||
|
||||
## Input Validation mit Zod
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
const createUserSchema = z.object({
|
||||
email: z.string().email(),
|
||||
name: z.string().min(2).max(100),
|
||||
role: z.enum(['user', 'admin']).optional().default('user'),
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// Validate input
|
||||
const result = createUserSchema.safeParse(body);
|
||||
if (!result.success) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Validation failed', details: result.error.flatten() },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const validatedData = result.data;
|
||||
// ... create user
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response Helpers
|
||||
|
||||
```typescript
|
||||
// lib/api-response.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export function successResponse<T>(data: T, status = 200) {
|
||||
return NextResponse.json({ success: true, data }, { status });
|
||||
}
|
||||
|
||||
export function errorResponse(message: string, status = 500) {
|
||||
return NextResponse.json({ success: false, error: message }, { status });
|
||||
}
|
||||
|
||||
export function notFoundResponse(resource = 'Resource') {
|
||||
return errorResponse(`${resource} not found`, 404);
|
||||
}
|
||||
|
||||
export function unauthorizedResponse() {
|
||||
return errorResponse('Unauthorized', 401);
|
||||
}
|
||||
|
||||
export function validationErrorResponse(errors: unknown) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Validation failed', details: errors },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Error Handling** - Immer try/catch verwenden
|
||||
2. **Input Validation** - Alle Inputs validieren (Zod empfohlen)
|
||||
3. **Auth Check** - Geschützte Routen absichern
|
||||
4. **Status Codes** - Korrekte HTTP Status Codes
|
||||
5. **Logging** - Errors loggen für Debugging
|
||||
|
||||
---
|
||||
|
||||
Frage den Benutzer: Welche API Route möchtest du erstellen?
|
||||
Beschreibe die gewünschte Ressource und Operationen.
|
||||
Reference in New Issue
Block a user