So implementieren Sie die Paginierung in REST-APIs (Schritt-für-Schritt-Anleitung)

Beim Erstellen von REST-APIs für Listen ist Paginierung wichtig. Große Datensätze in handliche "Seiten" aufteilen, um Performance-Probleme zu vermeiden.

Leo Schulz

Leo Schulz

5 June 2025

So implementieren Sie die Paginierung in REST-APIs (Schritt-für-Schritt-Anleitung)

Beim Erstellen von REST-APIs, die eine Liste von Ressourcen zurückgeben, ist es entscheidend zu berücksichtigen, wie große Datensätze verarbeitet werden. Tausende oder sogar Millionen von Datensätzen in einer einzigen API-Antwort zurückzugeben, ist unpraktisch und kann zu erheblichen Leistungsproblemen, hohem Speicherverbrauch sowohl für den Server als auch für den Client und einer schlechten Benutzererfahrung führen. Paginierung ist die Standardlösung für dieses Problem. Sie beinhaltet die Aufteilung eines großen Datensatzes in kleinere, handhabbare Teile, sogenannte "Seiten", die dann sequenziell bereitgestellt werden. Dieses Tutorial führt Sie durch die technischen Schritte zur Implementierung verschiedener Paginierungsstrategien in Ihren REST-APIs.

💡
Benötigen Sie ein großartiges API-Testtool, das wunderschöne API-Dokumentation generiert?

Benötigen Sie eine integrierte All-in-One-Plattform, damit Ihr Entwicklerteam mit maximaler Produktivität zusammenarbeiten kann?

Apidog liefert alle Ihre Anforderungen und ersetzt Postman zu einem viel günstigeren Preis!
button

Warum ist Paginierung unerlässlich?

Bevor wir uns mit den Implementierungsdetails befassen, wollen wir kurz darauf eingehen, warum Paginierung ein unverzichtbares Feature für APIs ist, die mit Ressourcensammlungen arbeiten:

  1. Leistung: Das Anfordern und Übertragen großer Datenmengen kann langsam sein. Die Paginierung reduziert die Payload-Größe jeder Anfrage, was zu schnelleren Antwortzeiten und einer geringeren Serverauslastung führt.
  2. Ressourcenverbrauch: Kleinere Antworten verbrauchen weniger Speicher auf dem Server, der sie generiert, und auf dem Client, der sie verarbeitet. Dies ist besonders wichtig für mobile Clients oder Umgebungen mit begrenzten Ressourcen.
  3. Ratenbegrenzung und Kontingente: Viele APIs erzwingen Ratenbegrenzungen. Die Paginierung hilft Clients, innerhalb dieser Grenzen zu bleiben, indem sie Daten in kleineren Teilen im Laufe der Zeit abrufen, anstatt zu versuchen, alles auf einmal zu erhalten.
  4. Benutzererfahrung: Für UIs, die die API nutzen, ist die Darstellung von Daten in Seiten viel benutzerfreundlicher, als Benutzer mit einer riesigen Liste oder einem sehr langen Scroll zu überwältigen.
  5. Datenbankeffizienz: Das Abrufen einer Teilmenge von Daten ist im Allgemeinen weniger belastend für die Datenbank als das Abrufen einer gesamten Tabelle, insbesondere wenn eine ordnungsgemäße Indizierung vorhanden ist.

Gängige Paginierungsstrategien

Es gibt verschiedene gängige Strategien zur Implementierung der Paginierung, von denen jede ihre eigenen Vor- und Nachteile hat. Wir werden die beliebtesten untersuchen: Offset/Limit (oft als seitenbasiert bezeichnet) und cursorbasiert (auch als Keyset- oder Seek-Paginierung bekannt).

1. Offset/Limit (oder seitenbasierte) Paginierung

Dies ist wohl die einfachste und am weitesten verbreitete Paginierungsmethode. Sie funktioniert, indem sie dem Client erlaubt, zwei Hauptparameter anzugeben:

Alternativ können Clients Folgendes angeben:

Der offset kann aus page und pageSize mit der Formel berechnet werden: offset = (page - 1) * pageSize.

