Skip to content
GraphQL Architecture Guide: Server Setup, Subscriptions & Apollo Federation

GraphQL Architecture Guide: Server Setup, Subscriptions & Apollo Federation

DodaTech Updated Jun 6, 2026 7 min read

GraphQL architecture covers production concerns beyond the schema — server configuration, real-time subscriptions, federated services, and security.

What You’ll Learn

  • Apollo Server configuration and middleware
  • Subscriptions for real-time data with WebSockets
  • Schema federation for distributed GraphQL
  • Security: depth limiting, cost analysis, auth
  • Performance: caching, persisted queries, CDN

Why Architecture Matters

A GraphQL API in development is different from one handling production traffic. Without proper architecture, you’ll face performance issues, security vulnerabilities, and scaling problems. DodaTech’s Durga Antivirus Pro uses a federated GraphQL architecture — one gateway combines schemas from the devices service, threats service, and users service — enabling independent team deployment while providing a unified API.

    flowchart LR
    A["Client"] --> B["GraphQL Gateway"]
    B --> C["Devices\nService"]
    B --> D["Threats\nService"]
    B --> E["Users\nService"]
    C --> F["Firestore"]
    D --> G["External Threat\nIntelligence API"]
    E --> H["Auth0 / Firebase"]
    style B fill:#dbeafe,stroke:#2563eb
    style C fill:#fef3c7,stroke:#d97706
    style D fill:#fef3c7,stroke:#d97706
    style E fill:#fef3c7,stroke:#d97706
  
Prerequisites: GraphQL, GraphQL Mutations, and GraphQL Queries Resolvers.

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,
  csrfPrevention: true,         // CSRF protection
  cache: 'bounded',              // Bounded in-memory cache
  introspection: process.env.NODE_ENV !== 'production', // Disable in production
  persistedQueries: true,        // Persisted query support
  plugins: [
    ApolloServerPluginCacheControl({ defaultMaxAge: 300 }),
    responseCachePlugin(),
  ],
  context: async ({ req }) => {
    const user = await authenticate(req);
    return { user, db };
  },
  // Limit query depth
  validationRules: [depthLimit(7)],
});

server.listen(4000).then(({ url }) => console.log(`Server at ${url}`));

Subscriptions for Real-time Data

Subscriptions use WebSockets to push data from server to client:

Schema

type Subscription {
  threatDetected(deviceId: ID): Threat!
  deviceStatusChanged: Device!
}

Server

const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

const resolvers = {
  Subscription: {
    threatDetected: {
      subscribe: (parent, { deviceId }) => {
        // Filter by device ID if provided
        const channel = deviceId 
          ? `THREAT_DETECTED_${deviceId}` 
          : 'THREAT_DETECTED_ALL';
        return pubsub.asyncIterator(channel);
      },
    },
  },
  Mutation: {
    reportThreat: async (parent, { input }, context) => {
      const threat = await createThreat(input);
      
      // Publish to subscribers
      pubsub.publish(`THREAT_DETECTED_${threat.deviceId}`, {
        threatDetected: threat,
      });
      pubsub.publish('THREAT_DETECTED_ALL', {
        threatDetected: threat,
      });
      
      return threat;
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context,
  subscriptions: {
    path: '/subscriptions',
    onConnect: (connectionParams) => {
      // Verify auth token from connection params
      if (connectionParams.authToken) {
        return { user: verifyToken(connectionParams.authToken) };
      }
      throw new Error('Auth required for subscriptions');
    },
  },
});

Client

import { ApolloClient, InMemoryCache, gql, split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';

// HTTP link for queries/mutations
const httpLink = createHttpLink({ uri: 'http://localhost:4000' });

// WebSocket link for subscriptions
const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/subscriptions',
  options: { connectionParams: { authToken: 'bearer-token' } },
});

// Split based on operation type
const link = split(
  ({ query }) => {
    const def = getMainDefinition(query);
    return def.kind === 'OperationDefinition' && def.operation === 'subscription';
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({ link, cache: new InMemoryCache() });

// Subscribe to real-time threats
client.subscribe({
  query: gql`subscription { 
    threatDetected(deviceId: "dev-001") { id name severity } 
  }`,
}).subscribe({
  next({ data }) {
    console.log('New threat:', data.threatDetected.name);
    showNotification(data.threatDetected);
  },
});

Schema Federation

Federation splits a GraphQL schema across multiple services:

// Gateway
const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('apollo-server');

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'devices', url: 'http://devices-service:4001' },
    { name: 'threats', url: 'http://threats-service:4002' },
    { name: 'users', url: 'http://users-service:4003' },
  ],
});

