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']);
Tip: Grouping every API route under an 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']);
CodeMeaningTypical use
200OKSuccessful GET, PUT, or general success
201CreatedA new resource was successfully created via POST
400Bad RequestMalformed request, or validation failed
404Not FoundThe requested resource ID doesn't exist
422Unprocessable EntityWell-formed request, but the data fails business rules
429Too Many RequestsRate 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:

ApproachHow it works
API keysEach 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.

Next steps: You've now covered the full request lifecycle, from routing all the way to JSON APIs. Finish the track with Best Practices, a capstone checklist tying every lesson together.