← Backend← Backend
BackendBackend19 Th7, 2026Jul 19, 202618 phút đọc15 min read

APIAPIs

Thuộc bộ kiến thức Backend Roadmap.

Tổng quan

API (Application Programming Interface) là một bản hợp đồng (contract) cho phép một phần mềm giao tiếp với phần mềm khác. Nó định nghĩa bạn có thể yêu cầu điều gì, yêu cầu như thế nào, và nhận lại được gì — trong khi giấu đi phần implementation phía sau ranh giới đó. Với backend engineer, “API” gần như luôn có nghĩa là web API: một tập các operation được expose qua mạng (thường là HTTP) để browser, mobile app, các service khác và third-party developer có thể sử dụng khả năng cùng dữ liệu của hệ thống bạn.

Một API tốt là một sản phẩm, không phải thứ nghĩ sau. Nó là bề mặt mà consumer thực sự phụ thuộc vào, và một khi đã public thì rất khó thay đổi. Các họ API phổ biến bạn sẽ gặp là REST, GraphQL, gRPC, và SOAP (legacy). Mỗi loại có những đánh đổi khác nhau về transport, định dạng dữ liệu, mức độ coupling, streaming và tooling. Chọn đúng — và thiết kế contract một cách có chủ đích — là một trong những quyết định có đòn bẩy cao nhất trong công việc backend.

API-first design

API-first nghĩa là bạn thiết kế và thống nhất về API contract trước khi viết code implementation. Contract (một tài liệu OpenAPI, một GraphQL schema, một file .proto) trở thành single source of truth để các team frontend, backend và QA làm việc song song. Lợi ích:

Phần lớn web API hiện đại trao đổi bằng JSON vì nó dễ đọc với con người, phổ biến khắp nơi và native với JavaScript. Các định dạng binary như Protocol Buffers đánh đổi khả năng đọc để lấy sự gọn nhẹ và tốc độ.

Kiến thức nền tảng

Vòng đời request/response trên HTTP

Gần như mọi web API đều chạy trên HTTP. Một request mang theo một method (verb), một URL/path, các header (metadata như Content-Type, Authorization, Accept), và một body tùy chọn. Response mang theo một status code, các header, và một body tùy chọn.

POST /v1/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
Accept: application/json

{ "productId": "sku_123", "quantity": 2 }

HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/ord_789

{ "id": "ord_789", "status": "pending", "total": 49.98 }

HTTP methods

MethodMục đíchSafeIdempotent
GETĐọc một resource
POSTTạo resource / action không idempotentKhôngKhông
PUTThay thế toàn bộ resourceKhông
PATCHCập nhật một phần resourceKhôngKhông*
DELETEXóa một resourceKhông
HEADGiống GET nhưng chỉ trả header
OPTIONSKhám phá method được phép (dùng bởi CORS preflight)

*PATCH có thể được làm cho idempotent tùy vào ngữ nghĩa của patch.

Safe = không thay đổi state phía server. Idempotent = gọi cùng một request N lần cho hiệu ứng giống hệt như gọi một lần.

HTTP status codes

NhómÝ nghĩaVí dụ thường gặp
2xxThành công200 OK, 201 Created, 202 Accepted, 204 No Content
3xxRedirect301 Moved Permanently, 304 Not Modified
4xxLỗi client400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests
5xxLỗi server500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

Dùng status code cho chính xác: 401 nghĩa là “chưa authenticate”, 403 nghĩa là “đã authenticate nhưng không được phép”, 404 giấu đi sự tồn tại, 409 báo hiệu xung đột (ví dụ version mismatch), 422 nghĩa là payload đã hiểu được nhưng không hợp lệ về mặt ngữ nghĩa.

Khái niệm chính

REST

REST (Representational State Transfer) là một architectural style được Roy Fielding định nghĩa trong luận án năm 2000 của ông. Nó không phải là một protocol hay một standard — nó là một tập các ràng buộc (constraint) mà khi tuân theo sẽ tạo ra các hệ thống có khả năng mở rộng, dễ tiến hóa và loosely-coupled. Các constraint cốt lõi gồm: client–server, statelessness, cacheability, uniform interface, layered system, và tùy chọn code-on-demand.

Resource và representation

REST mô hình hóa domain thành các resource — những danh từ được định danh bằng URI. Một resource có một hoặc nhiều representation (thường là JSON). Thiết kế URL xoay quanh resource, không phải action:

GET    /v1/users              # liệt kê users
POST   /v1/users              # tạo một user
GET    /v1/users/42           # lấy user 42
PUT    /v1/users/42           # thay thế user 42
PATCH  /v1/users/42           # cập nhật một phần user 42
DELETE /v1/users/42           # xóa user 42
GET    /v1/users/42/orders    # liệt kê order thuộc về user 42

Dùng danh từ số nhiều cho collection, lồng sub-resource ở mức nông, và tránh verb trong path (/getUser, /createOrder là anti-pattern).

Statelessness

Mỗi request phải chứa mọi thứ server cần để xử lý nó — server không giữ session state của client giữa các request. Thông tin authentication đi kèm mỗi request (ví dụ một Bearer token). Chính statelessness cho phép bất kỳ server instance nào phía sau load balancer đều có thể xử lý bất kỳ request nào, tạo điều kiện cho horizontal scaling.

HATEOAS và Richardson Maturity Model

HATEOAS (Hypermedia As The Engine Of Application State) là constraint bị bỏ qua nhiều nhất của REST: response bao gồm các link cho client biết nó có thể làm gì tiếp theo, nhờ đó client điều hướng API bằng cách đi theo link thay vì hard-code URL.

{
  "id": "ord_789",
  "status": "pending",
  "total": 49.98,
  "_links": {
    "self":   { "href": "/v1/orders/ord_789" },
    "cancel": { "href": "/v1/orders/ord_789/cancel", "method": "POST" },
    "pay":    { "href": "/v1/orders/ord_789/payment", "method": "POST" }
  }
}

Richardson Maturity Model chấm điểm mức độ “RESTful” của một API:

LevelTênMô tả
0The Swamp of POXMột URI, một verb (thường là POST) — RPC trên HTTP
1ResourcesNhiều URI (resource), vẫn một verb
2HTTP VerbsDùng đúng method + status code (nơi phần lớn API “REST” đang ở)
3HypermediaHATEOAS — link điều khiển các chuyển đổi trạng thái

Versioning

API tiến hóa; versioning bảo vệ consumer hiện tại khỏi các breaking change. Các chiến lược phổ biến:

Chỉ tăng version cho các thay đổi breaking; các thay đổi mang tính bổ sung (field optional mới, endpoint mới) nên backward compatible.

Pagination, filtering, sorting

Đừng bao giờ trả về một list không giới hạn. Các kiểu pagination phổ biến:

{
  "data": [ { "id": 41 }, { "id": 42 } ],
  "pagination": {
    "limit": 20,
    "nextCursor": "eyJpZCI6NjJ9",
    "hasMore": true
  }
}

Filtering và sorting qua query param: GET /orders?status=paid&sort=-createdAt&minTotal=100.

Idempotency

Với các operation không idempotent (như POST thanh toán), hãy để client gửi một idempotency key để request bị retry không tạo ra bản ghi trùng lặp:

POST /v1/payments
Idempotency-Key: 5f3a9c2e-...

{ "amount": 4998, "currency": "usd" }

Server lưu key cùng với kết quả; một request lặp lại với cùng key sẽ trả về response gốc thay vì charge hai lần. Đây là cách Stripe và nhiều bên khác làm cho retry trở nên an toàn.

Ví dụ JSON đầy đủ

GET /v1/products?category=books&limit=2 HTTP/1.1
Accept: application/json
{
  "data": [
    {
      "id": "prd_1",
      "name": "Clean Architecture",
      "price": { "amount": 3499, "currency": "usd" },
      "inStock": true
    },
    {
      "id": "prd_2",
      "name": "Designing Data-Intensive Applications",
      "price": { "amount": 5499, "currency": "usd" },
      "inStock": false
    }
  ],
  "pagination": { "limit": 2, "nextCursor": "eyJpZCI6InByZF8yIn0", "hasMore": true }
}

GraphQL

GraphQL là một query language và runtime cho API, được tạo tại Facebook và hiện thuộc GraphQL Foundation. Thay vì nhiều endpoint, nó expose một endpoint duy nhất và một schema có kiểu chặt chẽ (strongly-typed); client yêu cầu chính xác các field cần thiết trong một round trip.

Schema

Schema chính là contract, được viết bằng SDL (Schema Definition Language):

type User {
  id: ID!
  name: String!
  email: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
}

enum OrderStatus { PENDING PAID CANCELLED }

type Query {
  user(id: ID!): User
  users(limit: Int = 20): [User!]!
}

type Mutation {
  createOrder(userId: ID!, productId: ID!): Order!
}

type Subscription {
  orderStatusChanged(orderId: ID!): Order!
}

Query và resolver

Một query yêu cầu một hình dạng chính xác; response phản chiếu đúng hình dạng đó:

query GetUserWithOrders {
  user(id: "42") {
    name
    email
    orders {
      id
      total
      status
    }
  }
}
{
  "data": {
    "user": {
      "name": "Ada",
      "email": "ada@example.com",
      "orders": [
        { "id": "ord_1", "total": 49.98, "status": "PAID" }
      ]
    }
  }
}

Mỗi field được backing bởi một resolver — một hàm trả về dữ liệu cho field đó. Các resolver kết hợp với nhau: field orders trên User được resolve bởi một hàm riêng, hàm này nhận User cha làm tham số.

Over-fetching, under-fetching và N+1

Điểm bán hàng chính của GraphQL là loại bỏ over-fetching (lấy về những field bạn không cần) và under-fetching (phải gọi nhiều REST call để ghép nên một màn hình). Client chỉ định chính xác cây dữ liệu nó muốn.

Cạm bẫy kinh điển của GraphQL là N+1 problem: resolve một list N user rồi resolve orders cho từng user sẽ bắn 1 query cho user cộng thêm N query cho order của họ. Cách khắc phục chuẩn là DataLoader — nó gom (batch) N lượt tra cứu theo từng item thành một query gom chung và cache trong phạm vi một request.

// Không batch: 1 + N query
// Với DataLoader: 1 (users) + 1 (orders đã gom) query
const orderLoader = new DataLoader(async (userIds) => {
  const rows = await db.orders.whereIn('userId', userIds);
  return userIds.map((id) => rows.filter((r) => r.userId === id));
});

Đánh đổi REST vs GraphQL

GraphQL tỏa sáng với các UI hướng client, nặng về tổng hợp dữ liệu (mobile app, dashboard). Chi phí: HTTP caching khó hơn (mọi thứ là POST tới một endpoint), độ phức tạp của query phải bị giới hạn để tránh lạm dụng, và server phức tạp hơn. REST giữ cho CRUD đơn giản luôn đơn giản và tận dụng HTTP caching miễn phí.

gRPC

gRPC là một RPC framework hiệu năng cao từ Google, dùng Protocol Buffers (protobuf) cho schema/serialization và HTTP/2 cho transport. Bạn định nghĩa service và message trong một file .proto, rồi generate code client và server có kiểu chặt chẽ ở nhiều ngôn ngữ.

syntax = "proto3";
package shop.v1;

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc WatchOrders(WatchRequest) returns (stream Order); // server streaming
}

message GetOrderRequest { string id = 1; }

message Order {
  string id = 1;
  double total = 2;
  Status status = 3;
  enum Status { PENDING = 0; PAID = 1; CANCELLED = 2; }
}

Các đặc tính chính:

Khi nào dùng gRPC: giao tiếp service-to-service nội bộ trong backend microservices/đa ngôn ngữ, các đường (path) yêu cầu latency thấp và throughput cao, và các workload streaming. Khi nào không dùng: API public hướng browser (browser không nói được raw gRPC nếu không có gRPC-Web và một proxy), hoặc khi payload dễ đọc với con người và khả năng curl dễ dàng là quan trọng.

SOAP (legacy)

SOAP (Simple Object Access Protocol) là một protocol nhắn tin cũ hơn, dựa trên XML. Contract được mô tả bằng WSDL, message là các envelope XML, và nó mang theo một tập nặng các standard WS-* (WS-Security, WS-ReliableMessaging, v.v.). Nó transport-agnostic (thường là HTTP, đôi khi message queue) và strongly typed.

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <getOrder xmlns="http://example.com/shop">
      <id>ord_789</id>
    </getOrder>
  </soap:Body>
</soap:Envelope>

Bạn sẽ chủ yếu gặp SOAP trong các hệ thống enterprise, ngân hàng và legacy của chính phủ. Với công việc mới, hãy ưu tiên REST, GraphQL hoặc gRPC — nhưng biết rằng SOAP tồn tại (và tại sao tính hình thức của nó từng có giá trị) vẫn hữu ích.

Tài liệu API và contract

OpenAPI / Swagger

OpenAPI (trước đây là Swagger) là specification chuẩn, không phụ thuộc ngôn ngữ, để mô tả các REST API. Một tài liệu YAML/JSON duy nhất mô tả path, method, parameter, schema request/response, auth và ví dụ. Từ nó bạn có thể generate tài liệu tương tác (Swagger UI), client SDK, server stubcontract test.

openapi: 3.0.3
info:
  title: Shop API
  version: 1.0.0
paths:
  /v1/orders/{id}:
    get:
      summary: Get an order by ID
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: The order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          description: Not found
components:
  schemas:
    Order:
      type: object
      properties:
        id:     { type: string }
        total:  { type: number, format: float }
        status: { type: string, enum: [pending, paid, cancelled] }
      required: [id, total, status]

Với GraphQL, chính schema là contract và cho phép tooling dựa trên introspection (GraphiQL, Apollo Studio). Với gRPC, file .proto là contract. Ở cả ba, contract có thể đọc được bằng máy — chứ không phải văn bản mô tả — mới là source of truth.

Các mối quan tâm xuyên suốt (cross-cutting)

Authentication và authorization

Mọi API không public đều cần biết ai đang gọi (authentication) và họ được phép làm gì (authorization). Các cơ chế phổ biến: API key, OAuth 2.0 / OpenID Connect, và JWT bearer token. Xem Authentication & AuthorizationWeb Security để đi sâu. Luôn truyền credential qua TLS và không bao giờ đặt trong URL.

CORS

CORS (Cross-Origin Resource Sharing) là một cơ chế bảo mật của browser kiểm soát origin nào được phép gọi API của bạn từ JavaScript. Browser gửi một request preflight OPTIONS cho các request không đơn giản; server của bạn phải phản hồi với đúng các header Access-Control-Allow-*.

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true

CORS chỉ ảnh hưởng tới browser; nó không thay thế cho authorization phía server.

Rate limiting và throttling

Bảo vệ API khỏi lạm dụng và quá tải bằng cách giới hạn số request một client có thể thực hiện trong một khoảng thời gian (token-bucket, sliding-window, v.v.). Truyền đạt giới hạn bằng các header chuẩn và trả về 429 Too Many Requests kèm Retry-After khi vượt ngưỡng:

HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 1000
RateLimit-Remaining: 0
RateLimit-Reset: 42
Retry-After: 42

Định dạng lỗi (RFC 7807 / RFC 9457)

Trả về lỗi theo một hình dạng nhất quán, đọc được bằng máy. RFC 7807 “Problem Details for HTTP APIs” (được cập nhật bởi RFC 9457) định nghĩa application/problem+json:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
  "type": "https://example.com/probs/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "quantity must be greater than 0",
  "instance": "/v1/orders",
  "errors": [
    { "field": "quantity", "message": "must be > 0" }
  ]
}

