Skip to content
Published on

GraphQL Complete Guide 2025: Beyond REST — From Schema Design to Federation and Performance Optimization

Authors

Introduction: Why GraphQL?

Since Facebook open-sourced GraphQL in 2015, it has become a standard choice for API design. Major tech companies including GitHub, Shopify, Netflix, and Airbnb have adopted it, demonstrating a new paradigm that transcends the limitations of REST.

This article covers everything from GraphQL basics to microservice integration with Federation and performance optimization — all practical knowledge you can apply immediately.


1. REST vs GraphQL: Fundamental Differences

REST Limitations

REST APIs have been the web API standard for years, but they suffer from several persistent issues.

Over-fetching: Responses include data you don't need. You only need a username but get the entire profile.

Under-fetching: You need multiple endpoint calls to render a single screen. User info + posts + comments = 3 requests.

Endpoint management: Every time client requirements change, you must create new endpoints or modify existing ones.

How GraphQL Solves These

# Get exactly the data you need in a single request
query GetUserWithPosts {
  user(id: "123") {
    name
    email
    posts(first: 5) {
      title
      commentCount
    }
  }
}
AspectRESTGraphQL
EndpointsSeparate URL per resourceSingle endpoint
Data amountServer decidesClient decides
Versioningv1, v2 URL branchingSchema evolution (deprecation)
Type systemAugmented with Swagger/OpenAPIBuilt-in type system (SDL)
Real-timeSeparate WebSocket implBuilt-in Subscriptions
CachingEasy HTTP cache utilizationRequires separate strategy
Learning curveLowMedium to high

Core Difference: Declarative Data Fetching

REST specifies WHERE to get data from, while GraphQL declares WHAT data to get.


2. Schema Design (Schema Definition Language)

Basic Type System

The GraphQL schema serves as the API contract. You define types using SDL.

# Scalar types: String, Int, Float, Boolean, ID
# ! means Non-null

