HTTP Clients And APIs

REST, RESTful APIs, RPC, GraphQL, And gRPC

REST, RPC, GraphQL, and gRPC are different ways to expose operations across a network. PHP developers commonly consume REST and JSON RPC-style APIs, may build or consume GraphQL, and sometimes integrate with gRPC through generated clients, gateways, or sidecar services.

The useful skill is not arguing that one style is always best. The useful skill is recognizing the interaction style, understanding its contract, and writing PHP code that handles its request shape, error model, timeout behavior, authentication, idempotency, and tooling.

Start With The Interaction Shape

Ask what the client is trying to do:

  • retrieve or modify resource state
  • issue a business command
  • select a flexible graph of fields
  • stream messages
  • make typed internal service calls

The answer matters more than fashion. A public CRUD API, a checkout command, a mobile feed, and an internal high-volume service boundary may deserve different styles.

Every style still needs security, versioning, observability, failure handling, and compatibility testing. A protocol choice can make some contracts easier to express; it does not remove distributed-system work.

REST And HTTP Resources

REST-oriented HTTP APIs organize around resources identified by URLs and use standard HTTP methods.

PHP example
<?php

declare(strict_types=1);

$restExamples = [
    'list users' => 'GET /v1/users',
    'get one user' => 'GET /v1/users/42',
    'create user' => 'POST /v1/users',
    'replace user' => 'PUT /v1/users/42',
    'delete user' => 'DELETE /v1/users/42',
];

foreach ($restExamples as $operation => $request) {
    echo $operation . ': ' . $request . PHP_EOL;
}

// Prints:
// list users: GET /v1/users
// get one user: GET /v1/users/42
// create user: POST /v1/users
// replace user: PUT /v1/users/42
// delete user: DELETE /v1/users/42

REST fits public APIs, CRUD-style resources, cacheable reads, browser-friendly tooling, OpenAPI documentation, and systems where HTTP status codes and intermediaries are important.

Use HTTP semantics honestly. GET should not create an export or send an email. 201 Created should mean creation. 202 Accepted should mean work was accepted but is not complete. 204 No Content should not contain JSON. 409 Conflict, 412 Precondition Failed, and 429 Too Many Requests are not interchangeable.

RPC For Explicit Commands

RPC means remote procedure call. The API is organized around actions or commands, such as calculateTax, sendInvoice, orders.cancel, or refundPayment.

PHP example
<?php

declare(strict_types=1);

function rpcRequest(string $method, array $params): array
{
    return [
        'jsonrpc' => '2.0',
        'method' => $method,
        'params' => $params,
        'id' => 'req_123',
    ];
}

echo json_encode(rpcRequest('orders.cancel', ['order_id' => 42]), JSON_THROW_ON_ERROR) . PHP_EOL;

// Prints:
// {"jsonrpc":"2.0","method":"orders.cancel","params":{"order_id":42},"id":"req_123"}

RPC fits action-heavy workflows that do not map cleanly to resources. It can be clearer to expose POST /orders.cancel than invent a fake resource just to avoid a verb.

The risk is inconsistency. A collection of action endpoints can drift into different naming, status, authentication, retry, and error conventions. Define an operation catalog, envelope, error codes, idempotency rules, and generated or documented clients.

GraphQL For Client-Selected Data

GraphQL exposes a typed schema and lets clients choose fields through queries. Most GraphQL APIs use a single HTTP endpoint such as POST /graphql.

PHP example
<?php

declare(strict_types=1);

$graphqlRequest = [
    'query' => <<<'GRAPHQL'
query Product($id: ID!) {
  product(id: $id) {
    id
    name
    price
  }
}
GRAPHQL,
    'variables' => ['id' => 'prod_123'],
];

echo array_key_first($graphqlRequest['variables']) . PHP_EOL;

// Prints:
// id

GraphQL is useful when clients need flexible data selection across related objects. It can reduce over-fetching and make UI-driven data requirements explicit.

