Zum Hauptinhalt springen

🔄 REST API vs GraphQL

📖 Definition

REST API ist eine traditionelle API-Architektur, die ressourcenorientiert mit HTTP-Methoden entwickelt wurde. GraphQL ist eine von Facebook entwickelte Abfragesprache, die einen flexiblen API-Ansatz bietet, bei dem Clients genau die Daten anfordern können, die sie benötigen. REST hat mehrere Endpunkte, während GraphQL alle Daten über einen einzigen Endpunkt verarbeitet.

🎯 Verstehen durch Analogien

Restaurant vs Buffet

REST API = Restaurant-Menü
├─ Nur vordefinierte Menüs bestellbar
├─ Jedes Menü hat feste Zusammensetzung
├─ "Eine Pizza bitte" → Bekomme ganze Pizza
├─ Schwierig, Zutaten zu entfernen oder hinzuzufügen
└─ Einfach und vorhersehbar

GraphQL = Buffet
├─ Wähle und nimm nur was du willst
├─ Wähle nur was du brauchst
├─ "Nur Käse bitte" → Bekomme nur Käse
├─ Frei kombinierbar
└─ Flexibel aber kann komplex sein

Bibliothek Buchausleihe

REST API = Traditionelle Ausleihe
Bibliothekar: "Welches Buch brauchen Sie?"
Ich: "Computerbücher bitte"
Bibliothekar: "Hier sind alle Bücher" (10 Bücher)
Ich: "Ich brauche nur Kapitel 1..." (Rest unnötig)

GraphQL = Intelligente Ausleihe
Ich: "Ich brauche nur Kapitel 3 vom Computerbuch"
Bibliothekar: "Hier ist nur Kapitel 3" (genau was benötigt wird)
Ich: "Perfekt!"

⚙️ Wie es funktioniert

1. Vergleich der Datenanfragemethoden

REST API: Mehrere Endpunkte
GET /users/1 → Benutzerinformationen
GET /users/1/posts → Beiträge des Benutzers
GET /posts/1/comments → Kommentare des Beitrags

Insgesamt 3 Anfragen erforderlich!

GraphQL: Einzelner Endpunkt
POST /graphql
{
user(id: 1) {
name
posts {
title
comments {
text
}
}
}
}

Alle Daten in 1 Anfrage!

2. Over-fetching vs Under-fetching

REST API Probleme

Over-fetching (unnötige Daten empfangen)
GET /users/1
{
"id": 1,
"name": "Max Mustermann",
"email": "max@example.com",
"phone": "0123-456789",
"address": "Hauptstraße 123...",
"createdAt": "2024-01-01",
// Alle Informationen erhalten, obwohl nur Name benötigt!
}

Under-fetching (unzureichende Daten)
GET /users/1 → Benutzerinformationen
GET /users/1/posts → Zusätzliche Anfrage erforderlich
GET /users/1/friends → Noch eine Anfrage erforderlich
// Mehrere Anfragen erforderlich!

GraphQL Lösung

Genau was benötigt wird
{
user(id: 1) {
name // Nur Name anfordern!
}
}
→ { "name": "Max Mustermann" }

Alles auf einmal
{
user(id: 1) {
name
posts { title }
friends { name }
}
}
→ Alle Daten in 1 Anfrage!

3. API-Design-Philosophie

REST: Ressourcenorientiert
┌─────────────────┐
│ /users │ → Benutzerliste
│ /users/1 │ → Bestimmter Benutzer
│ /posts │ → Beitragsliste
│ /posts/1 │ → Bestimmter Beitrag
└─────────────────┘
Endpunkt für jede Ressource

GraphQL: Abfrageorientiert
┌─────────────────┐
│ /graphql │ → Alle Anfragen
└─────────────────┘

┌────────┐
│ Query │ → Daten lesen
│Mutation│ → Daten ändern
│Subscribe│ → Echtzeit-Abonnement
└────────┘

💡 Praxisbeispiele

REST API Beispiel (Express.js)

// Express.js REST API Implementierung
const express = require('express');
const app = express();

app.use(express.json());

// Daten (in der Praxis würde Datenbank verwendet)
const users = [
{
id: 1,
name: 'Max Mustermann',
email: 'max@example.com',
age: 25
},
{
id: 2,
name: 'Anna Schmidt',
email: 'anna@example.com',
age: 30
}
];

const posts = [
{
id: 1,
userId: 1,
title: 'REST API Einführung',
content: 'REST ist...'
},
{
id: 2,
userId: 1,
title: 'GraphQL Einführung',
content: 'GraphQL ist...'
}
];

// ========== GET: Alle Benutzer abrufen ==========
app.get('/api/users', (req, res) => {
res.json(users);
});

// ========== GET: Bestimmten Benutzer abrufen ==========
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
}
res.json(user);
});

// ========== POST: Benutzer erstellen ==========
app.post('/api/users', (req, res) => {
const newUser = {
id: users.length + 1,
name: req.body.name,
email: req.body.email,
age: req.body.age
};
users.push(newUser);
res.status(201).json(newUser);
});

