GraphQL Apollo Server â Production Configuration and Deployment
In this tutorial, you will learn about GraphQL Apollo Server. We cover key concepts, practical examples, and best practices to help you master this topic.
Apollo Server is the most popular GraphQL server implementation, providing production-ready features like caching, persisted queries, plugins, and security controls.
What You'll Learn
You will learn Apollo Server configuration, production plugins, caching strategies, persisted queries, CSRF protection, CORS setup, and deployment patterns.
Why Apollo Server Matters
Building a production GraphQL server requires more than just resolvers and typeDefs. You need caching, rate limiting, CSRF protection, performance monitoring, and graceful error handling. Apollo Server provides all of these as built-in or plugin-based features. DodaTech's Durga Antivirus Pro runs Apollo Server in production behind a load balancer, serving 50,000+ requests per minute with caching, persisted queries, and federated gateway.
flowchart TB
A["Apollo Server"] --> B["Plugins\n(caching, logging)"]
A --> C["Security\n(CSRF, depth limit)"]
A --> D["Performance\n(persisted queries, cache)"]
A --> E["Context\n(auth, DB, loaders)"]
A --> F["Federation\n(gateway + subgraphs)"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style C fill:#fef3c7,stroke:#d97706
Apollo Server Configuration
const { ApolloServer } = require('apollo-server');
const { ApolloServerPluginCacheControl } = require('apollo-server-core');
const responseCachePlugin = require('apollo-server-plugin-response-cache');
const server = new ApolloServer({
typeDefs,
resolvers,
// Security
csrfPrevention: true,
introspection: process.env.NODE_ENV !== 'production',
// Caching
cache: 'bounded', // Bounded in-memory cache (LRU)
persistedQueries: { // Automatic Persisted Queries
ttl: 900, // Cache persisted queries for 15 min
},
// Context
context: async ({ req }) => ({
user: await authenticate(req),
db,
loaders: createLoaders(),
}),
// Plugins
plugins: [
ApolloServerPluginCacheControl({ defaultMaxAge: 300 }),
responseCachePlugin(),
],
// Validation rules
validationRules: [
depthLimit(7),
],
// Error handling
formatError: (err) => ({
message: err.message,
extensions: {
code: err.extensions?.code || 'INTERNAL_ERROR',
},
}),
});
server.listen(4000).then(({ url }) => console.log(`Server at ${url}`));
Express Middleware Integration
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const { ApolloServerPluginDrainHttpServer } = require('apollo-server-core');
const http = require('http');
async function startServer() {
const app = express();
const httpServer = http.createServer(app);
// Express middleware
app.use(express.json({ limit: '1mb' }));
app.use(cors({ origin: ['https://dashboard.dodatech.com'] }));
app.use(rateLimit({ windowMs: 60000, max: 100 }));
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
],
context: ({ req }) => ({
user: authenticate(req),
db,
}),
});
await server.start();
server.applyMiddleware({ app, path: '/graphql' });
httpServer.listen(4000, () => {
console.log(`Server at http://localhost:4000${server.graphqlPath}`);
});
}
startServer();
Caching Strategies
// Schema-level cache hints
type Threat @cacheControl(maxAge: 60) {
id: ID!
name: String!
severity: Severity!
# Real-time data â don't cache
status: ThreatStatus @cacheControl(maxAge: 0)
}
type Query {
# Cache threat lists for 5 minutes
threats: [Threat!]! @cacheControl(maxAge: 300)
# Cache device list for 30 seconds
devices: [Device!]! @cacheControl(maxAge: 30)
# Never cache user-specific data
myProfile: User! @cacheControl(maxAge: 0)
}
const { ApolloServer } = require('apollo-server');
const responseCachePlugin = require('apollo-server-plugin-response-cache');
const server = new ApolloServer({
typeDefs,
resolvers,
cache: 'bounded',
plugins: [
responseCachePlugin({
// Custom cache key based on user role
sessionId: (requestContext) => {
const user = requestContext.context.user;
return user ? `${user.role}:${user.id}` : 'anonymous';
},
}),
],
});
Persisted Queries
// Automatic Persisted Queries (APQ)
// Client sends hash instead of full query
// Server
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
ttl: 900, // Cache for 15 minutes
cache: 'bounded', // Use server cache
},
});
// First request: { query: "query { ... }", extensions: { persistedQuery: { version: 1, sha256Hash: "abc..." } } }
// Server: Registers query, returns result
// Subsequent requests: { extensions: { persistedQuery: { version: 1, sha256Hash: "abc..." } } }
// Server: Looks up cached query, returns result
// Benefits: Up to 90% bandwidth reduction for repeated queries
Security Configuration
const depthLimit = require('graphql-depth-limit');
const { costAnalysis } = require('graphql-cost-analysis');
const server = new ApolloServer({
typeDefs,
resolvers,
// Prevent deeply nested malicious queries
validationRules: [
depthLimit(7), // Max 7 levels of nesting
costAnalysis({
maximumCost: 1000,
defaultCost: 1,
costMap: {
"Device.threats": 10, // Expensive field â higher cost
"User.devices": 5,
"Threat.analysis": 8,
},
}),
],
// CSRF protection
csrfPrevention: true,
// Disable introspection in production
introspection: process.env.NODE_ENV !== 'production',
// Upload limits
maxFileSize: 10 * 1024 * 1024, // 10MB
});
Monitoring and Logging
const { ApolloServerPluginUsageReporting } = require('apollo-server-core');
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
// Apollo Studio reporting
ApolloServerPluginUsageReporting({
sendReportsImmediately: true,
// Mask variables to avoid sending sensitive data
sendVariableValues: { only: ['severity', 'limit', 'offset'] },
sendHeaders: { except: ['Authorization'] },
}),
// Custom logging plugin
{
requestDidStart(requestContext) {
const startTime = Date.now();
const query = requestContext.request.query;
return {
didEncounterErrors(ctx) {
logger.error('GraphQL errors:', ctx.errors);
},
willSendResponse(ctx) {
const duration = Date.now() - startTime;
logger.info({
operation: ctx.operationName || 'anonymous',
duration,
errors: ctx.errors?.length || 0,
});
// Alert on slow queries
if (duration > 1000) {
logger.warn('Slow query detected', {
operation: ctx.operationName,
query: query?.substring(0, 200),
duration,
});
}
},
};
},
},
],
});
Common Mistakes
1. Not Setting csrfPrevention
Without CSRF protection, an attacker can trick authenticated users into executing GraphQL mutations via cross-site requests. Enable csrfPrevention: true.
2. Leaving Introspection Enabled in Production
Without introspection: false, anyone can run __schema queries to discover your entire API â types, fields, arguments, and deprecated fields.
3. Not Configuring CORS Properly
Using cors({ origin: '*' }) in a production GrafQL API allows any website to make requests. Restrict to specific origins.
4. Ignoring File Upload Size Limits
Without maxFileSize, attackers can upload massive files to exhaust server memory. Set a reasonable limit like 10MB.
5. Not Using Response Caching
Without responseCachePlugin, identical queries from different clients hit resolvers every time. Cache public data aggressively.
Practice Questions
- What does csrfPrevention do?
- How do Automatic Persisted Queries reduce bandwidth?
- What plugins should a production Apollo Server use?
- How do you restrict query depth?
- What is the purpose of response caching?
Answers:
csrfPrevention: truerequires a special header (usuallyapollo-require-preflight) on all mutations to prevent cross-site request forgery attacks.- APQ replaces the full query string with a hash. The server caches the query on first encounter. Subsequent requests send only the hash, reducing bandwidth by up to 90%.
ApolloServerPluginCacheControl(cache hints),responseCachePlugin(response caching),ApolloServerPluginUsageReporting(Apollo Studio metrics), custom logging plugin.- Use
graphql-depth-limitas a validation rule:validationRules: [depthLimit(7)]. This rejects queries that nest deeper than 7 levels. - Response caching stores query results in memory or Redis. Identical subsequent queries are served from cache without executing resolvers, reducing response time and server load.
Challenge: Configure a production-ready Apollo Server for DodaTech's GraphQL API. Include CSRF protection, CORS restricted to dashboard domains, query depth limiting (7 levels), cost analysis (max 1000), response caching (5 min default), persisted queries, disabled introspection in production, and Apollo Studio monitoring. Integrate with Express for custom middleware.
FAQ
Mini Project
Deploy a production-grade Apollo Server for DodaTech. Include Express integration with CORS, rate limiting, and body size limits. Configure all security features (CSRF, depth limit, cost analysis, introspection disabled). Add response caching with custom TTLs per type. Set up Apollo Studio for monitoring. Dockerize and deploy.
What's Next
| Topic | Description |
|---|---|
| Code Generation | TypeScript types from schema |
| Testing | Testing GraphQL APIs |
| Performance | Query optimization and caching |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro