GraphQL BFF mit Apollo Server erstellen
Optimieren Sie Frontend-APIs mit GraphQL BFF und Apollo Server
Der Backend for Frontend (BFF)-Ansatz in Kombination mit GraphQL und Apollo Server schafft eine leistungsstarke Architektur für moderne Webanwendungen.
Dieser Ansatz ermöglicht es Ihnen, clientoptimierte APIs zu erstellen, die Daten aus mehreren Quellen aggregieren, während eine saubere Trennung der Verantwortlichkeiten aufrechterhalten wird.

Verständnis des BFF-Musters
Das Backend-for-Frontend-Muster entstand als Lösung für die Herausforderungen, die mit der Unterstützung mehrerer Frontend-Anwendungen (Web, Mobil, Desktop) mit unterschiedlichen Datenanforderungen verbunden sind. Anstatt alle Clients zu zwingen, eine einzige generische API zu verwenden, erstellt BFF dedizierte Backend-Dienste, die auf die spezifischen Bedürfnisse jedes Clients zugeschnitten sind.
Wichtige Vorteile von BFF
- Clientoptimierte APIs: Jedes Frontend erhält genau die Daten, die es benötigt, im Format, das es erwartet
- Reduzierte Client-Komplexität: Die Datenaggregations- und Transformationslogik wird in das Backend verlagert
- Unabhängige Entwicklung: Frontends können sich weiterentwickeln, ohne andere Clients oder Kernservices zu beeinflussen
- Bessere Leistung: Weniger Roundtrips und kleinere Payloads verbessern die Anwendungsgeschwindigkeit
- Teamautonomie: Frontend-Teams können ihren BFF besitzen, was schnellere Iterationen ermöglicht
Warum GraphQL perfekt zu BFF passt
Die flexible Abfragesprache von GraphQL macht es ideal für BFF-Implementierungen:
- Präzise Datenabfrage: Clients fordern nur die Felder an, die sie benötigen
- Einzelne Anfrage: Kombinieren Sie Daten aus mehreren Quellen in einer Abfrage
- Starke Typisierung: Das Schema bietet einen klaren Vertrag zwischen Frontend und Backend
- Echtzeitfähigkeiten: Subscriptions ermöglichen Live-Datenaktualisierungen
- Entwicklererfahrung: Introspektion und GraphQL-Playground vereinfachen die Entwicklung
Einrichtung von Apollo Server für BFF
Apollo Server bietet eine robuste Grundlage für den Aufbau von GraphQL-BFF-Schichten. Lassen Sie uns eine produktionsreife Implementierung durchgehen.
Installation und Grundeinrichtung
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
// Definieren Sie Ihr GraphQL-Schema
const typeDefs = gql`
type User {
id: ID!
email: String!
name: String!
orders: [Order!]!
}
type Order {
id: ID!
status: String!
total: Float!
items: [OrderItem!]!
}
type OrderItem {
id: ID!
productId: ID!
quantity: Int!
price: Float!
}
type Query {
me: User
user(id: ID!): User
orders(userId: ID!): [Order!]!
}
`;
// Implementieren Sie Resolver
const resolvers = {
Query: {
me: async (_, __, { dataSources, user }) => {
return dataSources.userAPI.getUser(user.id);
},
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.getUser(id);
},
orders: async (_, { userId }, { dataSources }) => {
return dataSources.orderAPI.getOrdersByUser(userId);
},
},
User: {
orders: async (parent, _, { dataSources }) => {
return dataSources.orderAPI.getOrdersByUser(parent.id);
},
},
};
// Erstellen Sie eine Apollo Server-Instanz
const server = new ApolloServer({
typeDefs,
resolvers,
});
// Starten Sie den Server
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({
token: req.headers.authorization,
dataSources: {
userAPI: new UserAPI(),
orderAPI: new OrderAPI(),
},
}),
listen: { port: 4000 },
});
console.log(`🚀 Server bereit unter ${url}`);
Implementierung von Datenquellen
Datenquellen bieten eine saubere Abstraktion zum Abrufen von Daten aus verschiedenen Backends:
import { RESTDataSource } from '@apollo/datasource-rest';
class UserAPI extends RESTDataSource {
override baseURL = 'https://api.example.com/users/';
async getUser(id: string) {
return this.get(`${id}`);
}
async getUsersByIds(ids: string[]) {
return Promise.all(ids.map(id => this.getUser(id)));
}
async updateUser(id: string, data: any) {
return this.patch(`${id}`, { body: data });
}
}
class OrderAPI extends RESTDataSource {
override baseURL = 'https://api.example.com/orders/';
async getOrdersByUser(userId: string) {
return this.get('', {
params: { userId },
});
}
async getOrder(id: string) {
return this.get(`${id}`);
}
}
Optimierung mit DataLoader
DataLoader batcht und zwischenspeichert Anfragen, um das N+1-Abfrageproblem zu verhindern:
import DataLoader from 'dataloader';
const createUserLoader = (userAPI: UserAPI) =>
new DataLoader(async (ids: readonly string[]) => {
const users = await userAPI.getUsersByIds([...ids]);
return ids.map(id => users.find(user => user.id === id));
});
// Verwenden Sie im Kontext
const context = async ({ req }) => ({
dataSources: {
userAPI: new UserAPI(),
orderAPI: new OrderAPI(),
},
loaders: {
userLoader: createUserLoader(new UserAPI()),
},
});
Fortgeschrittene BFF-Muster
Aggregation mehrerer Dienste
Eine der Kernstärken von BFF ist die Kombination von Daten aus mehreren Backend-Diensten:
const resolvers = {
Query: {
dashboard: async (_, __, { dataSources, user }) => {
// Holen Sie Daten aus mehreren Diensten parallel ab
const [userData, orders, recommendations, analytics] = await Promise.all([
dataSources.userAPI.getUser(user.id),
dataSources.orderAPI.getRecentOrders(user.id),
dataSources.recommendationAPI.getRecommendations(user.id),
dataSources.analyticsAPI.getUserStats(user.id),
]);
return {
user: userData,
recentOrders: orders,
recommendations,
stats: analytics,
};
},
},
};
Fehlerbehandlung und Resilienz
Implementieren Sie eine robuste Fehlerbehandlung für die Produktion von BFF:
import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
try {
const user = await dataSources.userAPI.getUser(id);
if (!user) {
throw new GraphQLError('Benutzer nicht gefunden', {
extensions: {
code: 'NOT_FOUND',
http: { status: 404 },
},
});
}
return user;
} catch (error) {
if (error instanceof GraphQLError) throw error;
throw new GraphQLError('Fehler beim Abrufen des Benutzers', {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
originalError: error.message,
},
});
}
},
},
};
Caching-Strategien
Implementieren Sie effizientes Caching, um die Leistung zu verbessern:
import { KeyValueCache } from '@apollo/utils.keyvaluecache';
import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache';
const server = new ApolloServer({
typeDefs,
resolvers,
cache: new InMemoryLRUCache({
maxSize: Math.pow(2, 20) * 100, // 100 MB
ttl: 300, // 5 Minuten
}),
plugins: [
{
async requestDidStart() {
return {
async willSendResponse({ response, contextValue }) {
// Setzen Sie Cache-Control-Header
response.http.headers.set(
'Cache-Control',
'max-age=300, public'
);
},
};
},
},
],
});
Authentifizierung und Autorisierung
Sichern Sie Ihren BFF mit einer ordnungsgemäßen Authentifizierung und Autorisierung:
import jwt from 'jsonwebtoken';
const context = async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return { user: null };
}
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
return { user };
} catch (error) {
throw new GraphQLError('Ungültiger Authentifizierungstoken', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
};
// Schützen Sie Resolver
const resolvers = {
Query: {
me: (_, __, { user }) => {
if (!user) {
throw new GraphQLError('Sie müssen angemeldet sein', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
return user;
},
},
};
Apollo Federation für Microservices
Wenn Sie mit mehreren Teams und Diensten arbeiten, ermöglicht Apollo Federation eine verteilte GraphQL-Architektur:
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
email: String!
name: String!
}
`;
const resolvers = {
User: {
__resolveReference: async (reference, { dataSources }) => {
return dataSources.userAPI.getUser(reference.id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
Leistungoptimierungstipps
- Verwenden Sie DataLoader: Implementieren Sie immer DataLoader, um Anfragen zu batchen und zu zwischenspeichern
- Implementieren Sie Feld-Level-Caching: Zwischenspeichern Sie teure Berechnungen auf Feldebene
- Analyse der Abfragekomplexität: Begrenzen Sie die Abfragetiefe und -komplexität, um Missbrauch zu verhindern
- Persistierte Abfragen: Verwenden Sie persistierte Abfragen in der Produktion, um die Payload-Größe zu reduzieren
- Antwortkomprimierung: Aktivieren Sie gzip/brotli-Komprimierung für Antworten
- Überwachen Sie die Abfrageleistung: Verfolgen Sie langsame Abfragen und optimieren Sie Resolver
- Verwenden Sie CDN für statische Schemas: Zwischenspeichern Sie Introspektionsabfragen am Edge
Testen Ihres BFF
Schreiben Sie umfassende Tests für Ihren GraphQL-BFF:
import { ApolloServer } from '@apollo/server';
describe('User Queries', () => {
let server: ApolloServer;
beforeAll(() => {
server = new ApolloServer({
typeDefs,
resolvers,
});
});
it('holt Benutzer nach ID', async () => {
const result = await server.executeOperation({
query: `
query GetUser($id: ID!) {
user(id: $id) {
id
email
name
}
}
`,
variables: { id: '123' },
});
expect(result.body.kind).toBe('single');
if (result.body.kind === 'single') {
expect(result.body.singleResult.data?.user).toEqual({
id: '123',
email: 'test@example.com',
name: 'Test User',
});
}
});
});
Bereitstellungsüberlegungen
Containerisierung
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 4000
CMD ["node", "dist/index.js"]
Umgebungskonfiguration
Verwenden Sie Umgebungsvariablen für die Konfiguration:
const config = {
port: process.env.PORT || 4000,
userServiceUrl: process.env.USER_SERVICE_URL,
orderServiceUrl: process.env.ORDER_SERVICE_URL,
jwtSecret: process.env.JWT_SECRET,
nodeEnv: process.env.NODE_ENV || 'development',
};
Überwachung und Beobachtbarkeit
Implementieren Sie umfassende Überwachung:
import { ApolloServerPluginInlineTrace } from '@apollo/server/plugin/inlineTrace';
import { ApolloServerPluginLandingPageDisabled } from '@apollo/server/plugin/disabled';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
ApolloServerPluginInlineTrace(),
process.env.NODE_ENV === 'production'
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageLocalDefault(),
{
async requestDidStart() {
const start = Date.now();
return {
async willSendResponse({ operationName, contextValue }) {
const duration = Date.now() - start;
console.log(`Operation ${operationName} took ${duration}ms`);
},
};
},
},
],
});
Häufige Fallstricke, die vermieden werden sollten
- N+1 Query Problem: Verwenden Sie immer DataLoader für verwandte Daten
- Übermäßiges Abfragen vom Backend: Optimieren Sie Backend-Abfragen basierend auf GraphQL-Auswahlmengen
- Fehlende Fehlerbehandlung: Implementieren Sie eine ordnungsgemäße Fehlerbehandlung und Protokollierung
- Keine Rate Limiting: Schützen Sie Ihren BFF vor Missbrauch mit Rate Limiting
- Sicherheit ignorieren: Validieren Sie Eingaben, implementieren Sie Authentifizierung und begrenzen Sie die Abfragekomplexität
- Schlechte Schema-Design: Entwerfen Sie Schemas mit den Bedürfnissen der Clients im Blick
- Keine Caching-Strategie: Implementieren Sie Caching auf mehreren Ebenen