Back to Technology

API Development Series Part 14: GraphQL & gRPC

January 31, 2026 Wasil Zafar 50 min read

Master GraphQL and gRPC including GraphQL schemas, queries, mutations, subscriptions, gRPC Protocol Buffers, streaming, and choosing the right API style for your use case.

Table of Contents

  1. GraphQL Fundamentals
  2. Schemas & Types
  3. Queries & Mutations
  4. gRPC Fundamentals
  5. Protocol Buffers
  6. REST vs GraphQL vs gRPC
Series Navigation: This is Part 14 of the 17-part API Development Series. Review Part 13: Performance & Rate Limiting first.

GraphQL Fundamentals

What is GraphQL?

GraphQL is a query language and runtime for APIs that allows clients to request exactly the data they need. Developed by Facebook, it solves over-fetching and under-fetching problems common in REST.

GraphQL query language compared to REST showing precise data fetching vs over-fetching
GraphQL lets clients request exactly the fields they need in a single query, eliminating the over-fetching and under-fetching problems common in REST APIs.

GraphQL Core Concepts

Fundamentals
Concept Description
Schema Type definitions describing available data
Query Read operations - fetch data
Mutation Write operations - create/update/delete
Subscription Real-time updates via WebSocket
Resolver Functions that return data for fields

Schemas & Types

# schema.graphql - Type definitions
type Query {
  tasks(status: TaskStatus, limit: Int = 20, cursor: String): TaskConnection!
  task(id: ID!): Task
  me: User!
}

type Mutation {
  createTask(input: CreateTaskInput!): Task!
  updateTask(id: ID!, input: UpdateTaskInput!): Task!
  deleteTask(id: ID!): Boolean!
}

type Subscription {
  taskUpdated(userId: ID!): Task!
}

type Task {
  id: ID!
  title: String!
  description: String
  status: TaskStatus!
  priority: Priority!
  dueDate: DateTime
  createdAt: DateTime!
  updatedAt: DateTime!
  owner: User!
  tags: [Tag!]!
}

type User {
  id: ID!
  email: String!
  name: String!
  tasks(status: TaskStatus): [Task!]!
}

type TaskConnection {
  edges: [TaskEdge!]!
  pageInfo: PageInfo!
}

type TaskEdge {
  node: Task!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

enum TaskStatus {
  PENDING
  IN_PROGRESS
  COMPLETED
  CANCELLED
}

enum Priority {
  LOW
  MEDIUM
  HIGH
  URGENT
}

input CreateTaskInput {
  title: String!
  description: String
  priority: Priority = MEDIUM
  dueDate: DateTime
  tagIds: [ID!]
}

input UpdateTaskInput {
  title: String
  description: String
  status: TaskStatus
  priority: Priority
  dueDate: DateTime
}

scalar DateTime
GraphQL type system showing object types, enums, input types, and connection-based pagination
The GraphQL type system defines object types, enums, input types for mutations, and connection types for cursor-based pagination.

Queries & Mutations

// Apollo Server setup
const { ApolloServer, gql } = require('apollo-server-express');
const { makeExecutableSchema } = require('@graphql-tools/schema');

// Resolvers
const resolvers = {
  Query: {
    tasks: async (_, { status, limit, cursor }, { dataSources, user }) => {
      return dataSources.taskAPI.getTasks({ userId: user.id, status, limit, cursor });
    },
    task: async (_, { id }, { dataSources }) => {
      return dataSources.taskAPI.getTask(id);
    },
    me: (_, __, { user }) => user
  },
  
  Mutation: {
    createTask: async (_, { input }, { dataSources, user }) => {
      return dataSources.taskAPI.createTask({ ...input, userId: user.id });
    },
    updateTask: async (_, { id, input }, { dataSources, user }) => {
      // Authorization check
      const task = await dataSources.taskAPI.getTask(id);
      if (task.userId !== user.id) {
        throw new ForbiddenError('Not authorized');
      }
      return dataSources.taskAPI.updateTask(id, input);
    },
    deleteTask: async (_, { id }, { dataSources, user }) => {
      const task = await dataSources.taskAPI.getTask(id);
      if (task.userId !== user.id) {
        throw new ForbiddenError('Not authorized');
      }
      return dataSources.taskAPI.deleteTask(id);
    }
  },
  
  // Field resolver for nested data
  Task: {
    owner: (task, _, { dataSources }) => {
      return dataSources.userAPI.getUser(task.userId);
    },
    tags: (task, _, { dataSources }) => {
      return dataSources.tagAPI.getTagsByTask(task.id);
    }
  }
};

// Create server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({
    user: req.user,
    dataSources: {
      taskAPI: new TaskAPI(),
      userAPI: new UserAPI(),
      tagAPI: new TagAPI()
    }
  })
});

Client Query Examples

# Query: Get tasks with owner info
query GetMyTasks($status: TaskStatus, $limit: Int) {
  tasks(status: $status, limit: $limit) {
    edges {
      node {
        id
        title
        status
        priority
        dueDate
        owner {
          name
          email
        }
        tags {
          name
          color
        }
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

# Mutation: Create task
mutation CreateTask($input: CreateTaskInput!) {
  createTask(input: $input) {
    id
    title
    status
    createdAt
  }
}

gRPC Fundamentals

What is gRPC?

gRPC is a high-performance RPC framework using Protocol Buffers for serialization. It's ideal for microservice communication with features like streaming and strong typing.

gRPC architecture showing HTTP/2 transport, binary Protocol Buffer serialization, and streaming modes
gRPC uses HTTP/2 for transport and Protocol Buffers for binary serialization, delivering 10x smaller payloads with support for client, server, and bidirectional streaming.
gRPC Advantages:
  • Performance: Binary serialization (10x smaller than JSON)
  • Streaming: Client, server, and bidirectional streams
  • Code Generation: Typed clients in 10+ languages
  • HTTP/2: Multiplexing, header compression

Protocol Buffers

// task.proto - Protocol Buffer definitions
syntax = "proto3";

package taskapi.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

service TaskService {
  // Unary RPCs
  rpc GetTask(GetTaskRequest) returns (Task);
  rpc CreateTask(CreateTaskRequest) returns (Task);
  rpc UpdateTask(UpdateTaskRequest) returns (Task);
  rpc DeleteTask(DeleteTaskRequest) returns (google.protobuf.Empty);
  
  // Server streaming
  rpc ListTasks(ListTasksRequest) returns (stream Task);
  
  // Bidirectional streaming
  rpc SyncTasks(stream TaskUpdate) returns (stream Task);
}

message Task {
  string id = 1;
  string title = 2;
  string description = 3;
  TaskStatus status = 4;
  Priority priority = 5;
  google.protobuf.Timestamp due_date = 6;
  google.protobuf.Timestamp created_at = 7;
  google.protobuf.Timestamp updated_at = 8;
  string user_id = 9;
}

enum TaskStatus {
  TASK_STATUS_UNSPECIFIED = 0;
  TASK_STATUS_PENDING = 1;
  TASK_STATUS_IN_PROGRESS = 2;
  TASK_STATUS_COMPLETED = 3;
  TASK_STATUS_CANCELLED = 4;
}

enum Priority {
  PRIORITY_UNSPECIFIED = 0;
  PRIORITY_LOW = 1;
  PRIORITY_MEDIUM = 2;
  PRIORITY_HIGH = 3;
  PRIORITY_URGENT = 4;
}

message GetTaskRequest {
  string id = 1;
}

message CreateTaskRequest {
  string title = 1;
  string description = 2;
  Priority priority = 3;
  google.protobuf.Timestamp due_date = 4;
}

message UpdateTaskRequest {
  string id = 1;
  optional string title = 2;
  optional string description = 3;
  optional TaskStatus status = 4;
  optional Priority priority = 5;
}

message DeleteTaskRequest {
  string id = 1;
}

message ListTasksRequest {
  optional TaskStatus status = 1;
  int32 limit = 2;
}

message TaskUpdate {
  string id = 1;
  Task task = 2;
}
Protocol Buffer message definition and binary serialization format with field numbers and wire types
Protocol Buffers define strongly-typed messages with numbered fields, enabling efficient binary serialization across 10+ programming languages.

gRPC Server Implementation

// grpc-server.js - Node.js gRPC server
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDefinition = protoLoader.loadSync('task.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
});

const taskProto = grpc.loadPackageDefinition(packageDefinition).taskapi.v1;

// Service implementation
const taskService = {
  getTask: async (call, callback) => {
    try {
      const task = await db.tasks.findById(call.request.id);
      callback(null, task);
    } catch (error) {
      callback({ code: grpc.status.NOT_FOUND, message: 'Task not found' });
    }
  },
  
  createTask: async (call, callback) => {
    const task = await db.tasks.create(call.request);
    callback(null, task);
  },
  
  // Server streaming - sends multiple responses
  listTasks: async (call) => {
    const { status, limit } = call.request;
    const cursor = db.tasks.find({ status }).limit(limit).cursor();
    
    for await (const task of cursor) {
      call.write(task);
    }
    call.end();
  },
  
  // Bidirectional streaming
  syncTasks: (call) => {
    call.on('data', async (update) => {
      const task = await db.tasks.upsert(update.id, update.task);
      call.write(task);
    });
    
    call.on('end', () => call.end());
  }
};

// Start server
const server = new grpc.Server();
server.addService(taskProto.TaskService.service, taskService);
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
  console.log('gRPC server running on port 50051');
});

REST vs GraphQL vs gRPC

When to Use Each

Decision Guide
Aspect REST GraphQL gRPC
Best For Public APIs, CRUD Complex frontends, BFF Microservices, real-time
Data Format JSON JSON Binary (Protobuf)
Performance Good Good (can optimize) Excellent
Browser Support Native Native Requires grpc-web
Learning Curve Low Medium Medium-High
Decision tree for choosing between REST, GraphQL, and gRPC based on use case, client type, and performance needs
Decision guide: REST for public CRUD APIs, GraphQL for complex frontends with varied data needs, gRPC for high-performance microservice communication.

Practice Exercises

Exercise 1: GraphQL API

Beginner 1.5 hours
  • Create GraphQL schema for tasks
  • Implement Query and Mutation resolvers
  • Test with Apollo Studio

Exercise 2: gRPC Service

Intermediate 2 hours
  • Define Protocol Buffer schema
  • Generate server and client code
  • Implement server streaming RPC

Exercise 3: API Gateway with Multiple Backends

Advanced 3 hours
  • GraphQL gateway federating multiple services
  • REST and gRPC backends
  • Protocol translation layer
Technology