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 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

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 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;
}

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

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
Next Steps: In Part 15: Testing & Contracts, we'll master API testing strategies, contract testing with Pact, and Postman automation.
Technology