Technische Implementierungsschritte:

Nehmen wir an, wir haben einen API-Endpunkt /items, der eine Liste von Elementen zurückgibt.

a. API-Anfrageparameter:
Der Client würde eine Anfrage wie folgt stellen:
GET /items?offset=20&limit=10 (10 Elemente abrufen, die ersten 20 überspringen)
oder
GET /items?page=3&pageSize=10 (die 3. Seite abrufen, mit 10 Elementen pro Seite, was offset=20, limit=10 entspricht).

Es ist gute Praxis, Standardwerte für diese Parameter festzulegen (z. B. limit=20, offset=0 oder page=1, pageSize=20), wenn der Client sie nicht angibt. Erzwingen Sie außerdem ein maximales limit oder pageSize, um zu verhindern, dass Clients eine übermäßig große Anzahl von Datensätzen anfordern, was den Server belasten könnte.

b. Backend-Logik (konzeptionell):
Wenn der Server diese Anfrage empfängt, muss er diese Parameter in eine Datenbankabfrage übersetzen.

// Beispiel in Java mit Spring Boot
@GetMapping("/items")
public ResponseEntity<PaginatedResponse<Item>> getItems(
    @RequestParam(defaultValue = "0") int offset,
    @RequestParam(defaultValue = "20") int limit
) {
    // Limit validieren, um Missbrauch zu verhindern
    if (limit > 100) {
        limit = 100; // Ein maximales Limit erzwingen
    }

    List<Item> items = itemRepository.findItemsWithOffsetLimit(offset, limit);
    long totalItems = itemRepository.countTotalItems(); // Für Metadaten

    // Paginierte Antwort erstellen und zurückgeben
    // ...
}

c. Datenbankabfrage (SQL-Beispiel):
Die meisten relationalen Datenbanken unterstützen Offset- und Limit-Klauseln direkt.

Für PostgreSQL oder MySQL:

SELECT *
FROM items
ORDER BY created_at DESC -- Konsistente Reihenfolge ist entscheidend für eine stabile Paginierung
LIMIT 10 -- Dies ist der Parameter 'limit'
OFFSET 20; -- Dies ist der Parameter 'offset'

Für SQL Server (ältere Versionen verwenden möglicherweise ROW_NUMBER()):

SELECT *
FROM items
ORDER BY created_at DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;

Für Oracle:

SELECT *
FROM (
    SELECT i.*, ROWNUM rnum
    FROM (
        SELECT *
        FROM items
        ORDER BY created_at DESC
    ) i
    WHERE ROWNUM <= 20 + 10 -- offset + limit
)
WHERE rnum > 20; -- offset

Wichtiger Hinweis zur Reihenfolge: Damit die Offset/Limit-Paginierung zuverlässig ist, muss der zugrunde liegende Datensatz nach einem konsistenten und eindeutigen (oder nahezu eindeutigen) Schlüssel oder einer Kombination von Schlüsseln sortiert werden. Wenn sich die Reihenfolge der Elemente zwischen den Anfragen ändern kann (z. B. wenn neue Elemente eingefügt oder Elemente so aktualisiert werden, dass sich ihre Sortierreihenfolge ändert), können Benutzer doppelte Elemente sehen oder Elemente verpassen, wenn sie durch die Seiten navigieren. Eine gängige Wahl ist die Sortierung nach Erstellungszeitstempel oder einer primären ID.

d. API-Antwortstruktur:
Eine gute paginierte Antwort sollte nicht nur die Daten für die aktuelle Seite, sondern auch Metadaten enthalten, um dem Client bei der Navigation zu helfen.

