Skip to content

GraphQL Apollo Server — Production Configuration and Deployment

DodaTech Updated 2026-06-28 7 min read

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
â„šī¸ Info

Prerequisites: GraphQL server basics. Familiarity with Node.js and Express.

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

  1. What does csrfPrevention do?
  2. How do Automatic Persisted Queries reduce bandwidth?
  3. What plugins should a production Apollo Server use?
  4. How do you restrict query depth?
  5. What is the purpose of response caching?

Answers:

  1. csrfPrevention: true requires a special header (usually apollo-require-preflight) on all mutations to prevent cross-site request forgery attacks.
  2. 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%.
  3. ApolloServerPluginCacheControl (cache hints), responseCachePlugin (response caching), ApolloServerPluginUsageReporting (Apollo Studio metrics), custom logging plugin.
  4. Use graphql-depth-limit as a validation rule: validationRules: [depthLimit(7)]. This rejects queries that nest deeper than 7 levels.
  5. 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

Can Apollo Server handle file uploads?

Yes — use the Upload scalar with graphql-upload middleware. Files are streamed to resolvers, supporting multipart form data without loading entire files into memory.

How do I deploy Apollo Server to production?

Package as a Docker container, deploy behind a load balancer, set NODE_ENV=production, disable introspection, configure CORS, and enable Apollo Studio monitoring.

What is the difference between apollo-server and apollo-server-express?

apollo-server is standalone (HTTP server included). apollo-server-express integrates with Express, useful when you need custom middleware (cors, rate-limiting, auth).

Does Apollo Server support HTTP/2?

Apollo Server runs on Node.js HTTP server, which does not support HTTP/2 natively. Use a reverse proxy (Nginx, Envoy) for HTTP/2 termination.

How do I handle CORS in Apollo Server?

In standalone mode, pass cors option: new ApolloServer({ cors: { origin: ['https://app.dodatech.com'] } }). In Express mode, use the cors middleware.

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
âŦ… Federation
➡ Code Generation

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro