Building a REST API
Everything covered so far in this track has produced full HTML pages through views. But CodeIgniter is
just as comfortable serving JSON instead of HTML — the exact same routing,
controller, and model layers you already know work identically for a REST API consumed by a mobile app,
a JavaScript frontend, or another server. This page builds a small but complete Articles
API from scratch: listing, showing, and creating records, all returning JSON with correct HTTP status
codes.
1. Two Ways to Build API Controllers
CodeIgniter 4 ships a purpose-built ResourceController (and a matching ResourcePresenter
for HTML-returning APIs) that pre-wires the conventional REST actions — index(),
show($id), create(), update($id), delete($id) —
for you. It's a genuinely useful shortcut once you're comfortable with the pattern. For learning purposes,
though, this page uses a plain Controller and builds JSON responses by hand, since seeing
every response constructed explicitly makes it much clearer exactly what's happening and why:
return $this->response->setJSON(['data' => $articles]);
$this->response is CodeIgniter's Response object, available in every controller.
setJSON() both encodes the array as JSON and sets the Content-Type: application/json
header automatically — you never need to call json_encode() yourself.
2. Defining API Routes
API routes are declared in app/Config/Routes.php just like the page routes from the
Routing lesson. You can either wire up each HTTP verb explicitly, which is the clearest starting point:
// app/Config/Routes.php
$routes->get('api/articles', 'Api\Articles::index');
$routes->get('api/articles/(:num)', 'Api\Articles::show/$1');
$routes->post('api/articles', 'Api\Articles::create');
Or, once the controller follows the conventional resource shape, register all of it in a single line
with resource() — this one call generates the index, show,
create, update, and delete routes together:
$routes->resource('api/articles', ['controller' => 'Api\Articles']);
api/ prefix, and placing the controller itself in an App\Controllers\Api namespace/folder, keeps API endpoints clearly separated from your regular page controllers as a project grows.3. Setting HTTP Status Codes
A REST API is expected to communicate outcomes through standard HTTP status codes, not just through the JSON body. CodeIgniter's response object makes this a single chained call:
return $this->response->setStatusCode(404)->setJSON(['error' => 'Not found']);
| Code | Meaning | Typical use |
|---|---|---|
| 200 | OK | Successful GET, PUT, or general success |
| 201 | Created | A new resource was successfully created via POST |
| 400 | Bad Request | Malformed request, or validation failed |
| 404 | Not Found | The requested resource ID doesn't exist |
| 422 | Unprocessable Entity | Well-formed request, but the data fails business rules |
| 429 | Too Many Requests | Rate limit exceeded (see the Throttler on the previous page) |
4. A Full Worked Example: Api\Articles
Here is a complete API controller combining routing, JSON responses, status codes, the Model layer from earlier in this track, and the Validation library, into three real endpoints:
// app/Controllers/Api/Articles.php
<?php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Models\ArticleModel;
class Articles extends BaseController
{
public function index()
{
$model = new ArticleModel();
return $this->response->setJSON([
'data' => $model->findAll(),
]);
}
public function show($id = null)
{
$model = new ArticleModel();
$article = $model->find($id);
if ($article === null) {
return $this->response->setStatusCode(404)->setJSON([
'error' => 'Article not found',
]);
}
return $this->response->setJSON(['data' => $article]);
}
public function create()
{
$input = $this->request->getJSON(true); // true = as an array
$rules = [
'title' => 'required|min_length[3]|max_length[255]',
'body' => 'required|min_length[10]',
];
if (!$this->validateData($input, $rules)) {
return $this->response->setStatusCode(400)->setJSON([
'errors' => $this->validator->getErrors(),
]);
}
$model = new ArticleModel();
$id = $model->insert($input);
return $this->response->setStatusCode(201)->setJSON([
'data' => ['id' => $id] + $input,
]);
}
}
getJSON(true) reads and decodes the raw JSON body of the incoming request (rather than
$_POST, which is empty for a JSON request), returning it as a plain associative array.
validateData() is the Validation library's equivalent of validate() for data
that isn't already sitting in $_POST — exactly this situation. A sample response from
show(1) on a found article looks like:
{
"data": {
"id": 1,
"title": "Getting Started with CodeIgniter",
"body": "CodeIgniter is a lightweight PHP framework..."
}
}
And the 404 case, from a missing ID, returns a clear error shape alongside the correct status code:
{
"error": "Article not found"
}
5. API Authentication (Conceptual Overview)
Everything above assumes a public, unauthenticated endpoint. Real-world APIs almost always need to know who is calling. Two common approaches, at a conceptual level:
| Approach | How it works |
|---|---|
| API keys | Each client is issued a long, unique key; every request includes it (commonly in a header like X-API-Key) and a filter checks it against a stored list before the controller runs |
| Token-based (e.g. JWT) | A client authenticates once and receives a signed token; that token is sent on every subsequent request and verified without a database lookup on each call |
Both are typically enforced with a CodeIgniter Filter — the same mechanism that
powers the CSRF protection from the previous page — registered to run before your API controllers
and rejecting any request missing a valid key or token with a 401 Unauthorized response.
Building a full authentication system is beyond this page's scope, but knowing this is the shape of the
solution will make the concept immediately recognizable when you need it.