type User {
  id: ID!
  name: String!
  email: String!
  age: Int
  posts: [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  tags: [String!]
  publishedAt: DateTime
}

type Comment {
  id: ID!
  body: String!
  author: User!
  post: Post!
  createdAt: DateTime!
}

Query, Mutation, Subscription

GraphQL has three operation types.

type Query {
  # Data retrieval
  user(id: ID!): User
  users(filter: UserFilter, pagination: PaginationInput): UserConnection!
  post(id: ID!): Post
  searchPosts(query: String!): [Post!]!
}

type Mutation {
  # Data modification
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
  createPost(input: CreatePostInput!): Post!
  likePost(postId: ID!): Post!
}

type Subscription {
  # Real-time updates
  postCreated: Post!
  commentAdded(postId: ID!): Comment!
  userStatusChanged(userId: ID!): User!
}

Input Types and Enums

input CreateUserInput {
  name: String!
  email: String!
  age: Int
  role: UserRole = USER
}

input UserFilter {
  role: UserRole
  nameContains: String
  createdAfter: DateTime
}

input PaginationInput {
  first: Int = 20
  after: String
}

enum UserRole {
  ADMIN
  EDITOR
  USER
  GUEST
}

enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

Relay-Style Connections (Cursor-based Pagination)

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

3. Resolver Patterns

Basic Resolver Structure

Resolvers connect each schema field to actual data.

// Resolver's 4 arguments: parent, args, context, info
const resolvers = {
  Query: {
    user: async (parent, args, context, info) => {
      const { id } = args;
      const { dataSources, currentUser } = context;
      return dataSources.userAPI.getUser(id);
    },

    users: async (_, { filter, pagination }, { dataSources }) => {
      return dataSources.userAPI.getUsers(filter, pagination);
    },

    searchPosts: async (_, { query }, { dataSources }) => {
      return dataSources.postAPI.search(query);
    },
  },

  Mutation: {
    createUser: async (_, { input }, { dataSources, currentUser }) => {
      if (!currentUser?.isAdmin) {
        throw new ForbiddenError('Admin access required');
      }
      return dataSources.userAPI.createUser(input);
    },

    likePost: async (_, { postId }, { dataSources, currentUser }) => {
      if (!currentUser) {
        throw new AuthenticationError('Login required');
      }
      return dataSources.postAPI.likePost(postId, currentUser.id);
    },
  },

  // Type resolver - resolving relationship fields
  User: {
    posts: async (user, _, { dataSources }) => {
      return dataSources.postAPI.getPostsByAuthor(user.id);
    },
  },

  Post: {
    author: async (post, _, { dataSources }) => {
      return dataSources.userAPI.getUser(post.authorId);
    },
    comments: async (post, _, { dataSources }) => {
      return dataSources.commentAPI.getByPost(post.id);
    },
  },
};

Context Setup

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

await server.start();

app.use(
  '/graphql',
  expressMiddleware(server, {
    context: async ({ req }) => {
      const token = req.headers.authorization || '';
      const currentUser = await getUserFromToken(token);
      return {
        currentUser,
        dataSources: {
          userAPI: new UserDataSource(),
          postAPI: new PostDataSource(),
          commentAPI: new CommentDataSource(),
        },
      };
    },
  })
);

4. The N+1 Problem and DataLoader

What Is the N+1 Problem?

When you fetch a list of posts and then fetch each post's author individually, the N+1 problem occurs.

Query: posts(first: 10)        -> 1 query (10 posts)
  Post.author (post 1)         -> query 2
  Post.author (post 2)         -> query 3
  ...
  Post.author (post 10)        -> query 11

That's 11 total database queries.

Solving with DataLoader

DataLoader batches requests within the same event loop tick.

import DataLoader from 'dataloader';

// Batch function: receives array of IDs, returns results in same order
const userLoader = new DataLoader(async (userIds: string[]) => {
  const users = await db.users.findMany({
    where: { id: { in: userIds } },
  });

  // Map results in the order of requested IDs
  const userMap = new Map(users.map((u) => [u.id, u]));
  return userIds.map((id) => userMap.get(id) || null);
});

// Usage in resolvers
const resolvers = {
  Post: {
    author: (post, _, { loaders }) => {
      return loaders.userLoader.load(post.authorId);
    },
  },
};

Result: 11 queries reduced to 2.

Query: posts(first: 10)                     -> 1 query
  DataLoader batch: users([1,2,3,...,10])    -> 2 queries (batched)

DataLoader Context Pattern

// Create new DataLoader instances per request
function createLoaders() {
  return {
    userLoader: new DataLoader((ids) => batchGetUsers(ids)),
    postLoader: new DataLoader((ids) => batchGetPosts(ids)),
    commentLoader: new DataLoader((ids) => batchGetComments(ids)),
    // Loader for 1:N relationships
    postsByAuthorLoader: new DataLoader(async (authorIds) => {
      const posts = await db.posts.findMany({
        where: { authorId: { in: authorIds } },
      });
      const grouped = groupBy(posts, 'authorId');
      return authorIds.map((id) => grouped[id] || []);
    }),
  };
}

// Create fresh loaders per request in context
context: async ({ req }) => ({
  currentUser: await getUser(req),
  loaders: createLoaders(),
}),

5. Apollo Server and Client

Apollo Server Setup

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { makeExecutableSchema } from '@graphql-tools/schema';

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

const server = new ApolloServer({
  schema,
  plugins: [
    // Disable introspection in production
    ApolloServerPluginLandingPageDisabled(),
    // Usage reporting
    ApolloServerPluginUsageReporting(),
  ],
  formatError: (formattedError, error) => {
    // Error formatting - hide internal info
    if (formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR') {
      return { message: 'Internal server error' };
    }
    return formattedError;
  },
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async ({ req }) => ({
    currentUser: await authenticateUser(req),
    loaders: createLoaders(),
  }),
});

console.log(`Server ready at ${url}`);

Apollo Client (React)

import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  useQuery,
  useMutation,
  gql,
} from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          posts: {
            // Relay-style cursor pagination merge
            keyArgs: ['filter'],
            merge(existing, incoming, { args }) {
              if (!args?.after) return incoming;
              return {
                ...incoming,
                edges: [...(existing?.edges || []), ...incoming.edges],
              };
            },
          },
        },
      },
    },
  }),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
    },
  },
});

// Usage in React components
const GET_POSTS = gql`
  query GetPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      edges {
        node {
          id
          title
          author {
            name
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
`;

function PostList() {
  const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {
    variables: { first: 10 },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error occurred</p>;

  return (
    <div>
      {data.posts.edges.map(({ node }) => (
        <PostCard key={node.id} post={node} />
      ))}
      {data.posts.pageInfo.hasNextPage && (
        <button
          onClick={() =>
            fetchMore({
              variables: { after: data.posts.pageInfo.endCursor },
            })
          }
        >
          Load More
        </button>
      )}
    </div>
  );
}

6. Apollo Federation v2

What Is Federation?

Apollo Federation combines multiple GraphQL services (Subgraphs) into a single unified API (Supergraph). Each team develops services independently while providing clients with a single GraphQL endpoint.

Subgraph Design

# --- Users Subgraph ---
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
}

type Query {
  user(id: ID!): User
  me: User
}

# --- Posts Subgraph ---
type Post @key(fields: "id") {
  id: ID!
  title: String!
  content: String!
  author: User!
  publishedAt: DateTime
}

# Extending User type from another subgraph
type User @key(fields: "id") {
  id: ID!
  posts: [Post!]!        # Field added by Posts service
  postCount: Int!
}

type Query {
  post(id: ID!): Post
  feed(first: Int = 20, after: String): PostConnection!
}

# --- Reviews Subgraph ---
type Review @key(fields: "id") {
  id: ID!
  rating: Int!
  comment: String
  author: User!
  post: Post!
}

type Post @key(fields: "id") {
  id: ID!
  reviews: [Review!]!     # Added by Reviews service
  averageRating: Float
}

Router Configuration (Apollo Router)

# router.yaml
supergraph:
  listen: 0.0.0.0:4000

subgraphs:
  users:
    routing_url: http://users-service:4001/graphql
  posts:
    routing_url: http://posts-service:4002/graphql
  reviews:
    routing_url: http://reviews-service:4003/graphql

headers:
  all:
    request:
      - propagate:
          named: authorization

Subgraph Reference Resolver

// Posts Subgraph
const resolvers = {
  User: {
    // Called when Federation needs to resolve a User
    __resolveReference: async (user, { loaders }) => {
      // Only user.id is passed
      return loaders.postsByUserLoader.load(user.id);
    },
    posts: async (user, _, { dataSources }) => {
      return dataSources.postAPI.getByAuthor(user.id);
    },
    postCount: async (user, _, { dataSources }) => {
      return dataSources.postAPI.countByAuthor(user.id);
    },
  },
};

7. Subscriptions (Real-Time Features)

Server Setup

import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const httpServer = createServer(app);
const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql',
});

useServer(
  {
    schema,
    context: async (ctx) => {
      const token = ctx.connectionParams?.authorization;
      const user = await authenticateUser(token);
      return { currentUser: user, pubsub };
    },
  },
  wsServer
);

// Resolvers
const resolvers = {
  Subscription: {
    commentAdded: {
      subscribe: (_, { postId }) => {
        return pubsub.asyncIterator(`COMMENT_ADDED_${postId}`);
      },
    },
    postCreated: {
      subscribe: () => pubsub.asyncIterator('POST_CREATED'),
    },
  },

  Mutation: {
    createComment: async (_, { input }, { pubsub, currentUser }) => {
      const comment = await db.comments.create({
        data: { ...input, authorId: currentUser.id },
      });

      // Notify subscribers
      pubsub.publish(`COMMENT_ADDED_${input.postId}`, {
        commentAdded: comment,
      });

      return comment;
    },
  },
};

