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/guzzleif you choose Guzzle. The cURL extension does not need Composer. - In Laravel, the framework's
Httpfacade wraps Guzzle: same patterns,Http::withToken($token)->get(...). curl_setopt_arrayis faster and tidier than callingcurl_setoptten times.- When debugging unexpected 401s, dump the request headers with
CURLINFO_HEADER_OUT; stray whitespace in the token is the usual cause.
Keep going
JavaScript (Fetch API)
Browser-native fetch with async/await, bearer-token auth, error handling, and pagination.
Node.js
Server-side requests with global fetch and axios: retries, env-loaded tokens, streaming JSON.
Python (requests)
Calls with the requests library, JSON bodies, query filtering, and dataclass parsing.