2 min read
BackendAPI Design & Documentation
REST API Design Best Practices. OpenAPI Specification, Dokumentation mit Swagger und Developer Experience optimieren.
API DesignREST APIOpenAPISwaggerAPI DocumentationDeveloper Experience

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
# 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
// 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
}
});// 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
// 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
// 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:
- Konsistenz: Einheitliche Struktur und Namenskonventionen
- OpenAPI: Maschinenlesbare Spezifikation
- Dokumentation: Interaktiv und aktuell
- Type Safety: Generierte Typen aus Spec
Developer Experience beginnt mit gutem API Design.
Bildprompts
- "API documentation page with Swagger UI, interactive endpoints"
- "REST API resource diagram, CRUD operations visualization"
- "OpenAPI specification editor, YAML code with validation"