Một error contract ổn định cho phép client lập trình dựa trên lỗi thay vì phải parse văn bản mô tả.

Content negotiation

Client khai báo thứ nó có thể chấp nhận và gửi đi; server phản hồi tương ứng. Được điều khiển bằng header:

Nếu server không thể đáp ứng header Accept thì nó trả về 406 Not Acceptable.

Real-time

Mô hình request-response của REST/GraphQL không phù hợp với nhu cầu real-time dựa trên push. Với các nhu cầu đó, hãy dùng WebSocket, SSE, GraphQL subscription, hoặc gRPC streaming — xem Real-time Communication.

So sánh: REST vs GraphQL vs gRPC vs SOAP

Tiêu chíRESTGraphQLgRPCSOAP
TransportHTTP/1.1, HTTP/2HTTP (một endpoint)HTTP/2HTTP, SMTP, MQ
Định dạng payloadJSON (thường), bất kỳJSONProtobuf (binary)XML
ContractOpenAPI (tùy chọn)Schema (SDL, bắt buộc).proto (bắt buộc)WSDL (bắt buộc)
CouplingLỏngTrung bìnhChặt (stub generated)Chặt
StreamingHạn chế (SSE/chunked)SubscriptionsFirst-class (4 chế độ)Không (native)
CachingTuyệt vời (HTTP)Khó (client-side)Hạn chếHạn chế
Thân thiện browserKhông (cần gRPC-Web)Có (dài dòng)
Use case tốt nhấtWeb API public/CRUDUI hướng client, tổng hợp dữ liệuMicroservices nội bộ, latency thấpLegacy/enterprise, WS-*
ToolingRất lớn, trưởng thànhMạnh (Apollo, v.v.)Mạnh (codegen)Trưởng thành nhưng nặng

Best Practices

Tài liệu tham khảo

Part of the Backend Roadmap knowledge base.

Overview

An API (Application Programming Interface) is a contract that lets one piece of software talk to another. It defines what you can ask for, how you ask, and what you get back — while hiding the implementation behind the boundary. For backend engineers, “API” almost always means a web API: a set of operations exposed over the network (usually HTTP) so that browsers, mobile apps, other services, and third-party developers can consume your system’s capabilities and data.

A good API is a product, not an afterthought. It is the surface your consumers actually depend on, and once it is public it becomes very hard to change. The dominant families you will encounter are REST, GraphQL, gRPC, and the legacy SOAP. Each makes different trade-offs around transport, data format, coupling, streaming, and tooling. Choosing well — and designing the contract deliberately — is one of the highest-leverage decisions in backend work.

API-first design

API-first means you design and agree on the API contract before writing implementation code. The contract (an OpenAPI document, a GraphQL schema, a .proto file) becomes the single source of truth that frontend, backend, and QA teams work against in parallel. Benefits:

Most modern web APIs exchange JSON because it is human-readable, ubiquitous, and native to JavaScript. Binary formats like Protocol Buffers trade readability for compactness and speed.

Fundamentals

The request/response cycle over HTTP

Nearly all web APIs ride on HTTP. A request carries a method (verb), a URL/path, headers (metadata such as Content-Type, Authorization, Accept), and an optional body. The response carries a status code, headers, and an optional body.

POST /v1/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
Accept: application/json

{ "productId": "sku_123", "quantity": 2 }

HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/ord_789

{ "id": "ord_789", "status": "pending", "total": 49.98 }

HTTP methods

MethodPurposeSafeIdempotent
GETRead a resourceYesYes
POSTCreate a resource / non-idempotent actionNoNo
PUTReplace a resource fullyNoYes
PATCHUpdate a resource partiallyNoNo*
DELETERemove a resourceNoYes
HEADLike GET but headers onlyYesYes
OPTIONSDiscover allowed methods (used by CORS preflight)YesYes

