Skip to main content

GraphQL APIs

GraphQL is a query language and runtime for APIs that enables clients to request exactly the data they need, avoiding over-fetching and under-fetching.

GraphQL Fundamentals

Core Concepts

Schema Definition

  • Type system: Strongly typed schema
  • Operations: Queries, mutations, subscriptions
  • Resolvers: Functions that resolve fields
  • Introspection: Self-documenting API

Single Endpoint

  • One URL: All operations go to single endpoint
  • POST method: Typically uses HTTP POST
  • HTTP headers: Content-Type: application/json
  • Query structure: JSON with query/mutation/subscription

Client-Driven Queries

"GraphQL APIs allow clients to request exactly the data they need, avoiding over-fetching."

  • Specify fields: Client chooses returned fields
  • Nested queries: Request related data in single request
  • No versioning: Schema evolution without breaking changes
  • Strong typing: Compile-time validation

GraphQL Operations

Queries

Purpose: Fetch data from server

Basic Query:

query GetUser {
user(id: "123") {
id
name
email
}
}

Nested Query:

query GetUserWithOrders {
user(id: "123") {
id
name
email
orders {
id
total
status
items {
name
price
}
}
}
}

Query with Variables:

query GetUser($id: ID!, $includeOrders: Boolean!) {
user(id: $id) {
id
name
email
orders @include(if: $includeOrders) {
id
total
}
}
}

Variables:

{
"id": "123",
"includeOrders": true
}

Mutations

Purpose: Modify data on server

Create Mutation:

mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
createdAt
}
}

Update Mutation:

mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
name
email
updatedAt
}
}

Delete Mutation:

mutation DeleteUser($id: ID!) {
deleteUser(id: $id) {
id
success
message
}
}

Subscriptions

Purpose: Real-time data updates

Subscription:

subscription OnUserUpdated($userId: ID!) {
userUpdated(userId: $userId) {
id
name
email
updatedAt
}
}

Schema Design

Type System

Object Types

type User {
id: ID!
name: String!
email: String!
status: UserStatus!
orders: [Order!]!
createdAt: DateTime!
updatedAt: DateTime!
}

Enum Types

enum UserStatus {
ACTIVE
INACTIVE
SUSPENDED
}

Input Types

input CreateUserInput {
name: String!
email: String!
status: UserStatus = ACTIVE
}

Union Types

union SearchResult = User | Order | Product

Interface Types

interface Node {
id: ID!
createdAt: DateTime!
}

type User implements Node {
id: ID!
name: String!
email: String!
createdAt: DateTime!
}

Implementation Examples

Apollo Server (Node.js)

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
status: UserStatus!
orders: [Order!]!
createdAt: DateTime!
}

type Order {
id: ID!
total: Float!
status: String!
user: User!
createdAt: DateTime!
}

enum UserStatus {
ACTIVE
INACTIVE
SUSPENDED
}

input CreateUserInput {
name: String!
email: String!
status: UserStatus = ACTIVE
}

type Query {
user(id: ID!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
}

type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}

type Subscription {
userUpdated(userId: ID!): User!
}
`;

const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return await dataSources.userAPI.getUserById(id);
},
users: async (_, { limit, offset }, { dataSources }) => {
return await dataSources.userAPI.getUsers(limit, offset);
}
},

Mutation: {
createUser: async (_, { input }, { dataSources }) => {
return await dataSources.userAPI.createUser(input);
}
},

User: {
orders: async (user, _, { dataSources }) => {
return await dataSources.orderAPI.getOrdersByUserId(user.id);
}
}
};

const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => ({
userAPI: new UserAPI(),
orderAPI: new OrderAPI()
})
});

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

GraphQL Yoga (Node.js)

import { createServer } from 'graphql-yoga';
import { schema } from './schema';

const server = createServer({
schema,
context: (request) => ({
user: request.user,
dataSources: {
userAPI: new UserAPI(),
orderAPI: new OrderAPI()
}
})
});

server.start(() => {
console.log('Server is running on http://localhost:4000');
});

Advanced Features

Fragments

fragment UserFields on User {
id
name
email
}

query GetUsers {
users {
...UserFields
}
}

Directives

query GetUser($id: ID!, $includeEmail: Boolean!) {
user(id: $id) {
id
name
email @include(if: $includeEmail)
}
}

Custom Scalars

const { GraphQLScalarType } = require('graphql');

const DateTime = new GraphQLScalarType({
name: 'DateTime',
description: 'DateTime custom scalar type',
serialize(value) {
return value.toISOString();
},
parseValue(value) {
return new Date(value);
},
parseLiteral(ast) {
return new Date(ast.value);
}
});

Performance Optimization

Query Complexity Analysis

const complexityLimit = 1000;

const complexityRule = {
ComplexityLimit: {
limit: complexityLimit,
estimators: [
simpleEstimator({ defaultComplexity: 1 }),
fieldConfigEstimator()
]
}
};

const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [complexityRule.ComplexityLimit]
});

Query Caching

const { RedisCache } = require('apollo-server-cache-redis');

const server = new ApolloServer({
typeDefs,
resolvers,
cache: new RedisCache({
host: 'redis-server',
port: 6379
}),
plugins: [
responseCachePlugin()
]
});

DataLoader Implementation

const DataLoader = require('dataloader');

const batchUsers = async (keys) => {
const users = await User.find({ id: { $in: keys } });
return keys.map(key => users.find(user => user.id === key));
};

const userLoader = new DataLoader(batchUsers, {
maxBatchSize: 100,
batchScheduleFn: () => new Promise(resolve => setTimeout(resolve, 10))
});

Best Practices

Schema Design

  • Granular types: Break down large objects
  • Nullable vs non-null: Use non-null for required fields
  • Consistent naming: Follow naming conventions
  • Documentation: Provide clear descriptions

Resolver Design

  • DataLoader: Prevent N+1 queries
  • Error handling: Graceful error responses
  • Authentication: Context-based auth
  • Authorization: Field-level permissions

Performance

  • Query complexity: Limit complex queries
  • Caching: Implement appropriate caching
  • Batching: Use DataLoader for efficiency
  • Monitoring: Track query performance

Key Takeaway: GraphQL provides flexible, efficient, and type-safe APIs that empower clients while maintaining server-side control and performance optimization capabilities.