Byggande av GraphQL-Backend för Frontend med Apollo Server
Optimera frontend-API:er med GraphQL BFF och Apollo Server
Mönstret Backend for Frontend (BFF) i kombination med GraphQL och Apollo Server skapar en kraftfull arkitektur för moderna webbapplikationer.
Denna tillvägagångssätt låter dig bygga klientoptimerade API:er som samlar data från flera källor samtidigt som de bibehåller en ren separation av ansvarsområden.

Förstå BFF-mönstret
Backend for Frontend-mönstret uppstod som en lösning på utmaningarna med att stödja flera frontend-applikationer (web, mobil, desktop) med olika databehov. Istället för att tvinga alla klienter att använda ett enda generiskt API, skapar BFF dedikerade backendservice som är anpassade för varje klients specifika behov.
Nyckelfördelar med BFF
- Klientoptimerade API:er: Varje frontend får exakt den data den behöver i det format den förväntar sig
- Minskad klientkomplexitet: Dataaggregation och transformering logik flyttas till backend
- Oberoende utveckling: Frontends kan utvecklas utan att påverka andra klienter eller kärnservicer
- Bättre prestanda: Färre rundresor och mindre payloads förbättrar applikationshastighet
- Teamautonomi: Frontend-team kan äga sin BFF, vilket möjliggör snabbare iteration
Varför GraphQL passar BFF perfekt
GraphQLs flexibla frågespråk gör det idealiskt för BFF-implementeringar:
- Precis datahämtning: Klienter begär endast de fält de behöver
- Enkel begäran: Kombinera data från flera källor i en enda fråga
- Stark typning: Schemat ger en tydlig kontrakt mellan frontend och backend
- Real-tidsförmåga: Subskriptioner möjliggör live datauppdateringar
- Utvecklarupplevelse: Introspektion och GraphQL playground förenklar utvecklingen
Att sätta upp Apollo Server för BFF
Apollo Server ger en robust grund för att bygga GraphQL BFF-lager. Låt oss gå igenom hur man skapar en produktionsklar implementering.
Installation och grundläggande uppsättning
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
// Definiera ditt 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!]!
}
`;
// Implementera resolvers
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);
},
},
};
// Skapa Apollo Server-instans
const server = new ApolloServer({
typeDefs,
resolvers,
});
// Starta servern
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 klar vid ${url}`);
Implementera data källor
Data källor ger en ren abstraktion för att hämta data från olika 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}`);
}
}
Optimera med DataLoader
DataLoader batchar och cachelagrar begäranden för att förebygga N+1-frågeproblemet:
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));
});
// Använd i kontext
const context = async ({ req }) => ({
dataSources: {
userAPI: new UserAPI(),
orderAPI: new OrderAPI(),
},
loaders: {
userLoader: createUserLoader(new UserAPI()),
},
});
Avancerade BFF-mönster
Aggregera flera tjänster
En av BFF:s kärnfördelar är att kombinera data från flera backend-tjänster:
const resolvers = {
Query: {
dashboard: async (_, __, { dataSources, user }) => {
// Hämta data från flera tjänster parallellt
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,
};
},
},
};
Felhantering och motståndskraft
Implementera robust felhantering för produktionsklar 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('Användare hittades inte', {
extensions: {
code: 'NOT_FOUND',
http: { status: 404 },
},
});
}
return user;
} catch (error) {
if (error instanceof GraphQLError) throw error;
throw new GraphQLError('Misslyckades med att hämta användare', {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
originalError: error.message,
},
});
}
},
},
};
Cachestrategier
Implementera effektiv cachning för att förbättra prestanda:
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 minuter
}),
plugins: [
{
async requestDidStart() {
return {
async willSendResponse({ response, contextValue }) {
// Ställ in cachekontrollhuvuden
response.http.headers.set(
'Cache-Control',
'max-age=300, public'
);
},
};
},
},
],
});
Autentisering och auktorisering
Säkra din BFF med korrekt autentisering och auktorisering:
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('Ogiltig autentiseringstoken', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
};
// Skydda resolvers
const resolvers = {
Query: {
me: (_, __, { user }) => {
if (!user) {
throw new GraphQLError('Du måste vara inloggad', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
return user;
},
},
};
Apollo Federation för mikrotjänster
När man arbetar med flera team och tjänster, möjliggör Apollo Federation en distribuerad GraphQL-arkitektur:
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 }),
});
Prestandoptimeringstips
- Använd DataLoader: Implementera alltid DataLoader för att batcha och cachlagra begäranden
- Implementera fältnivå cachning: Cachlagra dyra beräkningar på fältnivå
- Frågekomplexitetsanalys: Begränsa frågedjup och komplexitet för att förebygga missbruk
- Persisterade frågor: Använd persisterade frågor i produktion för att minska payloadstorlek
- Svarskomprimering: Aktivera gzip/brotli-komprimering för svar
- Övervaka frågeprestanda: Spåra långsamma frågor och optimera resolvers
- Använd CDN för statiska scheman: Cachlagra introspektionsfrågor vid kanten
Testning av din BFF
Skriv omfattande tester för din GraphQL BFF:
import { ApolloServer } from '@apollo/server';
describe('Användarfrågor', () => {
let server: ApolloServer;
beforeAll(() => {
server = new ApolloServer({
typeDefs,
resolvers,
});
});
it('hämtar användare efter 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 Användare',
});
}
});
});
Distributionsöverväganden
Containerisering
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 4000
CMD ["node", "dist/index.js"]
Miljökontroll
Använd miljövariabler för 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',
};
Övervakning och Observabilitet
Implementera omfattande övervakning:
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} tog ${duration}ms`);
},
};
},
},
],
});
Vanliga Fällor att Undvika
- N+1 Frågeproblem: Använd alltid DataLoader för relaterad data
- Överhämtning från Backend: Optimera backend-frågor baserat på GraphQL-selektionsuppsättningar
- Saknad Felhantering: Implementera korrekt felhantering och loggning
- Ingen Ratelimiting: Skydda din BFF från missbruk med ratelimiting
- Ignorera Säkerhet: Validera indata, implementera autentisering och begränsa frågekomplexitet
- Dålig Schemadesign: Designa scheman med tanke på klientbehov
- Ingen Cachestrategi: Implementera caching på flera nivåer