{
  "data": [
    // Array von Elementen für die aktuelle Seite
    { "id": "item_21", "name": "Item 21", ... },
    { "id": "item_22", "name": "Item 22", ... },
    // ... bis zu 'limit' Elemente
    { "id": "item_30", "name": "Item 30", ... }
  ],
  "pagination": {
    "offset": 20,
    "limit": 10,
    "totalItems": 5000, // Gesamtzahl der verfügbaren Elemente
    "totalPages": 500, // Berechnet als ceil(totalItems / limit)
    "currentPage": 3 // Berechnet als (offset / limit) + 1
  },
  "links": { // HATEOAS-Links für die Navigation
    "self": "/items?offset=20&limit=10",
    "first": "/items?offset=0&limit=10",
    "prev": "/items?offset=10&limit=10", // Null, wenn auf der ersten Seite
    "next": "/items?offset=30&limit=10", // Null, wenn auf der letzten Seite
    "last": "/items?offset=4990&limit=10"
  }
}

Das Bereitstellen von HATEOAS-Links (Hypermedia as the Engine of Application State) (self, first, prev, next, last) ist eine REST-Best Practice. Es ermöglicht Clients, durch die Seiten zu navigieren, ohne die URLs selbst erstellen zu müssen.

Vorteile der Offset/Limit-Paginierung:

Nachteile der Offset/Limit-Paginierung:

2. Cursorbasierte (Keyset/Seek-) Paginierung

Die cursorbasierte Paginierung behebt einige der Mängel von Offset/Limit, insbesondere die Leistung bei großen Datensätzen und Datenkonsistenzprobleme. Anstatt sich auf einen absoluten Offset zu verlassen, verwendet sie einen "Cursor", der auf ein bestimmtes Element im Datensatz zeigt. Der Client fordert dann Elemente "nach" oder "vor" diesem Cursor an.

Der Cursor ist typischerweise eine nicht transparente Zeichenfolge, die den/die Wert(e) des/der Sortierschlüssel(s) des letzten Elements codiert, das auf der vorherigen Seite abgerufen wurde.

Technische Implementierungsschritte:

a. API-Anfrageparameter:
Der Client würde eine Anfrage wie folgt stellen:
GET /items?limit=10 (für die erste Seite)
Und für nachfolgende Seiten:
GET /items?limit=10&after_cursor=opaquestringrepresentinglastitemid
Oder, um rückwärts zu paginieren (weniger üblich, aber möglich):
GET /items?limit=10&before_cursor=opaquestringrepresentingfirstitemid

Der Parameter limit definiert weiterhin die Seitengröße.

b. Was ist ein Cursor?
Ein Cursor sollte sein:

c. Backend-Logik (konzeptionell):

// Beispiel in Java mit Spring Boot
@GetMapping("/items")
public ResponseEntity<CursorPaginatedResponse<Item>> getItems(
    @RequestParam(defaultValue = "20") int limit,
    @RequestParam(required = false) String afterCursor
) {
    // Limit validieren
    if (limit > 100) {
        limit = 100;
    }

    // Cursor decodieren, um die Eigenschaften des zuletzt gesehenen Elements zu erhalten
    // z. B. LastSeenItemDetails lastSeen = decodeCursor(afterCursor);
    // Wenn afterCursor null ist, ist es die erste Seite.

    List<Item> items;
    if (afterCursor != null) {
        DecodedCursor decoded = decodeCursor(afterCursor); // z. B. { lastId: "some_uuid", lastCreatedAt: "timestamp" }
        items = itemRepository.findItemsAfter(decoded.getLastCreatedAt(), decoded.getLastId(), limit);
    } else {
        items = itemRepository.findFirstPage(limit);
    }

    String nextCursor = null;
    if (!items.isEmpty() && items.size() == limit) {
        // Unter der Annahme, dass Elemente sortiert sind, wird das letzte Element in der Liste verwendet, um den nächsten Cursor zu generieren
        Item lastItemOnPage = items.get(items.size() - 1);
        nextCursor = encodeCursor(lastItemOnPage.getCreatedAt(), lastItemOnPage.getId());
    }

    // Cursor-paginierte Antwort erstellen und zurückgeben
    // ...
}

// Hilfsmethoden zum Codieren/Decodieren von Cursorn
// private DecodedCursor decodeCursor(String cursor) { ... }
// private String encodeCursor(Timestamp createdAt, String id) { ... }

