Hallo Welt
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

234 Dokumentationen verfügbar

Wissensdatenbank

API Pagination Strategien

Zuletzt aktualisiert: 20.01.2026 um 11:24 Uhr

API Pagination Strategien

Pagination ist essentiell für performante APIs mit großen Datenmengen. Lernen Sie verschiedene Pagination-Strategien und deren Vor- und Nachteile.

Warum Pagination?

┌─────────────────────────────────────────────────────────────┐
│                   OHNE PAGINATION                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   GET /api/users                                            │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                      │  │
│   │   Response: 100.000 User-Objekte                    │  │
│   │                                                      │  │
│   │   • 50 MB Response                                  │  │
│   │   • 10+ Sekunden Ladezeit                          │  │
│   │   • Datenbank unter Last                           │  │
│   │   • Client-Speicher erschöpft                      │  │
│   │                                                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│                   MIT PAGINATION                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   GET /api/users?page=1&limit=20                           │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                      │  │
│   │   Response: 20 User-Objekte                         │  │
│   │                                                      │  │
│   │   • 10 KB Response                                  │  │
│   │   • < 100ms Ladezeit                               │  │
│   │   • Datenbank effizient                            │  │
│   │   • Gute UX mit Paging UI                          │  │
│   │                                                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Pagination-Strategien

Strategie Gut für Probleme
Offset/Limit Einfache Fälle, UI mit Seitenzahlen Performance bei großen Offsets
Cursor/Keyset Große Datenmengen, Real-time Feeds Keine direkten Seitensprünge
Seek/Keyset Sortierte Daten, konsistente Performance Komplexere Implementierung

Offset-Based Pagination

// Request
GET /api/users?page=3&limit=20
// oder
GET /api/users?offset=40&limit=20

// Response
{
    "data": [
        { "id": 41, "name": "User 41", ... },
        { "id": 42, "name": "User 42", ... },
        ...
    ],
    "pagination": {
        "page": 3,
        "limit": 20,
        "total": 1000,
        "totalPages": 50
    },
    "links": {
        "first": "/api/users?page=1&limit=20",
        "prev": "/api/users?page=2&limit=20",
        "next": "/api/users?page=4&limit=20",
        "last": "/api/users?page=50&limit=20"
    }
}
// SQL Implementation
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 40;  -- Seite 3 mit 20 pro Seite

// PHP Implementation
class UserController {
    public function index(Request $request): JsonResponse {
        $page = $request->input('page', 1);
        $limit = min($request->input('limit', 20), 100);  // Max 100
        $offset = ($page - 1) * $limit;

        $users = DB::table('users')
            ->orderBy('created_at', 'desc')
            ->offset($offset)
            ->limit($limit)
            ->get();

        $total = DB::table('users')->count();

        return response()->json([
            'data' => $users,
            'pagination' => [
                'page' => $page,
                'limit' => $limit,
                'total' => $total,
                'totalPages' => ceil($total / $limit)
            ]
        ]);
    }
}
VORTEILE:
✅ Einfach zu implementieren
✅ Intuitive UI (Seite 1, 2, 3...)
✅ Direkte Sprünge möglich
✅ Total Count möglich

NACHTEILE:
❌ OFFSET scannt alle vorherigen Rows
❌ Performance degradiert bei hohen Offsets
❌ Inkonsistente Ergebnisse bei Änderungen
❌ OFFSET 100000 = DB muss 100000 Rows überspringen

Cursor-Based Pagination

// Request
GET /api/users?cursor=eyJpZCI6MTAwfQ&limit=20

// cursor ist encoded: {"id": 100}

// Response
{
    "data": [
        { "id": 101, "name": "User 101", ... },
        { "id": 102, "name": "User 102", ... },
        ...
    ],
    "pagination": {
        "nextCursor": "eyJpZCI6MTIwfQ",  // Base64 encoded
        "prevCursor": "eyJpZCI6MTAxfQ",
        "hasMore": true
    }
}
// SQL Implementation
-- Statt OFFSET: WHERE Clause mit letztem Wert
SELECT * FROM users
WHERE id > 100  -- Cursor-Wert
ORDER BY id ASC
LIMIT 20;

// PHP Implementation
class UserController {
    public function index(Request $request): JsonResponse {
        $limit = min($request->input('limit', 20), 100);
        $cursor = $this->decodeCursor($request->input('cursor'));

        $query = DB::table('users')
            ->orderBy('id', 'asc')
            ->limit($limit + 1);  // +1 für hasMore Check

        if ($cursor) {
            $query->where('id', '>', $cursor['id']);
        }

        $users = $query->get();
        $hasMore = $users->count() > $limit;

        if ($hasMore) {
            $users = $users->slice(0, $limit);
        }

        $lastUser = $users->last();

        return response()->json([
            'data' => $users,
            'pagination' => [
                'nextCursor' => $hasMore
                    ? $this->encodeCursor(['id' => $lastUser->id])
                    : null,
                'hasMore' => $hasMore
            ]
        ]);
    }

    private function encodeCursor(array $data): string {
        return base64_encode(json_encode($data));
    }