// ========== PUT: Benutzer aktualisieren ==========
app.put('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
}

user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
user.age = req.body.age || user.age;

res.json(user);
});

// ========== DELETE: Benutzer löschen ==========
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
}

users.splice(index, 1);
res.status(204).send();
});

// ========== GET: Beiträge des Benutzers abrufen ==========
app.get('/api/users/:id/posts', (req, res) => {
const userId = parseInt(req.params.id);
const userPosts = posts.filter(p => p.userId === userId);
res.json(userPosts);
});

// ========== GET: Bestimmten Beitrag abrufen ==========
app.get('/api/posts/:id', (req, res) => {
const post = posts.find(p => p.id === parseInt(req.params.id));
if (!post) {
return res.status(404).json({ error: 'Beitrag nicht gefunden' });
}
res.json(post);
});

app.listen(3000, () => {
console.log('REST API Server läuft: http://localhost:3000');
});

GraphQL Beispiel (Apollo Server)

// GraphQL Server Implementierung
const { ApolloServer, gql } = require('apollo-server');

// Daten (in der Praxis würde Datenbank verwendet)
const users = [
{ id: 1, name: 'Max Mustermann', email: 'max@example.com', age: 25 },
{ id: 2, name: 'Anna Schmidt', email: 'anna@example.com', age: 30 }
];

const posts = [
{ id: 1, userId: 1, title: 'REST API Einführung', content: 'REST ist...' },
{ id: 2, userId: 1, title: 'GraphQL Einführung', content: 'GraphQL ist...' },
{ id: 3, userId: 2, title: 'Node.js Tutorial', content: 'Node ist...' }
];

// ========== Schema-Definition (Type Definitions) ==========
const typeDefs = gql`
# Benutzer-Typ
type User {
id: Int!
name: String!
email: String!
age: Int
posts: [Post!]! # Beiträge des Benutzers (Beziehung)
}

# Beitrags-Typ
type Post {
id: Int!
title: String!
content: String!
author: User! # Autor des Beitrags (Beziehung)
}

# Query (Daten lesen)
type Query {
# Alle Benutzer
users: [User!]!

# Bestimmter Benutzer
user(id: Int!): User

# Alle Beiträge
posts: [Post!]!

# Bestimmter Beitrag
post(id: Int!): Post
}

# Mutation (Daten ändern)
type Mutation {
# Benutzer erstellen
createUser(name: String!, email: String!, age: Int): User!

# Benutzer aktualisieren
updateUser(id: Int!, name: String, email: String, age: Int): User

# Benutzer löschen
deleteUser(id: Int!): Boolean!

# Beitrag erstellen
createPost(userId: Int!, title: String!, content: String!): Post!
}
`;

// ========== Resolver (Wie Daten abgerufen werden) ==========
const resolvers = {
Query: {
users: () => users,
user: (parent, args) => users.find(u => u.id === args.id),
posts: () => posts,
post: (parent, args) => posts.find(p => p.id === args.id)
},

Mutation: {
createUser: (parent, args) => {
const newUser = {
id: users.length + 1,
name: args.name,
email: args.email,
age: args.age
};
users.push(newUser);
return newUser;
},

updateUser: (parent, args) => {
const user = users.find(u => u.id === args.id);
if (!user) return null;

if (args.name) user.name = args.name;
if (args.email) user.email = args.email;
if (args.age) user.age = args.age;

return user;
},

deleteUser: (parent, args) => {
const index = users.findIndex(u => u.id === args.id);
if (index === -1) return false;

users.splice(index, 1);
return true;
},

createPost: (parent, args) => {
const newPost = {
id: posts.length + 1,
userId: args.userId,
title: args.title,
content: args.content
};
posts.push(newPost);
return newPost;
}
},

// ========== Beziehungs-Resolver ==========
User: {
posts: (parent) => posts.filter(p => p.userId === parent.id)
},

Post: {
author: (parent) => users.find(u => u.id === parent.userId)
}
};

// ========== Server starten ==========
const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
console.log(`GraphQL Server läuft: ${url}`);
});

🤔 Häufig gestellte Fragen

F1. Sollte ich REST API oder GraphQL wählen?

A: Wählen Sie basierend auf Ihren Projektmerkmalen:

✅ REST API wählen wenn:
├─ Einfache CRUD-Anwendung
├─ Klare Ressourcenstruktur
├─ Caching wichtig (HTTP-Caching nutzen)
├─ Team mit REST vertraut
├─ Viele Datei-Uploads/-Downloads
└─ Beispiel: Blog, E-Commerce-Grundfunktionen

✅ GraphQL wählen wenn:
├─ Komplexe Datenbeziehungen
├─ Mobile App (Dateneinsparung wichtig)
├─ Verschiedene Clients (Web, App, Tablet)
├─ Schnelle Frontend-Entwicklung gewünscht
├─ Echtzeit-Datenabonnement erforderlich
└─ Beispiel: Soziale Medien, Dashboard, Echtzeit-Apps