It also shifts complexity. A single request can trigger many resolvers and backend calls. Limit depth, complexity, aliases, and result size. Use batching to avoid N+1 queries. Cache carefully because clients can ask for many shapes. Track operation names and error categories rather than only HTTP status.

GraphQL errors may appear inside a 200 OK response with partial data. Clients must understand the GraphQL error contract as well as transport failures.

gRPC And Protocol Buffers

gRPC uses service definitions and Protocol Buffer messages to generate typed clients and servers. Instead of hand-writing URLs, clients call generated methods.

service ProductService {
  rpc GetProduct (GetProductRequest) returns (Product);
}

In PHP, gRPC usually means generated classes plus the gRPC extension or a compatible client library. It is common in controlled internal service-to-service systems where strict message contracts, efficient binary encoding, streaming, and generated clients matter more than browser readability.

Protocol Buffer field numbers are the durable wire identity. Do not reuse a removed field number for a different meaning. Adding optional fields is often compatible. Changing field type, oneof membership, required semantics, or default meaning needs careful review.

Clients should send deadlines. Servers should stop work when calls are cancelled where possible. Streaming adds backpressure, ordering, cancellation, and partial-delivery concerns.

Classification Is Only A Starting Point

Real systems mix styles. A Laravel application might expose REST to partners, GraphQL to its frontend, JSON RPC for internal admin commands, and gRPC to a search service.

PHP example
<?php

declare(strict_types=1);

function likelyApiStyle(string $method, string $path, ?string $operation = null): string
{
    if ($operation !== null && str_contains($operation, 'Service.')) {
        return 'gRPC';
    }

    if ($path === '/graphql') {
        return 'GraphQL';
    }

    if (strtoupper($method) === 'POST' && preg_match('#^/[a-z]+[A-Z][A-Za-z]*$#', $path)) {
        return 'RPC';
    }

    return 'REST';
}

foreach ([
    ['GET', '/v1/products/123', null],
    ['POST', '/cancelOrder', null],
    ['POST', '/graphql', null],
    ['POST', '', 'ProductService.GetProduct'],
] as [$method, $path, $operation]) {
    echo likelyApiStyle($method, $path, $operation) . PHP_EOL;
}

// Prints:
// REST
// RPC
// GraphQL
// gRPC

After classification, ask better questions: where is the contract documented, what are errors shaped like, how do retries work, are clients generated, and who owns compatibility?

Errors, Retries, And Idempotency

For every operation, define success, validation rejection, authentication failure, authorization denial, state conflict, rate limit, temporary dependency failure, and unexpected server failure.

Timeouts create ambiguity. The client may not know whether the server committed the operation. Resource creation can accept a client-generated identifier. Command-style APIs can accept idempotency keys. Long work can return an operation resource. gRPC can carry deadlines and status codes. GraphQL may need mutation idempotency rules despite using one endpoint.

These choices are part of the API contract, not an implementation detail.

Observability Across Styles

Record operation identity in a bounded form: HTTP route template, RPC method, GraphQL operation name, or gRPC service and method. Do not use raw URLs, arbitrary GraphQL documents, or user-provided identifiers as metric labels.

Propagate correlation or trace context across gateways and downstream calls. For GraphQL, one transport request can contain several resolver failures. For streaming gRPC, measure time to first message, stream duration, cancellations, and message counts. For asynchronous HTTP operations, trace the accepted request into the worker and status resource.

Preserve protocol semantics. A gateway that converts every error to HTTP 200 or one generic gRPC status makes dashboards simple while making clients unreliable.

Gateways And Migration

Many systems expose one style externally while using another internally. A GraphQL edge service may call REST services. A public REST API may call gRPC backends through a gateway. A legacy JSON RPC API may receive a REST facade during migration.

Gateways should preserve semantics deliberately. Map authentication failures, validation errors, conflicts, rate limits, and timeouts consistently. Do not collapse every backend error into a generic 500 or every GraphQL failure into transport 200 without a documented client contract.

During migration, support old and new styles long enough for consumers to move. Run contract tests for both paths and compare representative responses. A new API style is not an excuse to change field names, permissions, pagination, or retry behavior accidentally.

Versioning looks different in each style, but the discipline is the same: make compatibility promises explicit before clients depend on them. REST APIs often version through URL prefixes, media types, or headers. RPC APIs may version method names, envelopes, or service packages. GraphQL usually keeps one evolving schema, marks fields as deprecated, and measures usage before removal. gRPC and protobuf rely heavily on field-number compatibility, package names, and generated-client release discipline.

Do not confuse a new transport shape with a new product contract. Moving GET /orders/{id} behind a GraphQL field or gRPC method still requires the same tenant checks, money format, timestamp precision, and cancellation semantics. If the migration intentionally changes behavior, publish it as a versioned change and give consumers examples of both the old and new behavior.

Client ownership also affects the choice. A public API used by unknown integrators needs conservative compatibility and readable diagnostics. An internal gRPC boundary owned by two teams can move faster if both teams run generated-client compatibility checks. A frontend GraphQL schema can evolve quickly when the frontend and backend release together, but mobile clients with older app versions need longer deprecation windows.

When a team cannot name the supported client population, choose the more conservative contract. Unknown clients make removals, enum changes, and status-code changes harder to coordinate. Internal-only protocols still need documentation, but the migration plan can rely on known owners, shared CI, and scheduled releases.

Contract And Compatibility Testing

REST and HTTP RPC need request/response schema tests, status assertions, and OpenAPI or equivalent documentation. GraphQL needs schema checks, resolver integration tests, query complexity tests, deprecated-field monitoring, and generated type checks. gRPC needs protobuf compatibility checks and generated-client smoke tests across supported languages.

Run tests through the same gateways and proxies used in production where possible. Header handling, body size, compression, timeouts, and status mapping can differ from in-process handlers.

Choosing A Style

Use REST when resources, standard HTTP semantics, caching, public documentation, and many client types matter.

Use RPC when the domain is command-heavy and clear operation names are more honest than artificial resources.

Use GraphQL when clients genuinely need flexible graph-shaped selection and the team can operate schema governance, resolver performance, and query limits.

Use gRPC when service boundaries are controlled, generated clients are acceptable, efficient typed messages or streaming matter, and the infrastructure supports HTTP/2 and protobuf tooling.

Prototype one representative success and one representative failure before choosing. Measure payload shape, latency, debugging experience, generated code, observability, and deployment complexity.

What To Check

Before moving on, make sure you can:

  • recognize resource-oriented REST endpoints
  • recognize action-oriented RPC endpoints
  • describe how GraphQL requests, partial data, and errors differ from REST
  • explain why gRPC uses service definitions and generated clients
  • define retry and idempotency behavior for side effects
  • identify observability labels for each style
  • choose a style based on consumers, operations, tooling, and infrastructure

What You Should Be Able To Do

After this lesson, you should be able to distinguish resource-oriented HTTP, command-oriented RPC, GraphQL selection, and gRPC services. You should be able to explain each style's contract, error model, retry concerns, operational costs, and compatibility testing needs.

Practice

Practice: Classify API Styles

Write a PHP function that classifies example API operations as REST, RPC, GraphQL, or gRPC.

Requirements

  • Accept a method, path, and optional operation name.
  • Classify /graphql as GraphQL.
  • Classify service-style operation names such as ProductService.GetProduct as gRPC.
  • Classify action-style POST endpoints such as /cancelOrder as RPC.
  • Treat ordinary resource paths such as /v1/products/123 as REST.
  • Return a short reason for the classification.
Show solution

This solution uses simple signals rather than pretending the boundary is always perfect. Many real APIs mix styles.

PHP example
<?php

declare(strict_types=1);

function classifyApiStyle(string $method, string $path, ?string $operation = null): array
{
    if ($operation !== null && preg_match('/^[A-Z][A-Za-z]+Service\.[A-Z][A-Za-z]+$/', $operation)) {
        return ['style' => 'gRPC', 'reason' => 'The operation is service-method shaped.'];
    }

    if ($path === '/graphql') {
        return ['style' => 'GraphQL', 'reason' => 'GraphQL commonly uses a single /graphql endpoint.'];
    }

    if (strtoupper($method) === 'POST' && preg_match('#^/[a-z]+[A-Z][A-Za-z]*$#', $path)) {
        return ['style' => 'RPC', 'reason' => 'The endpoint is named like an action.'];
    }

    return ['style' => 'REST', 'reason' => 'The endpoint is resource-oriented.'];
}

$examples = [
    classifyApiStyle('GET', '/v1/products/123'),
    classifyApiStyle('POST', '/cancelOrder'),
    classifyApiStyle('POST', '/graphql'),
    classifyApiStyle('POST', '', 'ProductService.GetProduct'),
];

foreach ($examples as $result) {
    echo $result['style'] . ': ' . $result['reason'] . PHP_EOL;
}

// Prints:
// REST: The endpoint is resource-oriented.
// RPC: The endpoint is named like an action.
// GraphQL: GraphQL commonly uses a single /graphql endpoint.
// gRPC: The operation is service-method shaped.

The classification helps you ask the next questions: where is the contract documented, how are errors represented, and whether the client code is handwritten or generated.

Practice: Choose An API Style

Choose a style for four product requirements.

Task

For each case, choose REST, RPC, GraphQL, gRPC, or a deliberate mix:

  • public partner API for products, orders, and customers
  • checkout operation that reserves stock, calculates tax, and authorizes payment
  • frontend dashboard that needs different nested fields on different screens
  • internal high-volume inventory service used by three controlled backend services

For each answer, justify contract documentation, error handling, client generation, observability, and retry/idempotency behavior.

Show solution

A public partner API is a strong REST candidate because resource URLs, HTTP status codes, OpenAPI documentation, pagination, caching, and broad client compatibility matter. Some action endpoints may still exist, but the core contract should be stable and easy to inspect.

Checkout is command-heavy. RPC or a REST command resource such as POST /checkouts/{id}/confirm can be honest. The design must document idempotency keys, timeout ambiguity, conflict responses, and a status resource or reconciliation path.

The dashboard may fit GraphQL if screens need flexible nested selections and the team can operate query complexity limits, resolver batching, authorization checks, and schema evolution. A simpler REST backend-for-frontend may be enough if the field combinations are predictable.

The internal inventory service may fit gRPC when consumers are controlled, generated clients are acceptable, and low-latency typed calls or streaming updates matter. Deadlines, protobuf compatibility, generated-client tests, and observability by service method are required.

Practice: Review GraphQL Risks

A team wants to replace several REST endpoints with one GraphQL endpoint for a product catalog.

Task

Write a review note covering:

  • resolver N+1 risks
  • query depth and complexity limits
  • authorization at field and object level
  • partial data and error handling
  • caching differences from REST
  • schema deprecation policy
  • operation names and observability
  • when REST may still be simpler
Show solution

GraphQL may help the catalog UI request exactly the fields it needs, but resolver design must avoid one query per product or category. Use batching and request-scoped loading for related data.

Set depth, complexity, alias, and result-size limits before exposing the endpoint. Authorization must be enforced at object and field boundaries, not only by hiding fields in the schema. Clients must handle partial data plus errors because GraphQL can return both in one response.

Caching is harder than caching GET /products/{id} because many query shapes share one endpoint. Consider persisted operations or application-level caches for common views. Track operation names, resolver latency, error categories, and downstream calls rather than only HTTP status.

Deprecate fields before removal and measure usage. REST may remain simpler for public product reads, file downloads, cache-heavy lists, and integrations where broad tooling and straightforward HTTP semantics matter more than flexible selection.