    private function decodeCursor(?string $cursor): ?array {
        if (!$cursor) return null;
        return json_decode(base64_decode($cursor), true);
    }
}
VORTEILE:
✅ Konstante Performance (unabhängig von Position)
✅ Konsistente Ergebnisse bei Änderungen
✅ Kein OFFSET Overhead
✅ Gut für Infinite Scroll / Load More

NACHTEILE:
❌ Keine direkten Seitensprünge
❌ Kein Total Count ohne zusätzliche Query
❌ Komplexer bei mehreren Sort-Feldern
❌ Opaque Cursor für Client

Keyset Pagination (Multi-Column)

// Bei Sortierung nach mehreren Feldern

// Request
GET /api/posts?cursor=eyJjcmVhdGVkX2F0IjoiMjAyNC0wMS0xNSIsImlkIjo1MDB9

// SQL - Mehrere Spalten für eindeutige Sortierung
SELECT * FROM posts
WHERE (created_at, id) < ('2024-01-15', 500)
ORDER BY created_at DESC, id DESC
LIMIT 20;

// Oder mit OR für DB-Kompatibilität
SELECT * FROM posts
WHERE created_at < '2024-01-15'
   OR (created_at = '2024-01-15' AND id < 500)
ORDER BY created_at DESC, id DESC
LIMIT 20;
// JavaScript/TypeScript Implementation
interface Cursor {
    createdAt: string;
    id: number;
}

async function getPosts(cursor?: Cursor, limit = 20) {
    let query = db('posts')
        .orderBy('created_at', 'desc')
        .orderBy('id', 'desc')
        .limit(limit + 1);

    if (cursor) {
        query = query.where(function() {
            this.where('created_at', '<', cursor.createdAt)
                .orWhere(function() {
                    this.where('created_at', '=', cursor.createdAt)
                        .andWhere('id', '<', cursor.id);
                });
        });
    }

    const posts = await query;
    const hasMore = posts.length > limit;

    if (hasMore) posts.pop();

    const lastPost = posts[posts.length - 1];

    return {
        data: posts,
        nextCursor: hasMore ? {
            createdAt: lastPost.created_at,
            id: lastPost.id
        } : null,
        hasMore
    };
}

Performance Vergleich

OFFSET bei 1 Million Rows:

Page 1 (OFFSET 0):     ~5ms   ✅
Page 100 (OFFSET 1980): ~15ms  ✅
Page 1000 (OFFSET 19980): ~120ms ⚠️
Page 10000 (OFFSET 199980): ~800ms ❌
Page 50000 (OFFSET 999980): ~4000ms ❌❌

CURSOR bei 1 Million Rows:

Page 1:      ~5ms   ✅
Page 100:    ~5ms   ✅
Page 1000:   ~5ms   ✅
Page 10000:  ~5ms   ✅
Page 50000:  ~5ms   ✅

→ Cursor-Pagination hat KONSTANTE Performance!

API Response Format

// JSON:API Style
{
    "data": [...],
    "links": {
        "self": "/api/users?page[cursor]=abc",
        "first": "/api/users",
        "next": "/api/users?page[cursor]=xyz",
        "prev": "/api/users?page[cursor]=def"
    },
    "meta": {
        "hasMore": true
    }
}

// HAL Style
{
    "_embedded": {
        "users": [...]
    },
    "_links": {
        "self": { "href": "/api/users?cursor=abc" },
        "next": { "href": "/api/users?cursor=xyz" }
    }
}

// Simple Style
{
    "data": [...],
    "pagination": {
        "cursor": "abc123",
        "nextCursor": "xyz789",
        "hasMore": true,
        "limit": 20
    }
}

Wann welche Strategie?

OFFSET-BASED WÄHLEN WENN:
• Kleine Datenmengen (< 100.000)
• UI mit Seitenzahlen benötigt
• Direkte Seitensprünge nötig
• Total Count wichtig
• Einfachheit Priorität

CURSOR-BASED WÄHLEN WENN:
• Große Datenmengen (> 100.000)
• Infinite Scroll UI
• Real-time Feeds (Twitter, News)
• Performance kritisch
• Daten ändern sich häufig

KEYSET WÄHLEN WENN:
• Sortierung nach mehreren Feldern
• Maximale Performance benötigt
• Konsistente Ergebnisse bei Updates wichtig

Hybrid Approach

// Biete beide Optionen an

// Offset für kleine Requests
GET /api/users?page=1&limit=20

// Cursor für große Requests / Iteration
GET /api/users?cursor=abc&limit=20

// Implementation
class UserController {
    public function index(Request $request): JsonResponse {
        $limit = min($request->input('limit', 20), 100);

        // Wenn Cursor vorhanden: Cursor-Pagination
        if ($request->has('cursor')) {
            return $this->cursorPagination($request, $limit);
        }

        // Sonst: Offset-Pagination
        return $this->offsetPagination($request, $limit);
    }
}
💡 Best Practices: 1. Limit immer mit Maximum begrenzen (z.B. max 100)
2. Cursor-Pagination für große Datenmengen
3. Navigation Links in Response mitgeben
4. Cursor opaque halten (Base64 encoded)
5. Index auf Sort-Spalten für Performance

Weitere Informationen