
Chapter 23: Working with JSON & APIs
Overview
Modern web applications rarely work in isolation. Whether you're integrating with payment gateways, weather services, social media platforms, or building your own API, you need to exchange data with other systems. JSON (JavaScript Object Notation) has become the universal language for this data exchange.
In this chapter, you'll learn how to encode and decode JSON, consume external APIs using cURL, handle errors gracefully, and build your own simple REST API endpoints. By the end, you'll be able to integrate any web service into your PHP applications.
Prerequisites
- PHP 8.4 installed and accessible from your command line (Chapter 00)
- Understanding of arrays and associative arrays (Chapter 06)
- Familiarity with error handling (Chapter 11)
- Basic understanding of HTTP (GET, POST requests)
- A text editor
- Estimated time: 25 minutes
What You'll Build
By the end of this chapter, you will have created:
- JSON encoding and decoding scripts with error handling
- A cURL-based API client that fetches weather data
- A GitHub API integration that retrieves user information
- Your own simple REST API endpoint
- Error handling for API failures and JSON errors
- Working examples of all major JSON and API patterns
All examples will be working PHP scripts you can run immediately.
Quick Start
Want to see JSON and API calls in action right away? Create this file and run it:
<?php
// filename: quick-api.php
declare(strict_types=1);
// Encode PHP data to JSON
$user = [
'name' => 'Alice Johnson',
'email' => 'alice@example.com',
'roles' => ['admin', 'user']
];
$json = json_encode($user, JSON_PRETTY_PRINT);
echo "JSON Output:\n" . $json . PHP_EOL;
// Decode JSON back to PHP
$decoded = json_decode($json, true);
echo "\nDecoded Name: " . $decoded['name'] . PHP_EOL;
// Make an API request (using a free test API)
$ch = curl_init('https://api.github.com/users/github');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'PHP-Tutorial');
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "\nGitHub User: " . $data['name'] . PHP_EOL;
echo "Public Repos: " . $data['public_repos'] . PHP_EOL;# Run it
php quick-api.phpExpected output:
JSON Output:
{
"name": "Alice Johnson",
"email": "alice@example.com",
"roles": [
"admin",
"user"
]
}
Decoded Name: Alice Johnson
GitHub User: GitHub
Public Repos: 296This compact example shows JSON encoding, decoding, and making an actual API call. Now let's build this understanding step by step.
Objectives
- Understand JSON format and its relationship to PHP arrays
- Encode PHP data to JSON strings
- Decode JSON strings to PHP data structures
- Handle JSON encoding and decoding errors
- Make HTTP requests using cURL
- Consume REST APIs with proper error handling
- Build a simple REST API endpoint
- Work with common API authentication patterns
Step 1: JSON Basics (~5 min)
Goal
Master JSON encoding and decoding with proper error handling.
Actions
- Create a file named
json-basics.php:
# filename: json-basics.php
<?php
declare(strict_types=1);
// Encode PHP array to JSON
$products = [
[
'id' => 1,
'name' => 'Laptop',
'price' => 999.99,
'in_stock' => true
],
[
'id' => 2,
'name' => 'Mouse',
'price' => 29.99,
'in_stock' => false
]
];
// Basic encoding
$json = json_encode($products);
echo "Compact JSON:\n" . $json . "\n\n";
// Pretty-printed JSON (easier to read)
$prettyJson = json_encode($products, JSON_PRETTY_PRINT);
echo "Pretty JSON:\n" . $prettyJson . "\n\n";
// Decode JSON string back to PHP array
$decoded = json_decode($prettyJson, true); // true = return associative array
echo "First product name: " . $decoded[0]['name'] . PHP_EOL;
// Without true, you get an object
$decodedAsObject = json_decode($prettyJson);
echo "First product name (object): " . $decodedAsObject[0]->name . PHP_EOL;- Add error handling for encoding/decoding failures:
{# filename: json-error-handling.php #}
<?php
declare(strict_types=1);
// Invalid UTF-8 sequences will cause encoding to fail
$invalid = [
'name' => "Invalid \xB1\x31 data"
];
$json = json_encode($invalid);
if ($json === false) {
echo "JSON encoding failed: " . json_last_error_msg() . PHP_EOL;
} else {
echo "Success: " . $json . PHP_EOL;
}
// Decoding invalid JSON
$badJson = '{"name": "Bob", "age": }'; // Missing value
$decoded = json_decode($badJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "JSON decoding failed: " . json_last_error_msg() . PHP_EOL;
}- Run each script to inspect the output:
php json-basics.php
php json-error-handling.phpExpected Result
Compact JSON:
[{"id":1,"name":"Laptop","price":999.99,"in_stock":true},{"id":2,"name":"Mouse","price":29.99,"in_stock":false}]
Pretty JSON:
[
{
"id": 1,
"name": "Laptop",
"price": 999.99,
"in_stock": true
},
{
"id": 2,
"name": "Mouse",
"price": 29.99,
"in_stock": false
}
]
First product name: Laptop
First product name (object): Laptop
JSON encoding failed: Malformed UTF-8 characters, possibly incorrectly encoded
JSON decoding failed: Syntax errorWhy It Works
json_encode()converts PHP arrays/objects into JSON strings.json_decode()converts JSON strings back to PHP data structures; passingtruereturns associative arrays, whilefalsereturns objects.json_last_error_msg()reveals the reason for encoding/decoding failures, which is critical for debugging malformed or non-UTF-8 data.
Troubleshooting
json_encodereturns false — Verify input strings are valid UTF-8 or enableJSON_PARTIAL_OUTPUT_ON_ERRORfor best-effort encoding.- Decoded data is
null— Checkjson_last_error()for decoding issues; ensure the JSON string is valid. - Large numbers lose precision — Use
JSON_BIGINT_AS_STRINGwhen decoding to keep big integers as strings.
JSON Error Handling
Always check for errors when working with JSON:
<?php
declare(strict_types=1);
// Invalid UTF-8 sequences will cause encoding to fail
$invalid = [
'name' => "Invalid \xB1\x31 data"
];
$json = json_encode($invalid);
if ($json === false) {
echo "JSON encoding failed: " . json_last_error_msg() . PHP_EOL;
} else {
echo "Success: " . $json . PHP_EOL;
}
// Decoding invalid JSON
$badJson = '{"name": "Bob", "age": }'; // Missing value
$decoded = json_decode($badJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "JSON decoding failed: " . json_last_error_msg() . PHP_EOL;
}JSON Options
Common json_encode() options:
JSON_PRETTY_PRINT- Format with indentationJSON_UNESCAPED_UNICODE- Don't escape unicode charactersJSON_UNESCAPED_SLASHES- Don't escape forward slashesJSON_THROW_ON_ERROR- Throw exceptions instead of returning false (PHP 7.3+)
Step 2: Making API Requests with cURL (~8 min)
Goal
Use cURL to consume external REST APIs with proper error handling.
Actions
- Create a file named
api-client-curl.php:
# filename: api-client-curl.php
<?php
declare(strict_types=1);
/**
* Fetch user data from GitHub API
*/
function fetchGitHubUser(string $username): ?array
{
$url = "https://api.github.com/users/" . urlencode($username);
// Initialize cURL session
$ch = curl_init($url);
// Set options
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return response as string
curl_setopt($ch, CURLOPT_USERAGENT, 'PHP-Tutorial'); // GitHub requires user agent
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 second timeout
// Execute request
$response = curl_exec($ch);
// Check for cURL errors
if ($response === false) {
echo "cURL Error: " . curl_error($ch) . PHP_EOL;
curl_close($ch);
return null;
}
// Get HTTP status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
echo "HTTP Error: " . $httpCode . PHP_EOL;
return null;
}
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "JSON Decode Error: " . json_last_error_msg() . PHP_EOL;
return null;
}
return $data;
}
$user = fetchGitHubUser('github');
if ($user) {
echo "Name: " . $user['name'] . PHP_EOL;
echo "Public Repos: " . $user['public_repos'] . PHP_EOL;
}- Run the script:
php api-client-curl.phpExpected Result
Name: GitHub
Public Repos: 296Why It Works
curl_setoptflags configure cURL to return data, follow redirects, and set timeouts.- Handling HTTP status codes ensures success responses before decoding JSON.
- Decoding the JSON response yields an associative array for easy access to fields like
nameandpublic_repos.
Troubleshooting
cURL Error: Could not resolve host— Check your internet connection or DNS settings.- HTTP Error responses (401, 403) — API may require authentication or a valid User-Agent.
- JSON decode errors — Log the raw response to inspect API output before decoding.
Step 3: Building a REST API Endpoint (~7 min)
Goal
Create a simple REST endpoint that returns JSON using plain PHP.
Actions
- Create an API endpoint
public/api/posts.php:
# filename: public/api/posts.php
<?php
declare(strict_types=1);
header('Content-Type: application/json');
$posts = [
['id' => 1, 'title' => 'Hello JSON', 'content' => 'JSON is great for APIs'],
['id' => 2, 'title' => 'Consuming APIs', 'content' => 'cURL makes it easy'],
];
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
echo json_encode($posts, JSON_PRETTY_PRINT);
exit;
}
echo json_encode(['error' => 'Method not allowed'], JSON_PRETTY_PRINT);
http_response_code(405);- Serve the endpoint and test it:
php -S localhost:8000 -t public
curl http://localhost:8000/api/posts.phpExpected Result
[
{
"id": 1,
"title": "Hello JSON",
"content": "JSON is great for APIs"
},
{
"id": 2,
"title": "Consuming APIs",
"content": "cURL makes it easy"
}
]Why It Works
- Setting the
Content-Typeheader ensures clients treat the response as JSON. json_encodeconverts the posts array into a properly formatted string.- Returning a 405 status code for unsupported methods follows REST best practices.
Troubleshooting
- Blank output — Ensure
json_encodeis called and no syntax errors occurred before the response. Cannot redeclare header()warnings — Make sure no output is sent before theheader()call.curlshows HTML instead of JSON — Confirm you’re hitting the correct endpoint and not the default router.
Step 4: Adding Authentication (~6 min)
Goal
Demonstrate a simple token-based authentication check for API requests.
Actions
- Update your endpoint to require an API token:
# filename: public/api/posts.php
<?php
declare(strict_types=1);
header('Content-Type: application/json');
$token = $_GET['token'] ?? '';
if ($token !== 'secret123') {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$posts = [
['id' => 1, 'title' => 'Hello JSON', 'content' => 'JSON is great for APIs'],
['id' => 2, 'title' => 'Consuming APIs', 'content' => 'cURL makes it easy'],
];
echo json_encode($posts, JSON_PRETTY_PRINT);- Call the endpoint with the token:
curl "http://localhost:8000/api/posts.php?token=secret123"Expected Result
Requests without the token return a 401 Unauthorized response with an error message. Requests with the correct token return the JSON payload.
Why It Works
- A simple token check enforces authentication before serving data.
- Returning 401 status codes communicates auth errors clearly to clients.
Troubleshooting
- Always unauthorized — Confirm the query string includes
token=secret123and matches exactly, case-sensitive. - Token visible in URL — For sensitive data, use headers (
Authorization: Bearer) instead of query parameters.
Step 5: Handling JSON Errors (~4 min)
Goal
Gracefully handle encoding/decoding errors and provide actionable feedback.
Actions
- Create a utility function for safe decoding:
# filename: json-safe-decode.php
<?php
declare(strict_types=1);
function decodeJson(string $json): array
{
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON decode failed: ' . json_last_error_msg());
}
return $data;
}
try {
$payload = decodeJson('{"foo": "bar"}');
print_r($payload);
$broken = decodeJson('{"foo": }');
} catch (RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
}- Run the helper:
php json-safe-decode.phpExpected Result
Array
(
[foo] => bar
)
JSON decode failed: Syntax errorWhy It Works
- Wrapping
json_decodein a helper centralizes error handling and keeps calling code clean. - Throwing exceptions clarifies which part of the pipeline failed, aiding debugging.
Troubleshooting
- Helper throws exceptions on valid JSON — Ensure the input is a UTF-8 string and
json_last_error()is checked immediately after decoding. - Need partial results — Use
JSON_PARTIAL_OUTPUT_ON_ERRORand handle truncated data carefully.
Wrap-up
Great job! You've now combined JSON handling, API consumption, and simple REST design into a clean workflow. You can:
- ✅ Encode PHP arrays and objects into JSON with proper error handling
- ✅ Decode JSON safely and detect malformed payloads
- ✅ Fetch remote API data using cURL with timeouts and error checks
- ✅ Build basic REST endpoints that serve JSON responses
- ✅ Add authentication gates to protect API endpoints
- ✅ Centralize JSON validation logic for reuse across projects
These skills unlock third-party integrations, microservices, and modern front-end frameworks that expect JSON APIs.
Further Reading
- PHP Manual: JSON Functions
- cURL Manual — Detailed cURL command reference
- HTTP Status Codes — Official MDN guide
- REST API Design Guidelines — Best practices for designing RESTful services
Knowledge Check
Test your understanding of JSON and APIs: