Skip to main content
btheo.com btheo.com > press start to play
NEW POST: NODE.JS SECURITY 2025 OPEN FOR FREELANCE 10+ YEARS EXP REACT × NODE × AWS NEW POST: NODE.JS SECURITY 2025 OPEN FOR FREELANCE 10+ YEARS EXP REACT × NODE × AWS
GRPC 6 MIN READ

gRPC vs REST vs GraphQL: Choosing the Right API

WARNING · DRAGON AHEAD

You need an API. Three technologies promise different wins. Only one fits your job.

The industry will tell you gRPC is faster (true, but irrelevant if you’re not bottle-necked). GraphQL is more flexible (true, but overkill for many teams). REST is boring (true, and sometimes boring is winning).

This is how to actually choose.

REST: Simple, HTTP/1.1, Boring and Proven

REST is the default because it works:

// Express.js - REST API
import express from "express";
const app = express();
app.get("/users/:id", (req, res) => {
const user = db.users.find(req.params.id);
res.json(user);
});
app.post("/users", (req, res) => {
const user = db.users.create(req.body);
res.status(201).json(user);
});
app.listen(3000);

Pros: ✔ Every client can consume it (curl, fetch, mobile, browser, IoT) ✔ HTTP semantics are understood by every engineer ✔ Caching is built-in (HTTP cache headers) ✔ Debugging is trivial (browser dev tools, curl) ✔ No code generation needed

Cons: ⚠️ Over-fetching (you get the whole user, even if you only need the name) ⚠️ Under-fetching (you need the user AND their posts, so multiple requests) ⚠️ Versioning is messy (/v1/users vs /v2/users) ⚠️ N+1 problem if you’re not careful (fetch user, then 100 posts in a loop)

Use REST if: ✔ You’re building a public API ✔ Your team knows HTTP well ✔ Clients are diverse (web, mobile, CLI, hardware) ✔ You don’t have 1000s of endpoints

GraphQL: Flexible Queries, Great for Multi-Client, Overkill for Many

GraphQL lets clients ask for exactly what they need:

# Client asks for user + their 3 most recent posts
query GetUserWithPosts {
user(id: "123") {
id
name
email
posts(limit: 3) {
id
title
createdAt
}
}
}
// Apollo Server - GraphQL API
import { ApolloServer } from "@apollo/server";
const typeDefs = `
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
}
`;
const resolvers = {
Query: {
user: (_, { id }) => db.users.find(id),
},
User: {
posts: (user) => db.posts.where({ userId: user.id }),
},
};
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();

Pros: ✔ No over-fetching (ask for what you need) ✔ No under-fetching (single request gets nested data) ✔ Self-documenting (introspection gives you the entire schema) ✔ Great for BFF (Backend for Frontend) with varying client needs

Cons: ⚠️ N+1 problem by default (if you fetch 100 users, that’s 100 queries for posts). You need DataLoader to batch. ⚠️ Caching is hard (every query is a POST, cache headers don’t work) ⚠️ Debugging is painful (GraphQL errors are nested and opaque) ⚠️ Query complexity can explode (client asks for nested data 10 levels deep) ⚠️ Requires code generation (schema → client types) ⚠️ Overkill for simple APIs (if you have 5 endpoints, REST is faster to build)

Use GraphQL if: ✔ You have multiple clients with wildly different data needs (web, mobile, iPad, embed) ✔ You’re willing to implement DataLoader or fragment caching ✔ Your team is comfortable with schema design ✔ You’re building a public API where over-fetching kills mobile users

gRPC: Fast, Binary, HTTP/2, Perfect for Internal Services

gRPC uses Protocol Buffers (binary) and HTTP/2 (multiplexing), making it 5-10x faster than REST:

// user.proto - Schema definition
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserReply) {}
rpc ListUsers(ListUsersRequest) returns (stream GetUserReply) {}
rpc UpdateUser(User) returns (User) {}
}
message GetUserRequest {
string id = 1;
}
message GetUserReply {
string id = 1;
string name = 2;
string email = 3;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
// Node.js gRPC server (using @grpc/grpc-js)
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
const packageDef = protoLoader.loadSync("user.proto");
const UserService = grpc.loadPackageDefinition(packageDef).user;
const server = new grpc.Server();
server.addService(UserService.UserService.service, {
getUser: (call, callback) => {
const user = db.users.find(call.request.id);
callback(null, user);
},
listUsers: (call) => {
db.users.all().forEach(user => {
call.write(user);
});
call.end();
},
updateUser: (call, callback) => {
const updated = db.users.update(call.request.id, call.request);
callback(null, updated);
},
});
server.bindAsync("0.0.0.0:5000", grpc.ServerCredentials.createInsecure(), () => {
server.start();
});

gRPC client (Node.js):

import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
const packageDef = protoLoader.loadSync("user.proto");
const UserService = grpc.loadPackageDefinition(packageDef).user;
const client = new UserService.UserService(
"localhost:5000",
grpc.credentials.createInsecure()
);
// Single request
client.getUser({ id: "123" }, (err, user) => {
console.log(user);
});
// Streaming
const stream = client.listUsers({});
stream.on("data", (user) => {
console.log(user);
});
FeatureRESTgRPC
Payload size5KB (JSON)500B (binary)
Request latency (p50)45ms8ms
Throughput (req/s)2,00015,000
Time to implement10m30m
Client language supportAllGo, Java, Python, Node, Rust, C++, Ruby
Browser support⚠️ (gRPC-Web)
Debuggingcurl, browsergrpcurl (special tool)
HTTP versionHTTP/1.1HTTP/2

Pros: ✔ 5-10x faster than REST (binary payload, HTTP/2 multiplexing) ✔ Bi-directional streaming (server can push to client) ✔ Strongly typed (proto files generate code) ✔ Perfect for microservices (low latency inter-service calls)

Cons: ⚠️ Requires code generation (generate server stubs, client code) ⚠️ Harder to debug (binary format, not human-readable) ⚠️ No browser support (use gRPC-Web, which is slower) ⚠️ Your whole team needs to learn protobuf ⚠️ Limited ecosystem (fewer off-the-shelf middleware) ⚠️ Overkill for public APIs (gRPC-Web is just REST with overhead)

Use gRPC if: ✔ You’re building microservices (service-to-service is internal) ✔ You need bi-directional streaming (chat, notifications, live data) ✔ Latency matters (finance, trading, real-time features) ✔ You’re in an ecosystem with good gRPC support (Kubernetes, Go, Java services)

Decision Matrix: What to Pick

ScenarioPickWhy
Public API (SaaS)RESTEvery client can consume, caching works, easy to debug
Diverse internal clientsGraphQLMobile wants less data, web wants more, single endpoint
Microservices (internal)gRPC5-10x faster, streaming, strongly typed
Mobile BFFGraphQL + RESTGraphQL for mobile (exact data), REST for web (caching)
Simple CRUD appREST5 endpoints, REST is built in 2 hours
Real-time updatesgRPC or GraphQL SubscriptionsgRPC streaming is faster, GraphQL subscriptions are easier
Public + internalREST + gRPCREST for clients, gRPC for internal services

Real Example: When to Use Each

Your Uber competitor needs:

  • Mobile app (data-limited, expensive bandwidth)
  • Driver app (real-time location updates)
  • Backend services (order processing, payment, notifications)

REST for public driver/rider signup (external clients) ✔ GraphQL for mobile app (only request location + ETA, not 50 fields) ✔ gRPC for internal (service calls: payment → billing → notification are <10ms)

Mobile App ─(GraphQL)─> BFF
Driver App ─(REST)────> API Gateway
[Order Service] ─(gRPC)─> [Payment Service]
─(gRPC)─> [Notification Service]

The Hard Truth

Most teams overthink this. Start with REST. It’s boring, it works, every engineer knows it.

Move to GraphQL when: ✔ Your mobile bill is high (over-fetching data) ✔ You have 3+ different client frontends ✔ Your REST API has 50+ endpoints

Move to gRPC when: ✔ Your service-to-service latency is a bottleneck ✔ You have streaming requirements ✔ Your team is ready for code generation and .proto files

If you’re building a monolith, REST wins every time. Add GraphQL or gRPC only when they solve a real problem, not when someone reads a blog post about them.

Summary

TechnologyBest ForCost
RESTPublic APIs, simplicity, browser clientsLow
GraphQLMulti-client flexibility, mobile efficiencyMedium (N+1 issues, caching)
gRPCInternal services, speed, streamingMedium (code gen, debugging)

Pick REST unless you have a specific reason not to. Microservices? gRPC. Multi-client app? GraphQL. Everything else? REST.

Don’t let optimization theater fool you. Solve the problem in front of you with the simplest tool that works. You can always upgrade later.

ALL POSTS →