*PATCH can be made idempotent depending on the patch semantics.

Safe = does not modify server state. Idempotent = making the same call N times has the same effect as making it once.

HTTP status codes

RangeMeaningCommon examples
2xxSuccess200 OK, 201 Created, 202 Accepted, 204 No Content
3xxRedirection301 Moved Permanently, 304 Not Modified
4xxClient error400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests
5xxServer error500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

Use codes precisely: 401 means “not authenticated”, 403 means “authenticated but not allowed”, 404 hides existence, 409 signals a conflict (e.g., version mismatch), 422 means the payload was understood but semantically invalid.

Key Concepts

REST

REST (Representational State Transfer) is an architectural style defined by Roy Fielding in his 2000 dissertation. It is not a protocol or a standard — it is a set of constraints that, when followed, yield scalable, evolvable, loosely-coupled systems. The core constraints are: client–server, statelessness, cacheability, uniform interface, layered system, and optionally code-on-demand.

Resources and representations

REST models the domain as resources — nouns identified by URIs. A resource has one or more representations (typically JSON). Design URLs around resources, not actions:

GET    /v1/users              # list users
POST   /v1/users              # create a user
GET    /v1/users/42           # get user 42
PUT    /v1/users/42           # replace user 42
PATCH  /v1/users/42           # partially update user 42
DELETE /v1/users/42           # delete user 42
GET    /v1/users/42/orders    # list orders belonging to user 42

Use plural nouns for collections, nest sub-resources shallowly, and avoid verbs in paths (/getUser, /createOrder are anti-patterns).

Statelessness

Each request must contain everything the server needs to process it — the server keeps no client session state between requests. Authentication travels on every request (e.g., a Bearer token). Statelessness is what allows any server instance behind a load balancer to handle any request, enabling horizontal scaling.

HATEOAS and the Richardson Maturity Model

HATEOAS (Hypermedia As The Engine Of Application State) is REST’s most-ignored constraint: responses include links telling the client what it can do next, so clients navigate the API by following links rather than hard-coding URLs.

{
  "id": "ord_789",
  "status": "pending",
  "total": 49.98,
  "_links": {
    "self":   { "href": "/v1/orders/ord_789" },
    "cancel": { "href": "/v1/orders/ord_789/cancel", "method": "POST" },
    "pay":    { "href": "/v1/orders/ord_789/payment", "method": "POST" }
  }
}

The Richardson Maturity Model grades how “RESTful” an API is:

LevelNameDescription
0The Swamp of POXSingle URI, single verb (usually POST) — RPC over HTTP
1ResourcesMultiple URIs (resources), still one verb
2HTTP VerbsProper use of methods + status codes (where most “REST” APIs live)
3HypermediaHATEOAS — links drive state transitions

Versioning

APIs evolve; versioning protects existing consumers from breaking changes. Common strategies:

Bump the version only for breaking changes; additive changes (new optional fields, new endpoints) should be backward compatible.

Pagination, filtering, sorting

Never return an unbounded list. Common pagination styles:

{
  "data": [ { "id": 41 }, { "id": 42 } ],
  "pagination": {
    "limit": 20,
    "nextCursor": "eyJpZCI6NjJ9",
    "hasMore": true
  }
}

Filtering and sorting via query params: GET /orders?status=paid&sort=-createdAt&minTotal=100.

Idempotency

For non-idempotent operations (like POST payments), let clients send an idempotency key so a retried request does not create a duplicate:

POST /v1/payments
Idempotency-Key: 5f3a9c2e-...

{ "amount": 4998, "currency": "usd" }

The server stores the key with the result; a repeat with the same key returns the original response instead of charging twice. This is how Stripe and others make retries safe.

A full JSON example

