In short
API design is the discipline of defining the contract between a client and a service: what can be asked, in what shape, with what errors and guarantees. Three contract styles dominate today - OpenAPI (a machine-readable description of an HTTP/JSON API), GraphQL (a typed query language where the client selects exactly the fields it needs) and gRPC (a binary, contract-first remote procedure call framework over HTTP/2). MCA and B.Tech syllabi examine them under "web services and service-oriented architecture", and placement rounds at Flipkart, Swiggy, Zomato, Razorpay, Zerodha, PhonePe, Jio, Infosys, TCS, Wipro, Freshworks and Zoho routinely ask which style suits a given problem and why. In Indian public digital infrastructure the same three appear: ONDC and Account Aggregator publish OpenAPI/JSON contracts, while internal microservice fabrics in large product companies are mostly gRPC.
Explained
Why a contract-first API at all
- An API contract is the single source of truth: it drives server stubs, client SDKs, documentation, mock servers and automated tests, so front-end and back-end teams work in parallel.
- Design-first: write the schema/spec, review it, then generate code. Code-first: write annotated code, then generate the spec. Design-first is preferred where many teams or external partners consume the API.
- Four qualities judged in any design review: granularity (chatty vs coarse), evolvability (can a field be added without breaking clients), discoverability (can a new developer self-serve) and performance (bytes and round trips).
- Common failure modes a contract prevents: silent type changes, undocumented error shapes, inconsistent pagination, and clients hardcoding undocumented behaviour.
OpenAPI (formerly Swagger)
- What it is: a YAML/JSON *description* of an HTTP API - paths, methods, parameters, request and response bodies, status codes, security schemes. It is a specification format, not a protocol and not a runtime.
- Versions: Swagger 2.0 → OpenAPI 3.0 → OpenAPI 3.1, which aligns the schema language with JSON Schema 2020-12 (so
$ref,oneOf,anyOf,constandexamplesbehave as in JSON Schema). The spec is maintained under the Linux Foundation and has continued past 3.1 - check the spec site for the exact current patch/minor release before quoting one. - Document structure:
openapi,info,servers,paths,components(reusableschemas,parameters,responses,securitySchemes),tags,security, andwebhooks(added in 3.1). - Tooling: Swagger UI / Redoc for docs,
openapi-generatorandswagger-codegenfor client and server stubs in Java, Python, TypeScript, Go, C#; Prism/WireMock for mock servers; Spectral for style linting; Dredd/Schemathesis for contract testing; API gateways (Kong, Apigee, AWS API Gateway) import a spec to configure routing, validation and rate limits directly. - Strengths: works with ordinary HTTP caching, proxies, CDNs and browser tooling; easiest to hand to an external partner; mature governance and linting.
- Limits: describes request/response only - it cannot express a graph of related data in one call; over-fetching and under-fetching remain the client's problem; event-driven and streaming APIs are described better by AsyncAPI.
- A sibling format, JSON:API, standardises response shape; gRPC and GraphQL carry their own schema formats, so OpenAPI is not used for them.
GraphQL
- What it is: a typed query language and runtime, published as the GraphQL Specification (the October 2021 edition is the last ratified one; work continues in draft). A single endpoint, usually
POST /graphql, accepts a query naming exactly the fields wanted; the response mirrors the query shape. - Type system (SDL): scalars (
Int,Float,String,Boolean,ID, plus custom scalars),type(object),input(argument objects),enum,interface,union, lists[T]and non-nullT!. - Three root operation types:
Query(read),Mutation(write, executed serially),Subscription(a stream of events, usually over WebSocket or SSE). - Resolvers: one function per field; the engine walks the query tree calling resolvers with
(parent, args, context, info). Query fields resolve in parallel, mutations in series. - Client features: named operations, variables (
$id: ID!), fragments for reuse, aliases to request the same field twice, directives@include(if:),@skip(if:)and@deprecated(reason:), and introspection (__schema) which powers GraphiQL and codegen. - The N+1 problem: a list of N parents each resolving a child field issues N extra queries. Fixed by batching and per-request caching (DataLoader pattern) or by a join in the parent resolver.
- Pagination: the Relay cursor connection pattern -
edges { cursor node { } },pageInfo { hasNextPage endCursor }- is the conventional answer; offset pagination is allowed but drifts when rows are inserted. - Errors: transport status is normally 200 with a top-level
errorsarray alongsidedata; partial success is normal, which surprises teams used to HTTP status codes. - Security and cost control: depth limiting, query complexity/cost analysis, persisted (allow-listed) queries, disabling introspection in production, per-field authorisation, and timeouts. An unguarded public GraphQL endpoint is a denial-of-service surface.
- Caching: no URL-level cache, because every request is a POST to one path; solved with persisted queries over GET, normalised client caches (Apollo Client, Relay, urql) and server-side caching per resolver.
- Scaling across teams: schema stitching or Apollo Federation (subgraphs composed into one supergraph by a router) lets independent services own parts of one schema.
- Best fit: mobile and web clients with varied, evolving data needs; a BFF (backend-for-frontend) layer over many downstream services.
gRPC and Protocol Buffers
- What it is: a Google-originated, CNCF-hosted RPC framework. The service and messages are defined in a
.protofile; the compiler (protoc) generates typed client stubs and server skeletons in Java, Go, Python, C++, C#, Node.js, Kotlin and more. - Protocol Buffers (proto3): a compact binary encoding. Each field has a field number (the tag on the wire) and a type; names are not transmitted. Scalars,
enum, nested messages,repeated,map<K,V>,oneof,Any, and well-known types (Timestamp,Duration,FieldMask,Struct). - Wire compatibility rules: never reuse or renumber a field tag; mark removed tags
reserved; adding an optional field is backward compatible; changing a field's type generally is not; unknown fields are preserved on round-trip in proto3. - Four call types: unary (one request, one response), server streaming, client streaming, bidirectional streaming - all multiplexed over one HTTP/2 connection.
- Why it is fast: HTTP/2 multiplexing (no head-of-line blocking at the application level), HPACK header compression, persistent connections, and binary framing instead of text parsing.
- Operational features: deadlines/timeouts propagated to downstream calls, cancellation, metadata (headers), interceptors (the gRPC equivalent of middleware), channel-level load balancing, retries with backoff, health-checking and reflection services, TLS/mTLS, and status codes (
OK,INVALID_ARGUMENT,NOT_FOUND,DEADLINE_EXCEEDED,UNAVAILABLE,PERMISSION_DENIEDand others - not HTTP codes). - Limits: browsers cannot speak raw gRPC, so gRPC-Web with a proxy (Envoy) or grpc-gateway (which exposes a JSON/REST façade from the same
.proto) is needed; payloads are not human-readable, so debugging needsgrpcurlor logging interceptors; HTTP caching and simple curl testing are lost. - Best fit: internal service-to-service calls, polyglot microservices, low-latency and high-throughput paths, streaming telemetry, mobile clients on poor networks.
Choosing between them, and combining them
- A very common production shape: OpenAPI/REST or GraphQL at the edge for browsers, apps and partners; gRPC inside between microservices.
- Decide by: who the consumer is (browser, partner, internal service), how variable the data needs are, whether streaming is required, cacheability, and team maturity with tooling.
- Keep one style per boundary. Three protocols on one boundary triples the authentication, logging, tracing and rate-limiting work.
- Cross-cutting concerns are the same whichever style is chosen: authentication, authorisation, rate limiting, idempotency keys for writes, correlation IDs, structured errors, versioning, observability (OpenTelemetry) and documentation.
Versioning, governance and Indian practice
- OpenAPI/HTTP: version in the path (
/v1/), or by media type; publish a deprecation window andDeprecation/Sunsetheaders. - GraphQL: avoid versions - add fields, mark old ones
@deprecated(reason:), remove only after usage telemetry shows zero traffic. - gRPC: version the proto package (
package billing.v1;) and keep tags stable; a new major version is a new package. - Indian government and regulated APIs: MeitY's Policy on Open APIs for Government of India (2015) requires departments to publish open, documented APIs; API Setu, DigiLocker and UIDAI Aadhaar authentication/e-KYC publish versioned HTTP contracts with digital-signature and encryption requirements. NPCI's UPI and the RBI-regulated Account Aggregator ecosystem (ReBIT-specified FIU/FIP APIs) use documented HTTP/JSON or XML contracts with mandatory signing. ONDC uses the Beckn protocol, published as an OpenAPI contract with asynchronous callbacks.
- Data protection: API design should expose the minimum personal data needed per endpoint and avoid PII in URLs or logs. Under the DPDP Rules, 2025 the Data Fiduciary duties (notice, consent, breach intimation, erasure) and the omission of IT Act s.43A take effect only from 13 May 2027; until then s.43A and the SPDI Rules apply. CERT-In's 2022 directions require incident reporting and log retention, which affects API gateway logging design. Sector rules (RBI, IRDAI, SEBI) add their own API security and audit requirements.
Key points
- API contract - the machine-readable agreement that generates docs, stubs, mocks and tests.
- OpenAPI - a description format for HTTP/JSON APIs; 3.1 aligns with JSON Schema 2020-12; not a protocol.
- Swagger - the older name and the tool family (Swagger UI, codegen) around OpenAPI.
- GraphQL - one endpoint, a typed schema, client-selected fields; response shape mirrors the query.
- Resolver - the per-field function that fetches data; the execution engine walks the query tree.
- Over-fetching / under-fetching - receiving unused fields, or needing several calls to assemble one screen; GraphQL's main motivation.
- N+1 problem - N extra child queries for N parents; solved by DataLoader-style batching.
- Cursor connections - the Relay pagination convention (
edges,node,pageInfo,endCursor). - Query complexity limiting - reject expensive queries by computed cost or depth; strongly recommended on any public GraphQL endpoint.
- Persisted queries - allow-listed query hashes sent over GET, restoring HTTP caching and blocking arbitrary queries.
- gRPC - contract-first binary RPC over HTTP/2 with generated stubs in many languages.
- Protocol Buffers - compact binary encoding; field *numbers*, not names, go on the wire.
- Four gRPC call types - unary, server streaming, client streaming, bidirectional streaming.
- Deadline propagation - a caller's timeout travels to downstream calls, preventing pile-ups.
- gRPC-Web / grpc-gateway - the bridges needed because browsers cannot speak raw gRPC.
- Backward compatibility - add optional fields,
reservedremoved tags, never renumber or retype. - Federation / stitching - composing team-owned GraphQL subgraphs into one supergraph.
- Typical architecture - OpenAPI or GraphQL at the edge, gRPC between internal services.
Formulas and examples
Payload efficiency η = useful bytes / total bytes = 1 / over-fetch ratio
Query count for N parents, naive: Q = 1 + N; with batching: Q = 2
Total latency T = R × RTT + Σ(server processing time), R = number of sequential round trips (ms)
GraphQL complexity cost C = Σ over fields of (field cost × product of enclosing list sizes)
Bandwidth B = payload size × requests per second × duration (bytes); 1 GB = 10⁹ bytes here
Compression saving = 1 − (new size / old size)
Example 1 - over-fetching on a mobile list screen. A REST endpoint returns a full user object of 2,400 bytes; the screen uses only id, name, avatarUrl - 180 bytes.
- Over-fetch ratio = 2,400 / 180 ≈ 13.3; efficiency η ≈ 7.5%.
- For 50 users: REST = 50 × 2,400 = 120,000 bytes (120 kB); GraphQL selection = 50 × 180 = 9,000 bytes (9 kB).
- Saving = 1 − 9,000/120,000 = 92.5% before any gzip.
Example 2 - the N+1 problem costed. 50 authors are listed, each with their book count. One DB round trip = 8 ms.
- Naive: Q = 1 + 50 = 51 queries; T = 51 × 8 = 408 ms.
- With DataLoader batching (one authors query + one batched books query): Q = 2; T = 2 × 8 = 16 ms.
- Improvement factor = 408/16 = 25.5×.
Example 3 - GraphQL query cost check. Rule: each object node costs 1; cost is multiplied by list sizes. Query asks posts(first: 50) { comments(first: 20) { author { name } } }.
- Posts = 50; comments = 50 × 20 = 1,000; authors = 1,000 × 1 = 1,000.
- C = 50 + 1,000 + 1,000 = 2,050. Against a server limit of 1,000 the query is rejected before execution.
- Reducing to
first: 20posts andfirst: 10comments: C = 20 + 200 + 200 = 420 - accepted.
Example 4 - gRPC/Protobuf versus JSON bandwidth and egress cost. The same telemetry message is 142 bytes as JSON and 38 bytes as protobuf, at 1,000 requests per second.
- Per-second: JSON = 142,000 B/s; protobuf = 38,000 B/s.
- Per day (86,400 s): JSON = 142,000 × 86,400 = 12,268,800,000 B ≈ 12.27 GB; protobuf = 38,000 × 86,400 = 3,283,200,000 B ≈ 3.28 GB.
- Saving = 1 − 38/142 = 73.2%.
- At an illustrative egress price of ₹7 per GB (cloud list prices vary by provider, region and commit - verify before quoting): 30-day JSON ≈ 368 GB ≈ ₹2,576; protobuf ≈ 98.5 GB ≈ ₹690; monthly saving ≈ ₹1,886 on this one stream.
Example 5 - round trips on a 4G connection. RTT = 120 ms; server processing = 20 ms per call. A profile screen needs user, then orders, then product details (each depends on the previous).
- REST, 3 sequential calls: T = 3 × 120 + 3 × 20 = 360 + 60 = 420 ms.
- GraphQL, one call resolving all three server-side (resolvers 20 + 20 + 20 ms): T = 120 + 60 = 180 ms.
- Saving = 240 ms ≈ 57%. If the three REST calls were independent and issued in parallel over HTTP/2, T ≈ 120 + 20 = 140 ms - so GraphQL's gain comes from removing *dependent* round trips, not from HTTP itself.
| OpenAPI / HTTP+JSON | GraphQL | gRPC | |
|---|---|---|---|
| Contract artefact | OpenAPI YAML/JSON | SDL schema | .proto file |
| Wire format | Text JSON | Text JSON | Binary protobuf |
| Transport | HTTP/1.1 or HTTP/2 | HTTP (usually POST), WS for subscriptions | HTTP/2 only |
| Endpoints | Many URLs | One endpoint | One per service method |
| Client picks fields | No | Yes | No |
| Streaming | SSE / WebSocket separately | Subscriptions | Native, four modes |
| Browser support | Native | Native | Needs gRPC-Web proxy |
| HTTP caching | Easy (GET + ETag) | Hard (POST) | Not applicable |
| Codegen maturity | Good | Good (typed clients) | Excellent |
| Error model | HTTP status codes | 200 + errors array | gRPC status codes |
| Human-readable on wire | Yes | Yes | No |
| Typical use | Public/partner APIs | BFF for apps and web | Internal microservices |
syntax = "proto3";
package catalog.v1;
service Catalog {
rpc GetProduct (GetProductRequest) returns (Product); // unary
rpc WatchPrice (GetProductRequest) returns (stream PriceTick); // server streaming
}
message GetProductRequest { string sku = 1; }
message Product {
string sku = 1;
string name = 2;
int64 paise = 3; // price in paise - integer money, never float
reserved 4; // 'mrp' removed in v1.3; tag never reused
}
type Query { product(sku: ID!): Product }
type Product { sku: ID! name: String! pricePaise: Int! reviews(first: Int = 10): ReviewConnection! }
# client asks for only what it needs
query P($sku: ID!) { product(sku: $sku) { name pricePaise } }
Where it is used
- E-commerce and consumer apps: Flipkart, Myntra, Swiggy, Zomato, Meesho - GraphQL or a BFF for app screens, gRPC between pricing, inventory, search and payment services.
- Fintech and broking: Razorpay, PhonePe, Zerodha Kite Connect and Upstox publish OpenAPI-documented HTTP APIs to merchants and algo traders, with webhooks for asynchronous events; internal ledger and risk services use gRPC.
- Government and public digital infrastructure: API Setu, DigiLocker, UIDAI Aadhaar authentication/e-KYC, GSTN return APIs, NPCI UPI, ReBIT-specified Account Aggregator APIs, ONDC's Beckn contracts - all documented HTTP/JSON or XML with signing requirements.
- IT services and product engineering roles: API designer, backend developer, integration engineer and solution architect posts at TCS, Infosys, Wipro, Cognizant, Accenture and Freshworks ask for OpenAPI-driven contract-first delivery and API gateway configuration.
- Telecom, OTT and IoT: Jio, Airtel, Hotstar and smart-metering platforms use gRPC and protobuf for high-volume telemetry, device control and inter-service calls where bytes and latency matter.
- Platform and DevOps: Kubernetes, etcd and Envoy expose gRPC APIs; service meshes, API gateways and OpenTelemetry pipelines are configured from these same contracts.
Interview questions
- Q: What problem does GraphQL solve that a well-designed HTTP API does not? A: It lets the client specify exactly which fields it needs across related entities in one request, removing over-fetching and dependent round trips without the server adding a new endpoint per screen.
- Q: Why does GraphQL usually return HTTP 200 even for errors? A: A GraphQL response can be partially successful, so errors are reported per field in a top-level
errorsarray alongside whateverdataresolved; transport-level failures still use HTTP status codes. - Q: What is the N+1 problem and how do you fix it? A: Resolving a child field for N parents issues N additional queries; it is fixed by batching keys within a request (DataLoader), joining in the parent resolver, or caching per request.
- Q: How do you stop a malicious GraphQL query? A: Depth and complexity limits computed before execution, pagination caps, persisted/allow-listed queries, disabled introspection in production, per-field authorisation and server timeouts.
- Q: Why is gRPC faster than JSON over HTTP/1.1? A: Protobuf is a compact binary encoding with no field names on the wire, and HTTP/2 gives multiplexing, header compression and persistent connections, so both bytes and round trips drop.
- Q: Can a browser call a gRPC service directly? A: Not raw gRPC - browsers cannot control HTTP/2 frames, so you use gRPC-Web with an Envoy proxy, or expose a JSON façade via grpc-gateway.
- Q: What changes to a
.protofile are backward compatible? A: Adding a new optional field with an unused tag, adding enum values, or renaming a field (names are not on the wire). Reusing or renumbering a tag, or changing a field's type, breaks compatibility. - Q: OpenAPI versus GraphQL schema - are they alternatives? A: No. OpenAPI *describes* an HTTP API after the fact; a GraphQL schema *is* the API's executable contract. You would not write an OpenAPI document for a GraphQL endpoint beyond the single POST path.
- Q: What did OpenAPI 3.1 change? A: It aligned the schema language with JSON Schema 2020-12, added
webhooks, and allowed richer$refusage - so arbitrary JSON Schema keywords now validate correctly. - Q: How do you version a GraphQL API? A: Normally you do not - add fields, mark old ones
@deprecatedwith a reason, monitor field-level usage, and remove only when traffic is zero. - Q: Which would you choose for an internal service called 50,000 times a second, and why? A: gRPC - binary payloads, one multiplexed HTTP/2 connection, generated typed stubs, deadlines and streaming; readability and browser access are not needed internally.
- Q: What is a deadline in gRPC and why does it matter more than a client-side timeout? A: A deadline is an absolute time propagated with the call to downstream services, so the whole chain abandons work together instead of each hop waiting out its own timeout and piling up load.
Common mistakes
- Treating GraphQL as a database query language exposed to clients - it is a schema over resolvers you control, and unguarded nesting is a denial-of-service risk.
- Writing resolvers without batching and then blaming GraphQL for slow queries that are really N+1 database calls.
- Assuming an HTTP 200 from a GraphQL endpoint means success, or ignoring the
errorsarray becausedatawas present. - Confusing Swagger (the tool family and the old 2.0 format) with OpenAPI (the current specification), and mixing 2.0 and 3.x keywords in one document.
- Reusing a removed protobuf field number, or changing a field's type, and then debugging corrupted values at runtime.
- Choosing gRPC for a public or browser-facing API and discovering late that partners want curl, caching and readable payloads.
- Representing money as
float/doublein a proto or JSON schema instead of integer paise or a decimal string, and losing rounding accuracy. - Letting the generated spec drift from the running code - no contract test in CI, so the documentation is confidently wrong.
Related topics
REST APIs · Authentication and authorisation: sessions, OAuth and JWT · Spring Boot and microservices development · Back-end development with Node.js · TypeScript and Angular · Web services, SOAP and message queues