- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
617 lines
17 KiB
Markdown
617 lines
17 KiB
Markdown
# API Design & Documentation
|
|
|
|
**Meta-Description:** REST API Design Best Practices. OpenAPI Specification, Dokumentation mit Swagger und Developer Experience optimieren.
|
|
|
|
**Keywords:** API Design, REST API, OpenAPI, Swagger, API Documentation, Developer Experience, API Best Practices
|
|
|
|
---
|
|
|
|
## Einführung
|
|
|
|
**Gutes API Design** ist entscheidend für Developer Experience. Mit **OpenAPI/Swagger** können APIs dokumentiert, getestet und generiert werden. Dieser Guide zeigt Best Practices für konsistente, intuitive APIs.
|
|
|
|
---
|
|
|
|
## API Design Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ REST API DESIGN PRINCIPLES │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ URL Structure: │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ Resources (Nouns, plural): │ │
|
|
│ │ ├── GET /users List users │ │
|
|
│ │ ├── POST /users Create user │ │
|
|
│ │ ├── GET /users/{id} Get user │ │
|
|
│ │ ├── PUT /users/{id} Update user │ │
|
|
│ │ ├── PATCH /users/{id} Partial update │ │
|
|
│ │ └── DELETE /users/{id} Delete user │ │
|
|
│ │ │ │
|
|
│ │ Nested Resources: │ │
|
|
│ │ ├── GET /users/{id}/projects │ │
|
|
│ │ └── POST /users/{id}/projects │ │
|
|
│ │ │ │
|
|
│ │ Query Parameters: │ │
|
|
│ │ ├── ?page=1&limit=20 Pagination │ │
|
|
│ │ ├── ?sort=createdAt:desc Sorting │ │
|
|
│ │ ├── ?filter[status]=active Filtering │ │
|
|
│ │ └── ?fields=id,name,email Field selection │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ HTTP Status Codes: │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ 2xx Success: │ │
|
|
│ │ ├── 200 OK Successful request │ │
|
|
│ │ ├── 201 Created Resource created │ │
|
|
│ │ └── 204 No Content Successful, no body │ │
|
|
│ │ │ │
|
|
│ │ 4xx Client Errors: │ │
|
|
│ │ ├── 400 Bad Request Invalid input │ │
|
|
│ │ ├── 401 Unauthorized Not authenticated │ │
|
|
│ │ ├── 403 Forbidden Not authorized │ │
|
|
│ │ ├── 404 Not Found Resource not found │ │
|
|
│ │ └── 422 Unprocessable Validation failed │ │
|
|
│ │ │ │
|
|
│ │ 5xx Server Errors: │ │
|
|
│ │ ├── 500 Internal Error Server error │ │
|
|
│ │ └── 503 Service Unavailable Temporarily down │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## OpenAPI Specification
|
|
|
|
```yaml
|
|
# openapi/api.yaml
|
|
openapi: 3.1.0
|
|
info:
|
|
title: My SaaS API
|
|
description: API for managing projects and tasks
|
|
version: 1.0.0
|
|
contact:
|
|
name: API Support
|
|
email: api@example.com
|
|
url: https://example.com/support
|
|
license:
|
|
name: MIT
|
|
url: https://opensource.org/licenses/MIT
|
|
|
|
servers:
|
|
- url: https://api.example.com/v1
|
|
description: Production
|
|
- url: https://api.staging.example.com/v1
|
|
description: Staging
|
|
- url: http://localhost:3000/api/v1
|
|
description: Local development
|
|
|
|
tags:
|
|
- name: Projects
|
|
description: Project management endpoints
|
|
- name: Tasks
|
|
description: Task management endpoints
|
|
- name: Users
|
|
description: User management endpoints
|
|
|
|
paths:
|
|
/projects:
|
|
get:
|
|
tags: [Projects]
|
|
summary: List all projects
|
|
description: Returns a paginated list of projects for the authenticated user
|
|
operationId: listProjects
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/PageParam'
|
|
- $ref: '#/components/parameters/LimitParam'
|
|
- name: status
|
|
in: query
|
|
description: Filter by status
|
|
schema:
|
|
type: string
|
|
enum: [active, archived, draft]
|
|
- name: sort
|
|
in: query
|
|
description: Sort field and direction
|
|
schema:
|
|
type: string
|
|
example: createdAt:desc
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ProjectListResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'500':
|
|
$ref: '#/components/responses/InternalError'
|
|
|
|
post:
|
|
tags: [Projects]
|
|
summary: Create a project
|
|
description: Creates a new project
|
|
operationId: createProject
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateProjectInput'
|
|
responses:
|
|
'201':
|
|
description: Project created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Project'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
|
|
/projects/{projectId}:
|
|
get:
|
|
tags: [Projects]
|
|
summary: Get a project
|
|
operationId: getProject
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectIdParam'
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Project'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
patch:
|
|
tags: [Projects]
|
|
summary: Update a project
|
|
operationId: updateProject
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectIdParam'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateProjectInput'
|
|
responses:
|
|
'200':
|
|
description: Project updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Project'
|
|
|
|
delete:
|
|
tags: [Projects]
|
|
summary: Delete a project
|
|
operationId: deleteProject
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectIdParam'
|
|
responses:
|
|
'204':
|
|
description: Project deleted
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
|
|
parameters:
|
|
ProjectIdParam:
|
|
name: projectId
|
|
in: path
|
|
required: true
|
|
description: Project ID
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
|
|
PageParam:
|
|
name: page
|
|
in: query
|
|
description: Page number
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
default: 1
|
|
|
|
LimitParam:
|
|
name: limit
|
|
in: query
|
|
description: Items per page
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 20
|
|
|
|
schemas:
|
|
Project:
|
|
type: object
|
|
required: [id, name, status, createdAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
example: '123e4567-e89b-12d3-a456-426614174000'
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
example: 'My Project'
|
|
description:
|
|
type: string
|
|
maxLength: 1000
|
|
status:
|
|
type: string
|
|
enum: [active, archived, draft]
|
|
default: active
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateProjectInput:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
description:
|
|
type: string
|
|
maxLength: 1000
|
|
|
|
UpdateProjectInput:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
description:
|
|
type: string
|
|
maxLength: 1000
|
|
status:
|
|
type: string
|
|
enum: [active, archived, draft]
|
|
|
|
ProjectListResponse:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Project'
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
Pagination:
|
|
type: object
|
|
properties:
|
|
page:
|
|
type: integer
|
|
limit:
|
|
type: integer
|
|
total:
|
|
type: integer
|
|
totalPages:
|
|
type: integer
|
|
|
|
Error:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
message:
|
|
type: string
|
|
details:
|
|
type: object
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Bad request
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
Unauthorized:
|
|
description: Unauthorized
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
NotFound:
|
|
description: Resource not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
ValidationError:
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Error'
|
|
- type: object
|
|
properties:
|
|
errors:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
field:
|
|
type: string
|
|
message:
|
|
type: string
|
|
|
|
InternalError:
|
|
description: Internal server error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
```
|
|
|
|
---
|
|
|
|
## Type Generation from OpenAPI
|
|
|
|
```typescript
|
|
// scripts/generate-types.ts
|
|
import { generateApi } from 'swagger-typescript-api';
|
|
import path from 'path';
|
|
|
|
generateApi({
|
|
name: 'api-client.ts',
|
|
output: path.resolve(__dirname, '../lib/api'),
|
|
input: path.resolve(__dirname, '../openapi/api.yaml'),
|
|
httpClientType: 'fetch',
|
|
generateClient: true,
|
|
generateRouteTypes: true,
|
|
generateResponses: true,
|
|
extractRequestParams: true,
|
|
extractRequestBody: true,
|
|
unwrapResponseData: true,
|
|
prettier: {
|
|
printWidth: 100,
|
|
singleQuote: true
|
|
}
|
|
});
|
|
```
|
|
|
|
```typescript
|
|
// lib/api/client.ts - Generated API Client Usage
|
|
import { Api } from './api-client';
|
|
|
|
const api = new Api({
|
|
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
securityWorker: async () => {
|
|
const token = await getAccessToken();
|
|
return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
|
|
}
|
|
});
|
|
|
|
// Type-safe API calls
|
|
export async function getProjects(params?: {
|
|
page?: number;
|
|
limit?: number;
|
|
status?: 'active' | 'archived' | 'draft';
|
|
}) {
|
|
const response = await api.projects.listProjects(params);
|
|
return response.data;
|
|
}
|
|
|
|
export async function createProject(input: {
|
|
name: string;
|
|
description?: string;
|
|
}) {
|
|
const response = await api.projects.createProject(input);
|
|
return response.data;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## API Response Format
|
|
|
|
```typescript
|
|
// lib/api/response.ts
|
|
import { NextResponse } from 'next/server';
|
|
|
|
interface ApiResponse<T> {
|
|
data?: T;
|
|
error?: {
|
|
code: string;
|
|
message: string;
|
|
details?: unknown;
|
|
};
|
|
pagination?: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
}
|
|
|
|
export function success<T>(
|
|
data: T,
|
|
status: number = 200
|
|
): NextResponse<ApiResponse<T>> {
|
|
return NextResponse.json({ data }, { status });
|
|
}
|
|
|
|
export function paginated<T>(
|
|
data: T[],
|
|
pagination: { page: number; limit: number; total: number }
|
|
): NextResponse<ApiResponse<T[]>> {
|
|
return NextResponse.json({
|
|
data,
|
|
pagination: {
|
|
...pagination,
|
|
totalPages: Math.ceil(pagination.total / pagination.limit)
|
|
}
|
|
});
|
|
}
|
|
|
|
export function created<T>(data: T): NextResponse<ApiResponse<T>> {
|
|
return success(data, 201);
|
|
}
|
|
|
|
export function noContent(): NextResponse {
|
|
return new NextResponse(null, { status: 204 });
|
|
}
|
|
|
|
export function error(
|
|
code: string,
|
|
message: string,
|
|
status: number = 400,
|
|
details?: unknown
|
|
): NextResponse<ApiResponse<never>> {
|
|
return NextResponse.json(
|
|
{ error: { code, message, details } },
|
|
{ status }
|
|
);
|
|
}
|
|
|
|
export function badRequest(message: string, details?: unknown) {
|
|
return error('BAD_REQUEST', message, 400, details);
|
|
}
|
|
|
|
export function unauthorized(message: string = 'Unauthorized') {
|
|
return error('UNAUTHORIZED', message, 401);
|
|
}
|
|
|
|
export function forbidden(message: string = 'Forbidden') {
|
|
return error('FORBIDDEN', message, 403);
|
|
}
|
|
|
|
export function notFound(resource: string = 'Resource') {
|
|
return error('NOT_FOUND', `${resource} not found`, 404);
|
|
}
|
|
|
|
export function validationError(errors: Array<{ field: string; message: string }>) {
|
|
return error('VALIDATION_ERROR', 'Validation failed', 422, { errors });
|
|
}
|
|
|
|
export function internalError(message: string = 'Internal server error') {
|
|
return error('INTERNAL_ERROR', message, 500);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Interactive Documentation
|
|
|
|
```tsx
|
|
// app/docs/api/page.tsx
|
|
import SwaggerUI from 'swagger-ui-react';
|
|
import 'swagger-ui-react/swagger-ui.css';
|
|
|
|
export default function ApiDocsPage() {
|
|
return (
|
|
<div className="min-h-screen">
|
|
<SwaggerUI
|
|
url="/openapi/api.yaml"
|
|
docExpansion="list"
|
|
defaultModelsExpandDepth={-1}
|
|
displayRequestDuration={true}
|
|
filter={true}
|
|
showExtensions={true}
|
|
tryItOutEnabled={true}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Alternative: Custom docs with syntax highlighting
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
|
|
function CodeExample({ code, language }: { code: string; language: string }) {
|
|
return (
|
|
<SyntaxHighlighter
|
|
language={language}
|
|
style={vscDarkPlus}
|
|
customStyle={{ borderRadius: '8px' }}
|
|
>
|
|
{code}
|
|
</SyntaxHighlighter>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## API Design Checklist
|
|
|
|
| Aspect | Best Practice |
|
|
|--------|--------------|
|
|
| **URLs** | Use nouns, plural, lowercase |
|
|
| **HTTP Methods** | Match CRUD operations |
|
|
| **Status Codes** | Use appropriate codes |
|
|
| **Errors** | Consistent format with codes |
|
|
| **Pagination** | Always paginate lists |
|
|
| **Versioning** | Use URL versioning (/v1/) |
|
|
| **Documentation** | OpenAPI specification |
|
|
| **Authentication** | Bearer tokens, API keys |
|
|
|
|
---
|
|
|
|
## Fazit
|
|
|
|
Gutes API Design erfordert:
|
|
|
|
1. **Konsistenz**: Einheitliche Struktur und Namenskonventionen
|
|
2. **OpenAPI**: Maschinenlesbare Spezifikation
|
|
3. **Dokumentation**: Interaktiv und aktuell
|
|
4. **Type Safety**: Generierte Typen aus Spec
|
|
|
|
Developer Experience beginnt mit gutem API Design.
|
|
|
|
---
|
|
|
|
## Bildprompts
|
|
|
|
1. "API documentation page with Swagger UI, interactive endpoints"
|
|
2. "REST API resource diagram, CRUD operations visualization"
|
|
3. "OpenAPI specification editor, YAML code with validation"
|
|
|
|
---
|
|
|
|
## Quellen
|
|
|
|
- [OpenAPI Specification](https://spec.openapis.org/oas/latest.html)
|
|
- [REST API Design Guide](https://restfulapi.net/)
|
|
- [Google API Design Guide](https://cloud.google.com/apis/design)
|
|
- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines)
|