GraphQL and gRPC: other API styles
REST is not the only way to build an API. Learn what GraphQL and gRPC are, what problems each solves, how a small GraphQL API looks in Python, and how to choose between REST, GraphQL and gRPC.
Most of this track uses REST: URLs for resources, HTTP methods for actions, JSON for data (see *Designing REST APIs*). REST is simple and everywhere, and it should be your default. But two other styles appear often in jobs and interviews: GraphQL and gRPC. Each was created to fix a specific pain with REST.
You do not need to master them now. You need to know what they are, when they help and what they cost.
The problem GraphQL solves
Imagine the Brew & Bean mobile app home screen. It needs the customer's name, their last order's status, and the names of the items in that order. With REST you might call three endpoints: GET /users/7, GET /users/7/orders?limit=1 and then GET /menu to turn item IDs into names. Three round trips on a slow mobile network is painful.
The opposite problem also exists. GET /orders/42 may return 20 fields when a small widget needs only status. That is called over-fetching. Needing several calls is called under-fetching.
A team could add custom endpoints for every screen, but that grows endlessly. GraphQL takes a different approach: the server exposes one endpoint and a typed description of all available data, and the client asks for exactly the fields it wants in one request.
GraphQL in practice
A GraphQL API has three main ideas.
A schema: a typed description of what exists (types, fields, and the entry points).
Queries to read data and mutations to change data. (A third kind, subscriptions, pushes live updates.)
Resolvers: functions on the server that fetch the value for each field.
type MenuItem {
id: ID!
name: String!
category: String!
price: Int!
}
type Query {
menu(category: String): [MenuItem!]!
item(id: ID!): MenuItem
}
type Mutation {
placeOrder(customerName: String!, itemId: ID!, qty: Int!): Order!
}The ! means "never null". Query and Mutation are the entry points. Order would be another type like MenuItem.
query {
menu(category: "coffee") {
name
price
}
}Sent as a POST to a single URL such as /graphql with a JSON body {"query": "..."}.
{
"data": {
"menu": [
{"name": "Espresso", "price": 120},
{"name": "Cappuccino", "price": 180},
{"name": "Cold Brew", "price": 220},
{"name": "Filter Coffee", "price": 80}
]
}
}The response mirrors the query: only name and price come back, even though id and category exist. Add a field to the query and it appears, with no backend change needed.
A small GraphQL server in Python
Strawberry is a popular Python GraphQL library that works with FastAPI and uses type hints to build the schema. Install with pip install "strawberry-graphql[fastapi]" uvicorn, save the code as main.py and run uvicorn main:app --reload. Then open http://localhost:8000/graphql, which shows an interactive editor (GraphiQL) where you can try queries.
from typing import Optional
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
@strawberry.type
class MenuItem:
id: str
name: str
category: str
price: int
ITEMS = [
MenuItem(id="espresso", name="Espresso", category="coffee", price=120),
MenuItem(id="cappuccino", name="Cappuccino", category="coffee", price=180),
MenuItem(id="masala-chai", name="Masala Chai", category="tea", price=90),
MenuItem(id="croissant", name="Butter Croissant", category="bakery", price=150),
]
@strawberry.type
class Query:
@strawberry.field
def menu(self, category: Optional[str] = None) -> list[MenuItem]:
if category is None:
return ITEMS
return [i for i in ITEMS if i.category == category]
@strawberry.field
def item(self, id: str) -> Optional[MenuItem]:
return next((i for i in ITEMS if i.id == id), None)
schema = strawberry.Schema(query=Query)
app = FastAPI()
app.include_router(GraphQLRouter(schema), prefix="/graphql")The type hints on MenuItem and the resolver return types become the GraphQL schema. Try { menu(category: "tea") { name price } } in the editor.
What GraphQL costs you
GraphQL is powerful, but it moves complexity to the server.
The N+1 problem. If you ask for 50 orders and each order's customer, a naive resolver runs one query for the orders and then 50 more, one per customer. The usual fix is batching with a tool like DataLoader.
Expensive queries. Clients can write deeply nested queries that hammer your database. Servers limit query depth, cost and size.
Caching is harder. REST responses cache easily by URL (GET /menu). GraphQL usually sends POST requests to one URL, so you need other caching strategies.
Security still applies. A resolver must check that the caller may see each object, exactly like in REST (see *API security essentials*). Errors often come back with status 200 and an errors field, which surprises people used to HTTP status codes.
gRPC: fast calls between services
gRPC (created at Google) solves a different problem: efficient communication between backend services, for example an orders service calling a payments service. Instead of JSON over text, it uses:
Protocol Buffers (protobuf): you describe messages and services in a .proto file, and tools generate client and server code in many languages.
HTTP/2 for transport: many calls share one connection, and streaming in both directions is supported.
The result is smaller messages, faster parsing and strongly typed contracts. Browsers cannot call gRPC directly without a proxy layer (gRPC-Web), so it is mainly used behind the scenes, not for public web APIs.
syntax = "proto3";
package brewbean;
service MenuService {
rpc GetItem (GetItemRequest) returns (MenuItem);
}
message GetItemRequest {
string id = 1;
}
message MenuItem {
string id = 1;
string name = 2;
string category = 3;
int32 price = 4;
}The numbers are field tags used in the compact binary encoding. They must never be reused for a different field once clients exist.
From this file, the protobuf compiler generates code so a Python client can call stub.GetItem(GetItemRequest(id="espresso")) as if it were a local function. That style is called RPC (remote procedure call), in contrast to REST's focus on resources.
Choosing between them
REST vs GraphQL vs gRPC
| Point | REST | GraphQL | gRPC |
|---|---|---|---|
| Data format | JSON (usually) | JSON | Binary protobuf |
| Endpoints | Many URLs | One URL | Many service methods |
| Who decides the response shape | The server | The client | The contract (.proto) |
| Caching | Easy (HTTP, CDN) | Harder | Not through browsers' HTTP cache |
| Browser friendly | Yes | Yes | Needs gRPC-Web or a proxy |
| Best for | Public APIs, simple CRUD, most projects | Apps with varied screens and nested data, mobile clients | Internal service-to-service calls, streaming, low latency |
| Main cost | Over/under-fetching | Server complexity, N+1, query limits | Tooling, harder debugging by eye |
A realistic setup: a public REST or GraphQL API for the apps, and gRPC between internal services. Many successful products use only REST. Choose the simplest style that meets your needs; a new technology is not a reason by itself.
Common misconceptions
GraphQL does not replace your database or SQL; resolvers still query the database. GraphQL is not automatically faster than REST; it saves round trips, but a badly written resolver can be slower. gRPC is not "better REST"; it targets different use cases.
How this comes up in interviews. Typical questions: what is over-fetching and under-fetching; REST vs GraphQL trade-offs; what is the N+1 problem and how DataLoader helps; when would you pick gRPC; what is protobuf. A good answer names a concrete situation, for example "a mobile app on slow networks that needs data from several resources on one screen".
I am building [describe your product, clients such as web, Android app, other services, and expected traffic]. Compare REST, GraphQL and gRPC for it. For each, give two advantages and two risks for my case, then recommend one and say what would make you change your mind. Keep it under 250 words and use plain English.What to expect: A good answer ties each point to your actual clients, and does not recommend GraphQL or gRPC without a reason.
Try it: Extend the GraphQL menu
Using the Strawberry example, add a query that returns items cheaper than a given price. In Strawberry, write a resolver items_under(self, max_price: int) -> list[MenuItem]; Strawberry will expose it as itemsUnder(maxPrice: ...). Then in the GraphQL editor request only the name of items under 130 rupees.
Key takeaways
- REST is the default: simple, cacheable and universal.
- GraphQL lets the client choose exactly the fields it needs from one endpoint, which fixes over-fetching and under-fetching.
- GraphQL's costs are server complexity, the N+1 problem, query limits and harder caching. Authorisation is still your job.
- gRPC uses protobuf contracts over HTTP/2 for fast, typed calls between services; it is not designed for direct browser use.
- Choose the simplest style that fits your clients and team.
Check yourself
Finishing the quiz marks the lesson as done.
1. A mobile screen needs data from users, orders and menu items, and REST needs three round trips. Which style is designed to fix this?
2. What is the N+1 problem?
3. Which is the best fit for gRPC?
4. A GraphQL API returns HTTP 200 with an errors field in the body. What does this show?
5. Which statement is true?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.