d. Datenbankabfrage (SQL-Beispiel):
Der Schlüssel ist die Verwendung einer WHERE-Klausel, die Datensätze basierend auf dem/den Sortierschlüssel(n) aus dem Cursor filtert. Die ORDER BY-Klausel muss mit der Zusammensetzung des Cursors übereinstimmen.

Unter der Annahme der Sortierung nach created_at (absteigend) und dann nach id (absteigend) als Tie-Breaker für eine stabile Reihenfolge, wenn created_at nicht eindeutig ist:

Für die erste Seite:

SELECT *
FROM items
ORDER BY created_at DESC, id DESC
LIMIT 10;

Für nachfolgende Seiten, wenn der Cursor zu last_created_at_from_cursor und last_id_from_cursor decodiert wurde:

SELECT *
FROM items
WHERE (created_at, id) < (CAST('last_created_at_from_cursor' AS TIMESTAMP), CAST('last_id_from_cursor' AS UUID)) -- Oder geeignete Typen
-- Für aufsteigende Reihenfolge wäre es >
-- Der Tupelvergleich (created_at, id) < (val1, val2) ist eine prägnante Möglichkeit, Folgendes zu schreiben:
-- WHERE created_at < 'last_created_at_from_cursor'
--    OR (created_at = 'last_created_at_from_cursor' AND id < 'last_id_from_cursor')
ORDER BY created_at DESC, id DESC
LIMIT 10;

Diese Art von Abfrage ist sehr effizient, insbesondere wenn ein Index für (created_at, id) vorhanden ist. Die Datenbank kann direkt zur Startposition "suchen", ohne irrelevante Zeilen zu scannen.

e. API-Antwortstruktur:

{
  "data": [
    // Array von Elementen für die aktuelle Seite
    { "id": "item_N", "createdAt": "2023-10-27T10:05:00Z", ... },
    // ... bis zu 'limit' Elemente
    { "id": "item_M", "createdAt": "2023-10-27T10:00:00Z", ... }
  ],
  "pagination": {
    "limit": 10,
    "hasNextPage": true, // Boolescher Wert, der angibt, ob es weitere Daten gibt
    "nextCursor": "base64encodedcursorstringforitem_M" // Undurchsichtige Zeichenfolge
    // Möglicherweise ein "prevCursor", wenn bidirektionale Cursor unterstützt werden
  },
  "links": {
    "self": "/items?limit=10&after_cursor=current_request_cursor_if_any",
    "next": "/items?limit=10&after_cursor=base64encodedcursorstringforitem_M" // Null, wenn keine nächste Seite
  }
}

Beachten Sie, dass die cursorbasierte Paginierung typischerweise keine totalPages oder totalItems bereitstellt, da die Berechnung dieser eine vollständige Tabellenscan erfordern würde, wodurch einige der Leistungsvorteile negiert würden. Wenn diese unbedingt benötigt werden, kann ein separater Endpunkt oder eine Schätzung bereitgestellt werden.

Vorteile der cursorbasierten Paginierung:

Nachteile der cursorbasierten Paginierung:

Auswahl der richtigen Strategie

Die Wahl zwischen Offset/Limit und cursorbasierter Paginierung hängt von Ihren spezifischen Anforderungen ab:

In einigen Systemen wird sogar ein Hybridansatz verwendet, oder es werden verschiedene Strategien für verschiedene Anwendungsfälle oder Endpunkte angeboten.

Best Practices für die Implementierung der Paginierung

