Skip to content

PHP (cURL + Guzzle)

php-curl walkthrough with proper option setting, plus the Guzzle equivalent for production code.

PHP has two reasonable HTTP clients: the built-in curl extension (always available, low-level) and Guzzle (Composer dependency, friendlier API). Both call the GoRest API the same way: bearer token in the Authorization header, JSON body, JSON response. This guide walks through cURL first because it works on any PHP install, then shows the Guzzle equivalent for projects that already use Composer.

Setup

Put the token in an environment variable so it never ends up in source control. PHP reads env via getenv(). Simple and reliable.

<?php
// Requires PHP 8 or newer (uses the throw-expression syntax).

const API   = "https://gorest.co.in/public/v2";
$token = getenv("GOREST_TOKEN") ?: throw new RuntimeException("Set GOREST_TOKEN");

If you are on PHP 7, use the longer form instead, since the ?: throw new expression is PHP 8+:

<?php
// PHP 7 alternative

const API = "https://gorest.co.in/public/v2";
$token = getenv("GOREST_TOKEN");
if (!$token) {
    throw new RuntimeException("Set GOREST_TOKEN");
}

A small wrapper

Defining a single function for every API call cleans up the rest of your code. This one returns both the status code and the decoded body so callers can branch on either:

function gorest(string $method, string $path, ?array $body = null): array {
    global $token;

    $ch = curl_init(API . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => [
            "Authorization: Bearer $token",
            "Accept: application/json",
            "Content-Type: application/json",
        ],
        CURLOPT_TIMEOUT        => 15,
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }

    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error  = $raw === false ? curl_error($ch) : null;
    curl_close($ch);

    if ($raw === false) {
        throw new RuntimeException("cURL error: $error");
    }

    return [
        "status" => $status,
        "body"   => $raw === "" ? null : json_decode($raw, true),
    ];
}

List, fetch, write

$r = gorest("GET", "/users?status=active&page=1");
if ($r["status"] !== 200) {
    throw new RuntimeException("List users -> " . $r["status"]);
}
foreach ($r["body"] as $user) {
    echo $user["email"] . PHP_EOL;
}

Create, update, delete

The PHP cURL extension handles the body via CURLOPT_POSTFIELDS. When you pass a JSON-encoded string and the right Content-Type, the API treats it the same as a real JSON request.

// Create
$r = gorest("POST", "/users", [
    "name"   => "Avani Iyer",
    "email"  => "avani-" . time() . "@example.com",
    "gender" => "female",
    "status" => "active",
]);
if ($r["status"] !== 201) {
    throw new RuntimeException("Create -> " . $r["status"]);
}
$userId = $r["body"]["id"];

// Update
gorest("PATCH", "/users/$userId", ["status" => "inactive"]);

// Delete
$r = gorest("DELETE", "/users/$userId");
assert($r["status"] === 204);

Validation errors come back as 422 with field-level messages. Always show them next to the wrong input rather than as a generic "request failed":

$r = gorest("POST", "/users", ["name" => "x"]);
if ($r["status"] === 422) {
    foreach ($r["body"] as $err) {
        echo $err["field"] . ": " . $err["message"] . PHP_EOL;
    }
}

Pagination

Pagination metadata is in response headers. cURL gives you headers via a header callback (CURLOPT_HEADERFUNCTION); to keep it short, the pattern below relies on the empty-batch stop condition instead. In production code, capture the X-Pagination-Pages header for accurate looping:

function each_user(array $filters = []): Generator {
    $page = 1;
    while (true) {
        $query = http_build_query(array_merge($filters, ["page" => $page]));
        $r = gorest("GET", "/users?$query");
        if ($r["status"] !== 200) {
            throw new RuntimeException("List -> " . $r["status"]);
        }
        if ($r["body"] === []) return;
        foreach ($r["body"] as $u) yield $u;
        // Caller-side: read X-Pagination-Pages from a real header callback.
        // For brevity, this loop relies on the empty-array stop condition.
        $page += 1;
        if ($page > 1000) return; // safety
    }
}

foreach (each_user(["status" => "active"]) as $u) {
    echo $u["email"] . PHP_EOL;
}

Same calls in Guzzle

If your project already uses Composer, Guzzle removes most of the cURL boilerplate. JSON encoding, response decoding, and exception-based error handling are all built in.

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

$client = new Client([
    "base_uri" => "https://gorest.co.in/public/v2/",
    "headers"  => [
        "Authorization" => "Bearer " . getenv("GOREST_TOKEN"),
        "Accept"        => "application/json",
    ],
    "timeout"  => 15.0,
]);

$res  = $client->get("users", ["query" => ["status" => "active"]]);
$rows = json_decode($res->getBody(), true);

try {
    $client->post("users", ["json" => ["name" => "x"]]);
} catch (ClientException $e) {
    if ($e->getResponse()->getStatusCode() === 422) {
        $errors = json_decode($e->getResponse()->getBody(), true);
        // [{ field: "...", message: "..." }, ...]
    }
}

Tips

  • Run composer require guzzlehttp/guzzle if you choose Guzzle. The cURL extension does not need Composer.
  • In Laravel, the framework's Http facade wraps Guzzle: same patterns, Http::withToken($token)->get(...).
  • curl_setopt_array is faster and tidier than calling curl_setopt ten times.
  • When debugging unexpected 401s, dump the request headers with CURLINFO_HEADER_OUT; stray whitespace in the token is the usual cause.
Related guides

Keep going