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-incurl extension (always available, low-level) and Guzzle (Composer dependency, friendlier API). Both call the Go REST API the same way: bearer token in theAuthorization 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 pull in Composer.
Setup
Put the token in an environment variable so it never ends up in source control. PHP reads env viagetenv(). 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 viaCURLOPT_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 offending 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 lives in response headers. cURL gives you headers via a header callback (CURLOPT_HEADERFUNCTION); for brevity, the pattern below relies on the empty-batch stop condition instead. In production code, capture theX-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 code composer require guzzlehttp/guzzle | if you choose Guzzle. The cURL extension does not need Composer.
- In Laravel, the framework's code Http | facade wraps Guzzle: same patterns, code Http::withToken($token)->get(...) | .
curl_setopt_arrayis faster and tidier than callingcurl_setoptten times.- When debugging unexpected 401s, dump the request headers with code CURLINFO_HEADER_OUT | ; whitespace gremlins in the token are the usual culprit.
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.