Eine API (Application Programming Interface) ist die Schnittstelle, über die verschiedene Software-Systeme miteinander kommunizieren.

API-Arten vs. HTTP-Methoden


Ein häufiges Missverständnis ist die Verwechslung von API-Arten (wie REST oder GraphQL) und HTTP-Methoden (wie GET oder POST):

  • API-Arten (Architekturstile/Protokolle): Bestimmen die Regeln, das Format und die Struktur, wie Nachrichten zwischen Client und Server ausgetauscht werden (z. B. REST, GraphQL, gRPC).
  • HTTP-Methoden: Sind die Aktionen/Verben, die innerhalb von webbasierten APIs (insbesondere REST) genutzt werden, um dem Server mitzuteilen, was mit einer Ressource getan werden soll.

Die HTTP-Methoden (Verben der API-Welt)


In HTTP-basierten APIs (wie REST) steuern diese Methoden die Operationen auf Daten Ressourcen:

MethodeCRUD-ÄquivalentBeschreibungIdempotent?Sicher?
GETReadRuft Daten ab. Verändert nichts auf dem Server.JaJa
POSTCreateErstellt eine neue Ressource auf dem Server.NeinNein
PUTUpdate / ReplaceErsetzt eine komplette Ressource oder erstellt sie, falls nicht vorhanden.JaNein
PATCHPartial UpdateAktualisiert nur einzelne Felder einer Ressource.NeinNein
DELETEDeleteLöscht eine bestimmte Ressource.JaNein

Hinweise: Idempotent: Mehrfache identische Aufrufe haben dasselbe Ergebnis auf dem Server wie ein einzelner Aufruf. Sicher: Der Aufruf verändert den Zustand des Servers nicht (nur Lesezugriff).

Die wichtigsten API-Arten & Architektur-Stile


REST (Representational State Transfer)

REST ist der heute am weitesten verbreitete Standard für Web-APIs. Es nutzt Standard-HTTP-Methoden und überträgt Daten meist im JSON-Format.

  • Zweck: Standardisierte, stateless Kommunikation über Web-Ressourcen.
  • Vorteile:
    • Einfach zu verstehen und weit verbreitet.
    • Hohe Skalierbarkeit durch Caching (HTTP-Level).
    • Entkopplung von Client und Server.
  • Nachteile:
    • Over-Fetching: Man bekommt oft mehr Daten als benötigt.
    • Under-Fetching: Man muss oft mehrere Endpunkte aufrufen (z. B. /users/1 und dann /users/1/posts).

Minimalbeispiel (REST)

Frontend (JavaScript - Browser):

// POST-Request zum Erstellen eines Benutzers
async function createUser() {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'Max Mustermann', email: 'max@example.com' })
  });
  const data = await response.json();
  console.log('Erstellter User:', data);
}

Backend (Node.js mit Express):

const express = require('express');
const app = express();
app.use(express.json());
 
// REST Endpunkt für POST /users
app.post('/users', (req, res) => {
  const newUser = { id: Date.now(), ...req.body };
  res.status(201).json(newUser);
});
 
app.listen(3000, () => console.log('REST-Server läuft auf Port 3000'));

GraphQL

GraphQL ist eine von Meta entwickelte Abfragesprache für APIs. Der Client bestimmt exakt, welche Daten er zurückerhalten möchte.

  • Zweck: Präzise Datenabfrage mit nur einem einzigen Endpunkt.
  • Vorteile:
    • Kein Over- oder Under-Fetching (Client fordert exakt benötigte Felder an).
    • Ein einziger Endpunkt (/graphql) für alle Anfragen.
    • Starke Typisierung durch Schemas.
  • Nachteile:
    • Komplexere Serverseitige Caching-Mechanismen im Vergleich zu REST.
    • Steilere Lernkurve beim Aufsetzen.

Minimalbeispiel (GraphQL)

Frontend (JavaScript):

// GraphQL Anfrage sendet immer einen POST-Request mit einer 'query'
async function fetchUser() {
  const query = `
    query {
      user(id: "1") {
        name
      }
    }
  `;
  
  const response = await fetch('http://localhost:4000/graphql', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query })
  });
  const result = await response.json();
  console.log(result.data.user); // Nur { name: "Max" } wird zurückgegeben
}

Backend (Node.js mit Apollo Server):

const { ApolloServer, gql } = require('apollo-server');
 
const typeDefs = gql`
  type User { id: ID!, name: String! }
  type Query { user(id: ID!): User }
`;
 
const resolvers = {
  Query: {
    user: (_, { id }) => ({ id, name: 'Max Mustermann' })
  }
};
 
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000).then(({ url }) => console.log(`GraphQL Server auf ${url}`));

WebSockets

WebSockets bieten eine dauerhafte, bidirektionale Full-Duplex-Verbindung zwischen Client und Server.

  • Zweck: Echtzeit-Kommunikation (z. B. Chat-Apps, Live-Ticker, Finanzdaten).
  • Vorteile:
    • Sehr geringe Latenz.
    • Server kann eigenständig Daten an den Client “pushen” (ohne dass der Client fragen muss).
  • Nachteile:
    • Schwerer zu skalieren und zu meistern (Verbindungsaufbau muss gehalten werden).
    • Kein integriertes Caching.