Client Subscription

import { useSubscription, gql } from '@apollo/client';

const COMMENT_SUBSCRIPTION = gql`
  subscription OnCommentAdded($postId: ID!) {
    commentAdded(postId: $postId) {
      id
      body
      author {
        name
      }
      createdAt
    }
  }
`;

function CommentFeed({ postId }) {
  const { data, loading } = useSubscription(COMMENT_SUBSCRIPTION, {
    variables: { postId },
  });

  // data updates automatically with each new comment
  if (data?.commentAdded) {
    return <NewComment comment={data.commentAdded} />;
  }

  return null;
}

8. Authentication and Authorization

Directive-Based Authorization

# Custom directive definition
directive @auth(requires: Role = USER) on FIELD_DEFINITION | OBJECT

enum Role {
  ADMIN
  EDITOR
  USER
  GUEST
}

type Query {
  publicPosts: [Post!]!
  me: User! @auth
  adminDashboard: Dashboard! @auth(requires: ADMIN)
  userManagement: [User!]! @auth(requires: ADMIN)
}

type Mutation {
  createPost(input: CreatePostInput!): Post! @auth
  deletePost(id: ID!): Boolean! @auth(requires: ADMIN)
  updateProfile(input: UpdateProfileInput!): User! @auth
}

Directive Implementation

import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';
import { defaultFieldResolver } from 'graphql';

function authDirectiveTransformer(schema) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
      const authDirective = getDirective(schema, fieldConfig, 'auth')?.[0];

      if (authDirective) {
        const requiredRole = authDirective.requires || 'USER';
        const originalResolve = fieldConfig.resolve || defaultFieldResolver;

        fieldConfig.resolve = async (source, args, context, info) => {
          const { currentUser } = context;

          if (!currentUser) {
            throw new AuthenticationError('Not authenticated');
          }

          if (!hasRole(currentUser, requiredRole)) {
            throw new ForbiddenError('Insufficient permissions');
          }

          return originalResolve(source, args, context, info);
        };
      }

      return fieldConfig;
    },
  });
}

9. Performance Optimization

Query Complexity Limiting

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  schema,
  validationRules: [
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10,
      formatErrorMessage: (cost) =>
        `Query too complex: cost ${cost} exceeds maximum 1000`,
    }),
  ],
});

Depth Limiting

import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(7)],
});

Persisted Queries (APQ)

// Server
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';

const server = new ApolloServer({
  schema,
  plugins: [ApolloServerPluginCacheControl({ defaultMaxAge: 60 })],
  persistedQueries: {
    cache: new KeyValueCache(), // Redis, etc.
  },
});

// Client
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
import { sha256 } from 'crypto-hash';

const link = createPersistedQueryLink({ sha256 });

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

Cache Control

type Query {
  posts: [Post!]! @cacheControl(maxAge: 300)
  user(id: ID!): User @cacheControl(maxAge: 60)
  me: User @cacheControl(maxAge: 0, scope: PRIVATE)
}

type Post @cacheControl(maxAge: 600) {
  id: ID!
  title: String!
  author: User! @cacheControl(inheritMaxAge: true)
  viewCount: Int! @cacheControl(maxAge: 30)
}

10. Caching Strategies

Server-Side Caching

import { KeyvAdapter } from '@apollo/utils.keyvadapter';
import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const server = new ApolloServer({
  schema,
  cache: new KeyvAdapter(new Keyv({ store: new KeyvRedis('redis://localhost:6379') })),
});

// DataSource-level caching
class CachedUserAPI {
  private cache: RedisClient;
  private ttl = 300; // 5 minutes

  async getUser(id: string): Promise<User> {
    const cacheKey = `user:${id}`;
    const cached = await this.cache.get(cacheKey);
    if (cached) return JSON.parse(cached);

    const user = await db.users.findUnique({ where: { id } });
    await this.cache.set(cacheKey, JSON.stringify(user), 'EX', this.ttl);
    return user;
  }
}