Unabhängig von der gewählten Strategie sollten Sie sich an diese Best Practices halten:

  1. Konsistente Parameternamen: Verwenden Sie klare und konsistente Namen für Ihre Paginierungsparameter (z. B. limit, offset, page, pageSize, after_cursor, before_cursor). Halten Sie sich an eine Konvention (z. B. camelCase oder snake_case) in Ihrer gesamten API.
  2. Navigationslinks bereitstellen (HATEOAS): Wie in den Antwortbeispielen gezeigt, fügen Sie Links für self, next, prev, first und last (falls zutreffend) ein. Dies macht die API leichter auffindbar und entkoppelt den Client von der Logik zum Erstellen von URLs.
  3. Standardwerte und maximale Grenzwerte:
  1. Klare API-Dokumentation: Dokumentieren Sie Ihre Paginierungsstrategie gründlich:
  1. Konsistente Sortierung: Stellen Sie sicher, dass die zugrunde liegenden Daten für jede paginierte Anfrage konsistent sortiert werden. Für Offset/Limit ist dies unerlässlich, um Datenschiefe zu vermeiden. Für cursorbasiert bestimmt die Sortierreihenfolge, wie Cursor erstellt und interpretiert werden. Verwenden Sie eine eindeutige Tie-Breaker-Spalte (z. B. eine primäre ID), wenn die primäre Sortierspalte doppelte Werte haben kann.
  2. Randfälle behandeln:
  1. Überlegungen zur Gesamtanzahl:
  1. Fehlerbehandlung: Geben Sie geeignete HTTP-Statuscodes für Fehler zurück (z. B. 400 für fehlerhafte Eingaben, 500 für Serverfehler beim Abrufen von Daten).
  2. Sicherheit: Stellen Sie sicher, dass die paginierten Daten die Autorisierungsregeln einhalten, auch wenn dies kein direkter Paginierungsmechanismus ist. Ein Benutzer sollte nur in der Lage sein, Daten zu paginieren, die er sehen darf.
  3. Caching: Paginierte Antworten können oft zwischengespeichert werden. Für die offsetbasierte Paginierung ist GET /items?page=2&pageSize=10 sehr zwischenspeicherbar. Für cursorbasiert ist GET /items?limit=10&after_cursor=XYZ ebenfalls zwischenspeicherbar. Stellen Sie sicher, dass Ihre Caching-Strategie gut mit der Art und Weise funktioniert, wie Paginierungslinks generiert und genutzt werden. Invalidierungsstrategien müssen berücksichtigt werden, wenn sich die zugrunde liegenden Daten häufig ändern.

Erweiterte Themen (kurze Erwähnungen)

Fazit

Die korrekte Implementierung der Paginierung ist grundlegend für den Aufbau skalierbarer und benutzerfreundlicher REST-APIs. Während die Offset/Limit-Paginierung einfacher zu beginnen ist, bietet die cursorbasierte Paginierung eine überlegene Leistung und Konsistenz für große, dynamische Datensätze. Indem Sie die technischen Details jeder Strategie verstehen, diejenige auswählen, die am besten zu den Anforderungen Ihrer Anwendung passt, und die Best Practices für die Implementierung und das API-Design befolgen, können Sie sicherstellen, dass Ihre API Ihren Clients effizient Daten liefert, unabhängig von der Skalierung. Denken Sie daran, immer die klare Dokumentation und die robuste Fehlerbehandlung zu priorisieren, um den API-Nutzern ein reibungsloses Erlebnis zu bieten.


Explore more

Fathom-R1-14B: Fortschrittliches KI-Argumentationsmodell aus Indien

Fathom-R1-14B: Fortschrittliches KI-Argumentationsmodell aus Indien

Künstliche Intelligenz wächst rasant. FractalAIResearch/Fathom-R1-14B (14,8 Mrd. Parameter) glänzt in Mathe & Logik.

5 June 2025

Cursor 1.0 mit BugBot: KI-gestütztes Automatisierungstest-Tool ist da:

Cursor 1.0 mit BugBot: KI-gestütztes Automatisierungstest-Tool ist da:

Die Softwareentwicklung erlebt Innovationen durch KI. Cursor, ein KI-Editor, erreicht mit Version 1.0 einen Meilenstein.

5 June 2025

30+ öffentliche Web 3.0 APIs, die Sie jetzt nutzen können

30+ öffentliche Web 3.0 APIs, die Sie jetzt nutzen können

Der Aufstieg von Web 3.0: Dezentral, nutzerorientiert, transparent. APIs ermöglichen innovative dApps und Blockchain-Integration.

4 June 2025

Praktizieren Sie API Design-First in Apidog

Entdecken Sie eine einfachere Möglichkeit, APIs zu erstellen und zu nutzen