🤝 Auch zusammen verwendbar:
├─ Haupt-API nutzt GraphQL
├─ Datei-Upload nutzt REST
└─ Vorteile beider nutzen

F2. Was ist der Leistungsunterschied?

A:

// REST API Leistungsmerkmale
Vorteile:
- HTTP-Caching nutzbar
GET /api/users/1
Cache-Control: max-age=3600

- CDN-Caching einfach
- Einfache Endpunkte sind schnell

Nachteile:
- Over-fetching überträgt unnötige Daten
GET /api/users/1
Gibt alle Felder zurück (100KB)

- Under-fetching erfordert mehrere Anfragen
GET /api/users/1 // 1. Anfrage
GET /api/users/1/posts // 2. Anfrage
GET /api/posts/1/comments // 3. Anfrage
// Insgesamt 3 Anfragen!

// GraphQL Leistungsmerkmale
Vorteile:
- Nur genau benötigte Daten (10KB)
query {
user(id: 1) {
name
email
}
}

- Alle Daten in einer Anfrage
query {
user(id: 1) {
name
posts {
title
comments { text }
}
}
}
// In 1 Anfrage erledigt!

Nachteile:
- HTTP-Caching schwierig (nutzt POST-Anfragen)
- Komplexe Abfragen belasten Server
- N+1-Problem (mit DataLoader lösbar)

Echter Benchmark:
REST: 3 Anfragen, 200KB gesamt, 300ms
GraphQL: 1 Anfrage, 50KB gesamt, 150ms
GraphQL vorteilhaft in mobilen Umgebungen

F3. Was ist das N+1-Problem in GraphQL?

A: Mit DataLoader lösbar:

// ========== N+1-Problem ==========
// 10 Benutzer und ihre Beiträge abfragen

// Problem: Ineffiziente Abfragen
const resolvers = {
Query: {
users: () => {
return db.users.findAll(); // 1 Abfrage
}
},
User: {
posts: (user) => {
return db.posts.findByUserId(user.id); // 1 Abfrage pro Benutzer!
}
}
};

// Gesamt-Abfragen: 1 + 10 = 11
// 1: Benutzer abrufen
// 10: Beiträge jedes Benutzers abrufen

// ========== Mit DataLoader lösen ==========
const DataLoader = require('dataloader');

// Beiträge in Batch abrufen
const postLoader = new DataLoader(async (userIds) => {
// Alle Beiträge auf einmal abrufen
const posts = await db.posts.findByUserIds(userIds);

// Nach Benutzer gruppieren
const postsByUser = {};
posts.forEach(post => {
if (!postsByUser[post.userId]) {
postsByUser[post.userId] = [];
}
postsByUser[post.userId].push(post);
});

// Beiträge jedes Benutzers zurückgeben
return userIds.map(id => postsByUser[id] || []);
});

const resolvers = {
User: {
posts: (user) => {
return postLoader.load(user.id); // Batch-Verarbeitung!
}
}
};

// Gesamt-Abfragen: 1 + 1 = 2
// 1: Benutzer abrufen
// 1: Alle Beiträge abrufen (Batch)
// → Signifikante Leistungsverbesserung!

🎓 Nächste Schritte

Nachdem Sie REST API und GraphQL verstanden haben, versuchen Sie zu lernen:

  1. Was ist API? (Demnächst) - API-Grundkonzepte
  2. JWT Token (Demnächst) - API-Authentifizierung
  3. Was ist WebSocket? (Demnächst) - Echtzeit-Kommunikation

Praxis

# ========== REST API Praxis (Express) ==========
mkdir rest-api-demo
cd rest-api-demo
npm init -y
npm install express

# Nach dem Schreiben von server.js
node server.js

# Testen
curl http://localhost:3000/api/users
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Max Mustermann","email":"max@example.com"}'

# ========== GraphQL Praxis (Apollo Server) ==========
mkdir graphql-demo
cd graphql-demo
npm init -y
npm install apollo-server graphql

# Nach dem Schreiben von server.js
node server.js

# GraphQL Playground öffnen
# http://localhost:4000
# Führen Sie Abfragen im Browser aus!

# ========== React + GraphQL ==========
npx create-react-app my-app
cd my-app
npm install @apollo/client graphql

# Nach Apollo Client Konfiguration
npm start

🎬 Fazit

REST API und GraphQL haben jeweils ihre Vor- und Nachteile:

  • REST API: Einfach, vertraut und einfaches Caching
  • GraphQL: Flexibel, effizient und Echtzeit-Unterstützung
  • Auswahlkriterien: Projektkomplexität, Team-Erfahrung, Leistungsanforderungen
  • Hybrid: Beide Ansätze nach Bedarf verwenden

Erstellen Sie großartige Anwendungen mit richtigem API-Design! 🔄