🔄 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:
- Was ist API? (Demnächst) - API-Grundkonzepte
- JWT Token (Demnächst) - API-Authentifizierung
- 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! 🔄