Minimalbeispiel (WebSockets)

Frontend (JavaScript):

const socket = new WebSocket('ws://localhost:8080');
 
// Nachricht empfangen
socket.onmessage = (event) => {
  console.log('Nachricht vom Server:', event.data);
};
 
// Nachricht senden
function sendMessage() {
  socket.send('Hallo Server!');
}

Backend (Node.js mit ws Modul):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
 
wss.on('connection', (ws) => {
  console.log('Client verbunden');
  
  ws.on('message', (message) => {
    console.log('Empfangen:', message.toString());
    ws.send('Antwort: Nachricht erhalten!');
  });
});

gRPC (Google Remote Procedure Call)

Ein modernes, extrem schnelles Framework von Google, das HTTP/2 und Protocol Buffers (protobuf) nutzt.

  • Zweck: Hochleistungs-Mikroservice-Kommunikation.
  • Vorteile:
    • Extrem schnell und performant (binäre Datenübertragung statt Text/JSON).
    • Automatische Code-Generierung für Client und Server in vielen Sprachen.
    • Unterstützt Streaming (Client, Server, Bidirektional).
  • Nachteile:
    • Für Menschen nicht direkt lesbare Daten (Binärformat).
    • Eingeschränkter Browser-Support (oft gRPC-Web Proxy nötig).

Minimalbeispiel (gRPC Schema & Backend-Logik)

Proto-Datei (user.proto):

syntax = "proto3";
 
service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}
 
message UserRequest { string id = 1; }
message UserResponse { string id = 1; string name = 2; }

Backend (Node.js gRPC Server Snippet):

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDef = protoLoader.loadSync('user.proto');
const proto = grpc.loadPackageDefinition(packageDef);
 
const server = new grpc.Server();
server.addService(proto.UserService.service, {
  getUser: (call, callback) => {
    callback(null, { id: call.request.id, name: 'Max' });
  }
});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => server.start());

SOAP (Simple Object Access Protocol)

Ein älteres, streng reglementiertes Protokoll, das ausschließlich XML zur Datenübertragung nutzt.

  • Zweck: Hochsichere Transaktionen in Enterprise-Umgebungen (Banken, Versicherungen, Legacy-Systeme).
  • Vorteile:
    • Eingebaute Sicherheits- und Transaktionsstandards (WS-Security, ACID-Transaktionen).
    • Strikte Vertragsbindung durch WSDL (Web Services Description Language).
  • Nachteile:
    • Sehr komplex und “schwerfällig” (Große XML-Payloads).
    • Aufwendig zu implementieren.

Minimalbeispiel (SOAP Request Payload)

Request (XML via POST):

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:usr="http://example.com/user">
   <soapenv:Header/>
   <soapenv:Body>
      <usr:GetUserRequest>
         <usr:id>1</usr:id>
      </usr:GetUserRequest>
   </soapenv:Body>
</soapenv:Envelope>

Webhooks (Event-driven API)

Webhooks kehren das klassische Client-Server-Prinzip um: Der Server ruft eine URL des Clients auf, sobald ein bestimmtes Ereignis eintritt (“Push” statt “Pull”).

  • Zweck: Benachrichtigungen über Events in Echtzeit (z. B. Stripe Zahlung erfolgreich, GitHub Push Event).
  • Vorteile:
    • Kein ständiges Abfragen (“Polling”) vom Client nötig -> spart Ressourcen.
  • Nachteile:
    • Der Empfänger muss einen öffentlich erreichbaren Endpunkt bereitstellen.
    • Re-Try Logik muss bei Ausfällen vom Sender verwaltet werden.

Minimalbeispiel (Webhook Receiver)

1. Sender / Auslöser (z. B. Zahlungsdienstleister oder eigener Event-Service):

// Backend des Senders: Sendet einen HTTP POST Request bei Eintritt eines Events
async function sendWebhookNotification(targetUrl, payload) {
  try {
    const response = await fetch(targetUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Webhook-Signature': 'secret_hash_signature' // Sicherheits-Token/Signatur
      },
      body: JSON.stringify(payload)
    });
 
    if (response.ok) {
      console.log('Webhook erfolgreich an Empfänger zugestellt!');
    }
  } catch (error) {
    console.error('Fehler beim Zustellen des Webhooks:', error);
  }
}
 
// Beispiel: Wird aufgerufen, wenn ein Kauf abgeschlossen wurde
sendWebhookNotification('https://kunden-backend.de/api/webhook/payment', {
  event: 'payment.succeeded',
  orderId: 'ORDER_12345',
  amount: 49.99,
  currency: 'EUR'
});

2. Empfänger / Receiver (z. B. Dein Express-Backend):

const express = require('express');
const app = express();
app.use(express.json());
 
// Webhook-Endpunkt, den der externe Dienst aufruft
app.post('/api/webhook/payment', (req, res) => {
  const event = req.body;
  console.log('Event empfangen:', event.event, 'für Bestellung:', event.orderId);
 
  // Verarbeite die Logik (z. B. Paket versenden, DB aktualisieren)
  
  // WICHTIG: Dem Sender schnell mit HTTP 200 (OK) quittieren!
  res.status(200).send('Webhook erhalten');
});
 
app.listen(3000);