const server = new ApolloServer({ gateway, subscriptions: false });
server.listen(4000);
// Devices service schema (partial)
type Device @key(fields: "id") {
  id: ID!
  name: String!
  os: String!
  userId: ID!
}

// Users service schema (extends Device)
type Device @key(fields: "id") {
  id: ID!
  user: User!
}

extend type User @key(fields: "id") {
  id: ID! @external
  devices: [Device!]!
}

Security Patterns

const { ApolloServer } = require('apollo-server');
const { costAnalysis } = require('graphql-cost-analysis');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(7),               // Max query depth
    costAnalysis({
      maximumCost: 1000,         // Max query cost
      defaultCost: 1,
      costMap: {
        // Set higher costs for expensive fields
        "Device.threats": 10,
        "User.devices": 5,
      },
    }),
  ],
});

Common Mistakes

1. Not Disabling Introspection in Production

Without introspection: false, anyone can run __schema queries to see your entire API — types, fields, arguments, and even deprecated fields.

2. Ignoring Query Depth Limits

Without depth limits, a client can craft query { device { threats { device { threats { ... } } } } } — an infinite loop that crashes your server.

3. Using Synchronous PubSub in Production

graphql-subscriptions’s PubSub uses an in-memory event emitter — it doesn’t scale across multiple server instances. Use RedisPubSub for production.

4. Not Batching Subscription Events

Publishing individual events for each threat creates overhead. Batch events and use withFilter for client-side filtering.

5. Overusing Subscriptions

Not everything needs real-time updates. Use subscriptions only for live events (threat alerts, chat messages). For periodic data refreshes, use polling queries with pollInterval.

Practice Questions

  1. What is the difference between queries and subscriptions?
  2. How does schema federation help microservice architectures?
  3. Why disable introspection in production?
  4. What is query depth limiting and why is it important?
  5. How does RedisPubSub differ from in-memory PubSub?

Answers:

  1. Queries are request-response (client pulls). Subscriptions are push-based — the server sends data when events occur.
  2. Federation lets each microservice own part of the schema. The gateway composes them into a unified API, so clients see one schema.
  3. Introspection exposes the entire schema to anyone. In production, this reveals internal APIs and gives attackers information about your data model.
  4. Depth limiting restricts how nested a query can be. It prevents malicious or accidental deeply nested queries from overloading the server.
  5. RedisPubSub uses Redis as a message broker, enabling subscriptions to work across multiple server instances. In-memory PubSub only works within a single process.

Challenge: Design a real-time threat monitoring system for Durga Antivirus Pro using GraphQL subscriptions. The system should: (1) allow clients to subscribe to threats for specific devices, (2) notify all admins of critical threats, (3) batch threat events every 5 seconds before publishing, and (4) include device name and user email in the notification.

FAQ

Can I use GraphQL subscriptions with REST APIs?
: Yes — subscriptions can use any event source. A mutation resolver that calls a REST API can publish subscription events when the REST response indicates a state change.
How do I handle authentication for WebSocket subscriptions?
: Pass the auth token in the WebSocket connection parameters (connectionParams). Validate it in the onConnect callback and attach the user to the context.
What is the Apollo Cache and how does it work with subscriptions?
: The Apollo Client InMemoryCache normalizes data by ID and stores it in memory. When a subscription event returns updated data, the cache is automatically updated, and all UI components using that data re-render.
How do I migrate from a monolithic GraphQL server to federation?
: Start by identifying bounded contexts (users, devices, threats). Extract each as a federated service. Use the @key directive to define entity relationships. The gateway stitches them together. Test extensively — federation adds network latency between services.

Try It Yourself

// Simple subscription server
const { ApolloServer, gql, PubSub } = require('apollo-server');
const pubsub = new PubSub();

const typeDefs = gql`
  type Threat { id: ID! name: String! }
  type Query { threats: [Threat!]! }
  type Mutation { addThreat(name: String!): Threat! }
  type Subscription { threatAdded: Threat! }
`;

const resolvers = {
  Query: { threats: () => [] },
  Mutation: {
    addThreat: (_, { name }) => {
      const threat = { id: String(Date.now()), name };
      pubsub.publish('THREAT_ADDED', { threatAdded: threat });
      return threat;
    },
  },
  Subscription: {
    threatAdded: { subscribe: () => pubsub.asyncIterator('THREAT_ADDED') },
  },
};

new ApolloServer({ typeDefs, resolvers }).listen(4000);

What’s Next

TopicDescription
GraphQL IntroductionReview core GraphQL concepts
Mutations GuideCreate, update, and delete patterns
RESTful APIsCompare architectural patterns
SOAP & Web ServicesEnterprise API architecture comparison

What’s Next

Congratulations on completing this Graphql Architecture tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro