# API Design Patterns für 2026

**Meta-Description:** Moderne API Design Patterns. REST Best Practices, GraphQL vs tRPC, API Versioning und Error Handling Standards.

**Keywords:** API Design, REST API, GraphQL, tRPC, API Versioning, Error Handling, OpenAPI, API Security

---

## Einführung

Gutes API Design entscheidet über Developer Experience und Wartbarkeit. 2026 stehen mehrere Paradigmen zur Verfügung: **REST, GraphQL, tRPC** – jedes mit eigenen Stärken für verschiedene Use Cases.

---

## API Paradigmen im Vergleich

```
┌─────────────────────────────────────────────────────────────┐
│              API PARADIGMEN 2026                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  REST                                                       │
│  ├── Resource-orientiert                                   │
│  ├── HTTP Verben (GET, POST, PUT, DELETE)                  │
│  ├── Stateless                                             │
│  ├── Caching-freundlich                                    │
│  └── Best for: Public APIs, Microservices                  │
│                                                             │
│  GraphQL                                                    │
│  ├── Schema-first                                          │
│  ├── Single Endpoint                                       │
│  ├── Client-driven Queries                                 │
│  ├── Subscriptions (Real-time)                             │
│  └── Best for: Complex Data, Mobile Apps                   │
│                                                             │
│  tRPC                                                       │
│  ├── End-to-End Type Safety                                │
│  ├── No Code Generation                                    │
│  ├── Zod Validation                                        │
│  ├── React Query Integration                               │
│  └── Best for: TypeScript Monorepos                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## REST Best Practices

```typescript
// 1. Resource Naming (Plural Nouns)
// ✅ Good
GET    /users
GET    /users/:id
POST   /users
PUT    /users/:id
DELETE /users/:id

// ❌ Bad
GET    /getUser
POST   /createUser
GET    /user-list

// 2. Nested Resources
GET    /users/:userId/posts
GET    /users/:userId/posts/:postId
POST   /users/:userId/posts

// 3. Query Parameters für Filtering/Sorting
GET    /posts?status=published&author=123&sort=-createdAt&limit=10&offset=20

// 4. HTTP Status Codes
// 200 OK - Success
// 201 Created - Resource created
// 204 No Content - Success, no body (DELETE)
// 400 Bad Request - Validation error
// 401 Unauthorized - Authentication required
// 403 Forbidden - Permission denied
// 404 Not Found - Resource not found
// 409 Conflict - Duplicate/Conflict
// 422 Unprocessable Entity - Semantic error
// 429 Too Many Requests - Rate limited
// 500 Internal Server Error - Server error
```

```typescript
// Next.js App Router REST API
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2)
});

// GET /api/users
export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const limit = parseInt(searchParams.get('limit') || '10');
  const offset = parseInt(searchParams.get('offset') || '0');

  const users = await db.user.findMany({
    take: limit,
    skip: offset
  });

  const total = await db.user.count();

  return NextResponse.json({
    data: users,
    pagination: {
      total,
      limit,
      offset,
      hasMore: offset + limit < total
    }
  });
}

// POST /api/users
export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const data = createUserSchema.parse(body);

    const user = await db.user.create({ data });

    return NextResponse.json(
      { data: user },
      { status: 201 }
    );
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        {
          error: 'Validation Error',
          details: error.errors
        },
        { status: 400 }
      );
    }
    throw error;
  }
}
```

---

## Error Response Standard

```typescript
// RFC 7807 Problem Details
interface ProblemDetails {
  type: string;        // URI Reference für Error Type
  title: string;       // Kurze Beschreibung
  status: number;      // HTTP Status Code
  detail?: string;     // Ausführliche Beschreibung
  instance?: string;   // URI der fehlerhaften Ressource
  [key: string]: unknown;  // Erweiterungen
}

// Beispiel Implementation
function createErrorResponse(
  status: number,
  title: string,
  detail?: string,
  extras?: Record<string, unknown>
): NextResponse {
  const body: ProblemDetails = {
    type: `https://api.example.com/errors/${status}`,
    title,
    status,
    detail,
    instance: `/api/request-id/${crypto.randomUUID()}`,
    timestamp: new Date().toISOString(),
    ...extras
  };

  return NextResponse.json(body, {
    status,
    headers: {
      'Content-Type': 'application/problem+json'
    }
  });
}

// Verwendung
if (!user) {
  return createErrorResponse(
    404,
    'User Not Found',
    `User with ID ${id} does not exist`
  );
}

// Validation Error mit Details
return createErrorResponse(
  400,
  'Validation Error',
  'The request body contains invalid data',
  {
    errors: [
      { field: 'email', message: 'Invalid email format' },
      { field: 'name', message: 'Name is required' }
    ]
  }
);
```

---

## API Versioning Strategies

```typescript
// 1. URL Versioning (Empfohlen für Breaking Changes)
// /api/v1/users
// /api/v2/users

// app/api/v1/users/route.ts
export async function GET() {
  // V1 Response Format
  return NextResponse.json({ users: [...] });
}

// app/api/v2/users/route.ts
export async function GET() {
  // V2 Response Format (z.B. neue Felder)
  return NextResponse.json({
    data: [...],
    meta: { version: 'v2' }
  });
}

// 2. Header Versioning
// Accept: application/vnd.api+json;version=2
export async function GET(request: NextRequest) {
  const accept = request.headers.get('Accept') || '';
  const version = accept.match(/version=(\d+)/)?.[1] || '1';

  if (version === '2') {
    return handleV2(request);
  }
  return handleV1(request);
}

// 3. Query Parameter (für Clients ohne Header-Kontrolle)
// /api/users?version=2
export async function GET(request: NextRequest) {
  const version = request.nextUrl.searchParams.get('version') || '1';
  // ...
}
```

---

## Rate Limiting

```typescript
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10s'), // 10 Requests pro 10s
  analytics: true
});

// Middleware
export async function middleware(request: NextRequest) {
  const ip = request.ip ?? '127.0.0.1';
  const { success, limit, reset, remaining } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      {
        error: 'Too Many Requests',
        retryAfter: Math.ceil((reset - Date.now()) / 1000)
      },
      {
        status: 429,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
          'X-RateLimit-Reset': reset.toString(),
          'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString()
        }
      }
    );
  }

  const response = NextResponse.next();
  response.headers.set('X-RateLimit-Limit', limit.toString());
  response.headers.set('X-RateLimit-Remaining', remaining.toString());
  return response;
}
```

---

## Pagination Patterns

```typescript
// 1. Offset Pagination (Einfach, aber langsam bei großen Datasets)
interface OffsetPagination {
  data: User[];
  pagination: {
    total: number;
    limit: number;
    offset: number;
    hasMore: boolean;
  };
}

// 2. Cursor Pagination (Empfohlen für große Datasets)
interface CursorPagination<T> {
  data: T[];
  pagination: {
    cursor: string | null;
    hasMore: boolean;
  };
}

async function getUsersWithCursor(cursor?: string, limit = 20) {
  const users = await db.user.findMany({
    take: limit + 1, // +1 um hasMore zu prüfen
    cursor: cursor ? { id: cursor } : undefined,
    orderBy: { createdAt: 'desc' }
  });

  const hasMore = users.length > limit;
  const data = hasMore ? users.slice(0, -1) : users;
  const nextCursor = hasMore ? data[data.length - 1].id : null;

  return {
    data,
    pagination: {
      cursor: nextCursor,
      hasMore
    }
  };
}

// 3. Keyset Pagination (Für sortierte Daten)
// /api/posts?after=2024-01-15T10:00:00Z&limit=20
```

---

## OpenAPI Specification

```typescript
// Mit Zod + zod-to-openapi
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';

const app = new OpenAPIHono();

const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string()
}).openapi('User');

const route = createRoute({
  method: 'get',
  path: '/users/{id}',
  request: {
    params: z.object({
      id: z.string().uuid()
    })
  },
  responses: {
    200: {
      content: {
        'application/json': {
          schema: UserSchema
        }
      },
      description: 'User found'
    },
    404: {
      content: {
        'application/json': {
          schema: z.object({
            error: z.string()
          })
        }
      },
      description: 'User not found'
    }
  }
});

app.openapi(route, (c) => {
  const { id } = c.req.valid('param');
  // ...
});

// OpenAPI Spec generieren
app.doc('/doc', {
  openapi: '3.1.0',
  info: {
    title: 'My API',
    version: '1.0.0'
  }
});
```

---

## API Security Checklist

```typescript
// 1. Authentication
// - JWT mit kurzer Expiration
// - Refresh Token Rotation
// - Secure Cookie Storage

// 2. Authorization
// - RBAC oder ABAC
// - Resource-level Permissions
// - Rate Limiting per User

// 3. Input Validation
// - Zod für alle Inputs
// - Sanitize User Input
// - File Upload Restrictions

// 4. Headers
const securityHeaders = {
  'Content-Security-Policy': "default-src 'self'",
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'DENY',
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
  'X-XSS-Protection': '1; mode=block'
};

// 5. CORS
const corsConfig = {
  origin: ['https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
  maxAge: 86400
};
```

---

## Fazit

Gutes API Design 2026 bedeutet:

1. **Konsistenz**: Einheitliche Naming, Responses, Errors
2. **Type Safety**: Zod/OpenAPI für Contracts
3. **Performance**: Pagination, Caching, Rate Limiting
4. **Security**: Validation, Auth, Headers

Die Wahl zwischen REST/GraphQL/tRPC hängt vom Use Case ab.

---

## Bildprompts

1. "API endpoints connecting client and server, clean architecture diagram"
2. "REST vs GraphQL vs tRPC comparison, three paths to data"
3. "Security shield protecting API gateway, authentication visualization"

---

## Quellen

- [REST API Design Best Practices](https://restfulapi.net/)
- [RFC 7807 - Problem Details](https://datatracker.ietf.org/doc/html/rfc7807)
- [OpenAPI Specification](https://spec.openapis.org/oas/latest.html)
- [tRPC Documentation](https://trpc.io/)