Client-Side Caching (Apollo Cache)

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      keyFields: ['id'],
      fields: {
        fullName: {
          read(_, { readField }) {
            const first = readField('firstName');
            const last = readField('lastName');
            return `${first} ${last}`;
          },
        },
      },
    },
    Post: {
      keyFields: ['id'],
    },
    Query: {
      fields: {
        post(_, { args, toReference }) {
          return toReference({ __typename: 'Post', id: args?.id });
        },
      },
    },
  },
});

11. Testing

Resolver Unit Tests

import { describe, it, expect, vi } from 'vitest';

describe('User Resolvers', () => {
  it('should return user by id', async () => {
    const mockUser = { id: '1', name: 'Test User', email: 'test@example.com' };
    const context = {
      dataSources: {
        userAPI: { getUser: vi.fn().mockResolvedValue(mockUser) },
      },
    };

    const result = await resolvers.Query.user(null, { id: '1' }, context, null);
    expect(result).toEqual(mockUser);
    expect(context.dataSources.userAPI.getUser).toHaveBeenCalledWith('1');
  });

  it('should throw if not admin for createUser', async () => {
    const context = {
      currentUser: { role: 'USER' },
      dataSources: { userAPI: { createUser: vi.fn() } },
    };

    await expect(
      resolvers.Mutation.createUser(null, { input: {} }, context, null)
    ).rejects.toThrow('Admin access required');
  });
});

Integration Tests

import { ApolloServer } from '@apollo/server';

describe('GraphQL Integration', () => {
  let server: ApolloServer;

  beforeAll(async () => {
    server = new ApolloServer({ typeDefs, resolvers });
  });

  it('should execute GetUser query', async () => {
    const response = await server.executeOperation({
      query: `
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            name
            email
          }
        }
      `,
      variables: { id: '1' },
    });

    expect(response.body.kind).toBe('single');
    expect(response.body.singleResult.errors).toBeUndefined();
    expect(response.body.singleResult.data?.user).toBeDefined();
  });
});

12. When NOT to Use GraphQL

GraphQL is not the right fit for every situation.

REST is better when:

  • Simple CRUD APIs with clear resource structures
  • File upload/download-centric services
  • Public APIs where HTTP caching is critical
  • Small teams that cannot absorb the learning curve
  • Server-to-server internal communication (gRPC is more suitable)

GraphQL shines when:

  • Multiple clients (web, mobile, TV) share the same API
  • Frontend teams need fast iteration
  • Complex data relationships (social networks, e-commerce)
  • Unifying microservices under a single API
  • Applications requiring real-time features

13. Interview Questions: 15 Essential Topics

Basics (1-5)

Q1. Explain the core differences between GraphQL and REST.

GraphQL is a query language where clients specify the exact structure of data they need. While REST returns fixed responses from server-defined endpoints, GraphQL lets clients selectively request only desired fields from a single endpoint.

Q2. Explain over-fetching and under-fetching.

Over-fetching occurs when an API returns more data than the client needs. Under-fetching occurs when the client must make multiple API calls to get all the data required for a single view.

Q3. Describe the main types in SDL (Schema Definition Language).

Object types (custom field definitions), Scalar types (String, Int, Float, Boolean, ID), Enums (enumerated values), Input (input objects), Interface (common field definitions), and Union (one of several types).

Q4. Explain the differences between Query, Mutation, and Subscription.

Query is for data retrieval (similar to GET), Mutation is for data modification (similar to POST/PUT/DELETE), and Subscription is for real-time event streams via WebSocket.

Q5. Explain Non-null (!) and list type combinations.

[String] is a nullable array of nullable strings, [String!] is a nullable array of non-null strings, [String!]! is a non-null array of non-null strings.

Intermediate (6-10)

Q6. What is the N+1 problem and how do you solve it?

