http clients and apis
JSON APIs
JSON APIs use HTTP to exchange structured data. The client sends and receives JSON instead of HTML.
A good JSON API is predictable. Clients should know what status codes mean, what successful responses look like, what error responses look like, and which fields can be missing or null.
Return JSON with the right content type
<?php
declare(strict_types=1);
$response = [
'data' => [
'id' => 123,
'type' => 'product',
'name' => 'Notebook',
],
];
echo json_encode($response, JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// {"data":{"id":123,"type":"product","name":"Notebook"}}
In a real HTTP response, also send Content-Type: application/json and the correct status code.
Choose a response shape
Many APIs wrap successful data in a data key and errors in an errors key. The exact convention matters less than consistency.
<?php
declare(strict_types=1);
function productResponse(int $id, string $name): array
{
return [
'data' => [
'id' => $id,
'type' => 'product',
'attributes' => [
'name' => $name,
],
],
];
}
echo json_encode(productResponse(123, 'Notebook'), JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// {"data":{"id":123,"type":"product","attributes":{"name":"Notebook"}}}
Avoid returning completely different shapes for similar endpoints. Clients become fragile when one success response returns { "id": 1 } and another returns { "data": { "id": 1 } } without a reason.
Error responses
Error responses should be machine-readable and safe to show or log. Do not expose stack traces, SQL, secrets, or internal class names.
<?php
declare(strict_types=1);
function errorResponse(string $code, string $message): array
{
return [
'errors' => [
[
'code' => $code,
'message' => $message,
],
],
];
}
echo json_encode(errorResponse('not_found', 'Product not found.'), JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// {"errors":[{"code":"not_found","message":"Product not found."}]}
Pair the body with the correct status code, such as 404 for missing resources or 422 for validation errors.
Validation errors
Validation errors should point to the fields that failed.
<?php
declare(strict_types=1);
$response = [
'errors' => [
['field' => 'email', 'message' => 'Enter a valid email address.'],
['field' => 'name', 'message' => 'Name is required.'],
],
];
echo json_encode($response, JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// {"errors":[{"field":"email","message":"Enter a valid email address."},{"field":"name","message":"Name is required."}]}
Clients can use this shape to attach messages to form fields or show a useful API error.
Missing and null are different
In JSON, a field can be absent or present with null.
Absent often means "not included in this response". null often means "known to be empty".
Be deliberate. If middle_name is optional, returning "middle_name": null can be clearer than sometimes omitting it, but the important thing is documenting and staying consistent.
Lists should stay lists
For collections, return arrays consistently. An empty result should be [], not {} or null.
<?php
declare(strict_types=1);
$response = ['data' => []];
echo json_encode($response, JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// {"data":[]}
What to check in a project
Check that JSON responses set Content-Type: application/json.
Check that success and error responses follow documented shapes.
Check that status codes match the outcome.
Check that validation errors are specific enough for clients to use.
Check that sensitive internals are not leaked in error bodies.
Check that empty lists, missing values, and null values are handled consistently.
What you should be able to do
After this lesson, you should be able to design basic JSON success and error responses, choose status codes that match outcomes, return validation errors cleanly, and explain why response consistency matters for API clients.
Practice
Task: Build JSON API Responses
Write a small PHP script that builds success and error response arrays for a JSON API.
Requirements
- Use
declare(strict_types=1);. - Create a success response with a
datakey. - Create an error response with an
errorskey. - Include a validation error with a field name.
- Encode the responses with
json_encode(..., JSON_THROW_ON_ERROR). - Print the encoded output.
Check Your Work
Run the script and confirm the success and error responses have different, predictable shapes.
Show solution
This solution keeps response shapes explicit and easy for a client to consume.
<?php
declare(strict_types=1);
function productCreatedResponse(int $id, string $name): array
{
return [
'data' => [
'id' => $id,
'type' => 'product',
'attributes' => [
'name' => $name,
],
],
];
}
function validationErrorResponse(string $field, string $message): array
{
return [
'errors' => [
[
'field' => $field,
'message' => $message,
],
],
];
}
echo json_encode(productCreatedResponse(123, 'Notebook'), JSON_THROW_ON_ERROR) . PHP_EOL;
echo json_encode(validationErrorResponse('name', 'Name is required.'), JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// {"data":{"id":123,"type":"product","attributes":{"name":"Notebook"}}}
// {"errors":[{"field":"name","message":"Name is required."}]}
In a real API response, the first body would likely use status 201 Created, while the validation body would use a 4xx status such as 422.
Why This Works
The success response has a data object. The validation failure has an errors list that tells the client which field failed and why.