Building a REST API in PHP
A REST API serves the same kind of application logic as a normal web page — just returning JSON (Intermediate Chapter 7) instead of HTML, with HTTP methods and status codes carrying meaning that a typical web form-based app barely uses. This chapter adapts Chapter 3's Router/Controller structure directly into an API.
REST's Core Idea — HTTP Methods Map to CRUD
This is exactly Intermediate Chapter 4's CRUD, expressed through HTTP methods rather than separate page names like new_post.php or delete_post.php.
Returning JSON Instead of HTML
A REST API needs the Content-Type: application/json header (matching Intermediate Chapter 7's requirement when sending JSON via cURL) so client applications know to parse the response as JSON, not HTML. A small helper like this avoids repeating those three lines in every controller method.
HTTP Status Codes — Communicating What Happened
| Code | Meaning |
|---|---|
| 200 OK | Success — GET, PUT succeeded |
| 201 Created | Success — POST created a new resource |
| 204 No Content | Success — DELETE succeeded, nothing to return |
| 400 Bad Request | The request itself is malformed/invalid |
| 401 Unauthorized | Authentication required, and missing/invalid |
| 403 Forbidden | Authenticated, but not allowed to do this |
| 404 Not Found | The requested resource doesn't exist |
| 422 Unprocessable Entity | Valid request format, but failed validation |
| 500 Internal Server Error | Something broke on the server's end |
An API Controller, Built on Chapter 3's Pattern
file_get_contents('php://input') reads the raw request body — a JSON API client sends a JSON-encoded body rather than a normal HTML form, so $_POST (used throughout this entire course up to now) doesn't get populated; the body has to be read and decoded manually instead.
Routing API Requests by Method, Not Just Path
Unlike Chapter 3's Router (which only mapped by path), an API router must check the HTTP method too — GET /posts and POST /posts share the same path but mean entirely different operations.
Coding Challenges
Write a jsonResponse() helper function (as shown in the chapter), then write a small script simulating a "user not found" scenario: it should call jsonResponse(['error' => 'User not found'], 404). Explain in a comment why setting the status code AND the Content-Type header both matter, even though the JSON body itself is valid either way.
📄 View solutionWrite a destroy(string $id) method for PostApiController that deletes a post by ID and returns a 204 status with an empty body on success, or a 404 JSON error if the post doesn't exist (using the Post class's find() method first to check, catching PostNotFoundException).
📄 View solutionWrite a store() method (like the chapter's example) that additionally validates the title is no longer than 200 characters, returning 422 with a specific error message if it is. Then write the matching cURL-based test code (Intermediate Chapter 7) that POSTs a JSON body to this endpoint and checks the response status code and decoded body.
📄 View solutionChapter 7 Quick Reference
- HTTP methods map to CRUD: GET (read), POST (create), PUT (update), DELETE (delete)
- jsonResponse() — sets status code + Content-Type, json_encode()'s the body, exits
- 200/201/204 — success variants; 400/401/403/404/422 — client error variants; 500 — server error
- file_get_contents('php://input') — reads the raw JSON request body; $_POST is NOT populated for JSON requests
- Route by method AND path — GET /posts and POST /posts are different operations on the same path
- Existing exceptions/models from earlier chapters slot directly into an API — no rewriting needed, just a different response format
- Next chapter: asynchronous/concurrent patterns in PHP — queues, background jobs