GraphQL ermöglicht es Kunden, in einer einzigen Anfrage genau die Daten anzufordern, die sie benötigen – kein Über-Abrufen, kein Unter-Abrufen. Kombiniert mitApollo-Server und TypeScript ist es eine leistungsstarke Möglichkeit, flexible APIs zu erstellen. In diesem Leitfaden wird eine vollständige GraphQL-API von Grund auf erstellt.
📋 Table of Contents
GraphQL vs. REST – Schnellvergleich
| Aspekt | RUHE | GraphQL |
|---|---|---|
| Datenabruf | Mehrere Endpunkte | Einzelner Endpunkt, präzise Abfragen |
| Übermäßiges Abrufen | Gewöhnlich | Eliminiert |
| Versionierung | URL-Versionen (v1, v2) | Schemaentwicklung |
| Am besten für | Einfaches CRUD, Caching | Komplexe, verschachtelte, kundengesteuerte Daten |
Projekt-Setup
mkdir graphql-api && cd graphql-api
npm init -y
npm install @apollo/server graphql
npm install -D typescript tsx @types/node
npx tsc --init
Definieren Sie das Schema
// src/schema.ts
export const typeDefs = `#graphql
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
published: Boolean!
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
publishPost(id: ID!): Post!
}
`;
Resolver schreiben
// src/resolvers.ts
import { db } from './db';
export const resolvers = {
Query: {
users: () => db.users.findAll(),
user: (_: any, { id }: { id: string }) => db.users.findById(id),
posts: () => db.posts.findAll(),
},
Mutation: {
createUser: (_: any, { name, email }: { name: string; email: string }) => {
return db.users.create({ name, email });
},
createPost: (_: any, args: { title: string; content: string; authorId: string }) => {
return db.posts.create({ ...args, published: false });
},
publishPost: (_: any, { id }: { id: string }) => {
return db.posts.update(id, { published: true });
},
},
// Field resolvers for relationships
User: {
posts: (parent: { id: string }) => db.posts.findByAuthor(parent.id),
},
Post: {
author: (parent: { authorId: string }) => db.users.findById(parent.authorId),
},
};
Erstellen Sie den Server
// src/index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => {
// Extract auth token, attach user to context
const token = req.headers.authorization?.replace('Bearer ', '');
const user = token ? await verifyToken(token) : null;
return { user };
},
});
console.log("GraphQL ready at " + url);
Authentifizierung in Resolvern
Mutation: {
createPost: (_: any, args, context) => {
// Require authentication
if (!context.user) {
throw new GraphQLError('Not authenticated', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
return db.posts.create({ ...args, authorId: context.user.id });
},
},
Lösung des N+1-Problems mit DataLoader
Feldauflöser können N+1 Abfragen auslösen (eine Abfrage pro übergeordnetem Element). DataLoader stapelt sie und speichert sie zwischen:
npm install dataloader
import DataLoader from 'dataloader';
// Create a loader that batches user lookups
const userLoader = new DataLoader(async (ids: readonly string[]) => {
const users = await db.users.findByIds(ids);
// Return in the same order as requested ids
return ids.map(id => users.find(u => u.id === id));
});
// Use in resolver — batches all author lookups into one query
Post: {
author: (parent, _, context) => context.userLoader.load(parent.authorId),
},
Beispielabfrage
# Client requests exactly what it needs
query {
user(id: "1") {
name
posts {
title
published
}
}
}
# Response contains only requested fields:
# { "data": { "user": { "name": "Alice", "posts": [...] } } }
Häufig gestellte Fragen
F: GraphQL oder REST für mein Projekt?
A: GraphQL für komplexe, verschachtelte, kundengesteuerte Daten (mobile Apps, Dashboards mit unterschiedlichen Anforderungen). REST für einfaches CRUD und wenn HTTP-Caching am wichtigsten ist. Viele Teams nutzen beides.
F: Wie gehe ich mit Datei-Uploads in GraphQL um?
A: Verwenden Sie das graphql-upload-Paket oder laden Sie Dateien besser über einen separaten REST-Endpunkt oder eine vorsignierte URL in den Objektspeicher (S3) hoch und übergeben Sie die URL in Ihrer GraphQL-Mutation.
F: Ist GraphQL schwieriger zwischenzuspeichern als REST?
A: Ja auf HTTP-Ebene (einzelner Endpunkt, POST-Anfragen). Verwenden Sie zum Ausgleich den normalisierten Cache des Apollo-Clients auf dem Client und das persistente Caching von Abfragen oder Antworten auf dem Server.
F: Wie verhindere ich teure/böswillige Anfragen?
A: Fügen Sie eine Begrenzung der Abfragetiefe, eine Komplexitätsanalyse und eine Ratenbegrenzung hinzu. Pakete wie graphql- Depth-Limit und graphql-query-complexity schützen vor tief verschachtelten missbräuchlichen Abfragen.
F: Sollte ich Code-First oder Schema-First verwenden?
A: Schema-first (SDL-Strings, hier gezeigt) ist klar und sprachunabhängig. Code-first (Nexus, TypeGraphQL) sorgt für eine bessere TypeScript-Integration. Beide funktionieren; Wählen Sie je nach Teampräferenz aus.
Fazit
GraphQL mit Apollo Server gibt Kunden eine präzise Kontrolle über die von ihnen abgerufenen Daten und verhindert so Über- und Unterabrufe. Der Aufbau hier – typisiertes Schema, Resolver mit Beziehungen, kontextbasierte Authentifizierung und DataLoader für das N+1-Problem – deckt die Grundlagen einer Produktions-GraphQL-API ab. Fügen Sie Tiefenbegrenzung und Komplexitätsanalyse hinzu, bevor Sie live gehen, und koppeln Sie sie mit Apollo Client, um einen normalisierten Cache im Frontend zu erhalten.
🔗 Share this article
✍️ Leave a Comment