When fetching a parent list and then individually querying each item's related data. DataLoader collects requests within the same tick and executes them as a single batch query.

Q7. Explain the core concepts of Apollo Federation.

Multiple Subgraphs are combined by a Router into a single Supergraph. @key identifies entities, and __resolveReference resolves entities from other services.

Q8. How do you implement authentication and authorization in GraphQL?

Authentication verifies tokens in context to set currentUser. Authorization uses custom directives (@auth) or role-based checks within resolvers.

Q9. What are the advantages of Persisted Queries?

They reduce network payload by sending query hashes instead of strings, and enhance security by only executing server-registered queries.

Q10. What's the difference between Cursor-based and Offset-based Pagination?

Offset pagination uses page numbers and can cause data duplication/loss during insertions/deletions. Cursor pagination uses opaque cursors based on specific positions and remains stable even with real-time data changes.

Advanced (11-15)

Q11. Explain Query Complexity and Depth Limiting.

Query Complexity assigns costs to fields and rejects queries exceeding the total cost threshold. Depth Limiting restricts nesting depth to prevent malicious recursive queries.

Q12. Explain Entity and Reference Resolver behavior in Federation.

An Entity is a type identified by @key. When the Router needs data from another subgraph, it calls __resolveReference to resolve the full entity using only key fields.

Q13. Why is GraphQL caching harder than REST?

REST directly leverages URL-based HTTP caching, but GraphQL uses a single endpoint with POST requests so HTTP caching cannot be used. Separate strategies like APQ, CDN cache key configuration, and normalized client caches are needed.

Q14. How do you scale Subscriptions in production?

Use Redis PubSub or Kafka instead of in-memory PubSub. Share messages across server instances, and configure sticky sessions or a dedicated WebSocket gateway for load balancing WebSocket connections.

Q15. Explain schema evolution strategy.

Use @deprecated directive to mark fields for deprecation and add new fields. Monitor client usage before removing fields, and verify change compatibility with a schema registry. This is more flexible than REST URL versioning.


14. Practice Quiz: 5 Questions

Q1. Which type represents "a non-null array of non-null strings" in GraphQL?

Answer: [String!]!

[String!]! means both the array itself is non-null (!) and each element is non-null (String!). [String] means both the array and elements can be null.

Q2. Explain the core operating principle of DataLoader.

Answer: It collects .load() calls within the same event loop tick and executes the batch function once.

DataLoader queues requests and when the current tick ends, passes all IDs to a single batch function. This reduces N+1 queries to 1+1 queries.

Q3. What is the role of the @key directive in Apollo Federation?

Answer: It specifies the fields that uniquely identify an Entity.

@key(fields: "id") declares the type as a Federation entity and enables the Router to resolve this entity from other subgraphs using the id field.

Q4. What protocol does GraphQL Subscription use internally?

Answer: WebSocket

Subscriptions use bidirectional connections via WebSocket rather than HTTP request-response. The graphql-ws protocol pushes real-time events from server to client.

Q5. Explain the security benefit of Persisted Queries.

Answer: Only server-registered queries can be executed, preventing arbitrary (malicious) query execution.

APQ (Automatic Persisted Queries) sends SHA256 hashes of queries. The server only executes queries mapped to known hashes, preventing attackers from sending arbitrary complex queries to consume server resources.


References

  1. GraphQL Official Documentation
  2. Apollo GraphQL Documentation
  3. Apollo Federation Guide
  4. GraphQL Spec (2021)
  5. DataLoader GitHub
  6. graphql-ws Protocol
  7. How to GraphQL - Tutorial
  8. Relay Official Documentation
  9. GraphQL Best Practices
  10. Production-Ready GraphQL (Marc-Andre Giroux)
  11. The Guild - GraphQL Tools
  12. GraphQL Security (OWASP)
  13. Apollo Client React Guide
  14. Netflix: GraphQL at Scale
  15. Shopify: GraphQL Design Tutorial
  16. GitHub GraphQL API