GET /v1/products?category=books&limit=2 HTTP/1.1
Accept: application/json
{
  "data": [
    {
      "id": "prd_1",
      "name": "Clean Architecture",
      "price": { "amount": 3499, "currency": "usd" },
      "inStock": true
    },
    {
      "id": "prd_2",
      "name": "Designing Data-Intensive Applications",
      "price": { "amount": 5499, "currency": "usd" },
      "inStock": false
    }
  ],
  "pagination": { "limit": 2, "nextCursor": "eyJpZCI6InByZF8yIn0", "hasMore": true }
}

GraphQL

GraphQL is a query language and runtime for APIs, created at Facebook and now under the GraphQL Foundation. Instead of many endpoints, it exposes a single endpoint and a strongly-typed schema; clients ask for exactly the fields they need in one round trip.

Schema

The schema is the contract, written in SDL (Schema Definition Language):

type User {
  id: ID!
  name: String!
  email: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
}

enum OrderStatus { PENDING PAID CANCELLED }

type Query {
  user(id: ID!): User
  users(limit: Int = 20): [User!]!
}

type Mutation {
  createOrder(userId: ID!, productId: ID!): Order!
}

type Subscription {
  orderStatusChanged(orderId: ID!): Order!
}

Queries and resolvers

A query asks for a precise shape; the response mirrors it exactly:

query GetUserWithOrders {
  user(id: "42") {
    name
    email
    orders {
      id
      total
      status
    }
  }
}
{
  "data": {
    "user": {
      "name": "Ada",
      "email": "ada@example.com",
      "orders": [
        { "id": "ord_1", "total": 49.98, "status": "PAID" }
      ]
    }
  }
}

Each field is backed by a resolver — a function that returns the data for that field. Resolvers compose: the orders field on User is resolved by a separate function that receives the parent User as its argument.

Over-fetching, under-fetching, and N+1

GraphQL’s main selling point is eliminating over-fetching (getting fields you do not need) and under-fetching (needing several REST calls to assemble one screen). The client specifies the exact tree it wants.

The classic GraphQL pitfall is the N+1 problem: resolving a list of N users and then resolving orders for each one fires 1 query for the users plus N queries for their orders. The standard fix is DataLoader — it batches the N per-item lookups into a single batched query and caches within a request.

// Without batching: 1 + N queries
// With DataLoader: 1 (users) + 1 (batched orders) queries
const orderLoader = new DataLoader(async (userIds) => {
  const rows = await db.orders.whereIn('userId', userIds);
  return userIds.map((id) => rows.filter((r) => r.userId === id));
});

REST vs GraphQL trade-offs

GraphQL shines for client-driven, aggregation-heavy UIs (mobile apps, dashboards). Costs: HTTP caching is harder (everything is a POST to one endpoint), query complexity must be bounded to prevent abuse, and the server is more complex. REST keeps simple CRUD simple and leverages HTTP caching for free.

gRPC

gRPC is a high-performance RPC framework from Google that uses Protocol Buffers (protobuf) for the schema/serialization and HTTP/2 for transport. You define services and messages in a .proto file, then generate strongly-typed client and server code in many languages.

syntax = "proto3";
package shop.v1;

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc WatchOrders(WatchRequest) returns (stream Order); // server streaming
}

message GetOrderRequest { string id = 1; }

message Order {
  string id = 1;
  double total = 2;
  Status status = 3;
  enum Status { PENDING = 0; PAID = 1; CANCELLED = 2; }
}

Key properties:

When to use gRPC: internal service-to-service communication in a microservices/polyglot backend, low-latency and high-throughput paths, and streaming workloads. When not to: public browser-facing APIs (browsers cannot speak raw gRPC without gRPC-Web and a proxy), or when human-readable payloads and easy curl-ability matter.

SOAP (legacy)

SOAP (Simple Object Access Protocol) is an older, XML-based messaging protocol. Contracts are described in WSDL, messages are XML envelopes, and it brings a heavy set of WS-* standards (WS-Security, WS-ReliableMessaging, etc.). It is transport-agnostic (often HTTP, sometimes message queues) and strongly typed.

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <getOrder xmlns="http://example.com/shop">
      <id>ord_789</id>
    </getOrder>
  </soap:Body>
</soap:Envelope>

You will mostly meet SOAP in enterprise, banking, and legacy government systems. For new work, prefer REST, GraphQL, or gRPC — but knowing SOAP exists (and why its formality was once valuable) is useful.

API documentation and contracts

OpenAPI / Swagger

OpenAPI (formerly Swagger) is the standard, language-agnostic specification for describing REST APIs. A single YAML/JSON document describes paths, methods, parameters, request/response schemas, auth, and examples. From it you can generate interactive docs (Swagger UI), client SDKs, server stubs, and contract tests.

openapi: 3.0.3
info:
  title: Shop API
  version: 1.0.0
paths:
  /v1/orders/{id}:
    get:
      summary: Get an order by ID
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: The order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          description: Not found
components:
  schemas:
    Order:
      type: object
      properties:
        id:     { type: string }
        total:  { type: number, format: float }
        status: { type: string, enum: [pending, paid, cancelled] }
      required: [id, total, status]

For GraphQL, the schema itself is the contract and enables introspection-driven tooling (GraphiQL, Apollo Studio). For gRPC, the .proto file is the contract. In all three, the machine-readable contract — not prose — is the source of truth.

Cross-cutting concerns

Authentication and authorization

Every non-public API needs to know who is calling (authentication) and what they may do (authorization). Common mechanisms: API keys, OAuth 2.0 / OpenID Connect, and JWT bearer tokens. See Authentication & Authorization and Web Security for depth. Always transmit credentials over TLS and never in the URL.

CORS

CORS (Cross-Origin Resource Sharing) is a browser security mechanism controlling which origins may call your API from JavaScript. The browser sends a preflight OPTIONS request for non-simple requests; your server must respond with the right Access-Control-Allow-* headers.

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true

CORS only affects browsers; it is not a substitute for server-side authorization.

Rate limiting and throttling

Protect your API from abuse and overload by capping how many requests a client may make in a window (token-bucket, sliding-window, etc.). Communicate limits with standard headers and return 429 Too Many Requests with Retry-After when exceeded:

HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 1000
RateLimit-Remaining: 0
RateLimit-Reset: 42
Retry-After: 42

Error formats (RFC 7807 / RFC 9457)

Return errors in a consistent, machine-readable shape. RFC 7807 “Problem Details for HTTP APIs” (updated by RFC 9457) defines application/problem+json:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
  "type": "https://example.com/probs/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "quantity must be greater than 0",
  "instance": "/v1/orders",
  "errors": [
    { "field": "quantity", "message": "must be > 0" }
  ]
}

A stable error contract lets clients program against errors instead of parsing prose.

Content negotiation

Clients declare what they can accept and send; servers respond accordingly. Driven by headers:

If the server cannot honor the Accept header it responds 406 Not Acceptable.

Real-time

REST/GraphQL request-response does not fit push-based, real-time needs. For those, reach for WebSockets, SSE, GraphQL subscriptions, or gRPC streaming — see Real-time Communication.

Comparison: REST vs GraphQL vs gRPC vs SOAP

DimensionRESTGraphQLgRPCSOAP
TransportHTTP/1.1, HTTP/2HTTP (single endpoint)HTTP/2HTTP, SMTP, MQ
Payload formatJSON (usually), anyJSONProtobuf (binary)XML
ContractOpenAPI (optional)Schema (SDL, required).proto (required)WSDL (required)
CouplingLooseMediumTight (generated stubs)Tight
StreamingLimited (SSE/chunked)SubscriptionsFirst-class (4 modes)No (natively)
CachingExcellent (HTTP)Hard (client-side)LimitedLimited
Browser-friendlyYesYesNo (needs gRPC-Web)Yes (verbose)
Best use casePublic/CRUD web APIsClient-driven, aggregated UIsInternal microservices, low-latencyLegacy/enterprise, WS-*
ToolingHuge, matureStrong (Apollo, etc.)Strong (codegen)Mature but heavy

Best Practices

References