banner-shape-1
banner-shape-1
object-3d-1
object-3d-2

Designing a RESTful API in CodeIgniter 4 with Authentication, Rate Limiting, and Logging

In modern web development, APIs form the backbone of digital ecosystems. Whether you’re powering a mobile app, a front-end dashboard, or integrating with third-party systems, a well-structured RESTful API is essential.

CodeIgniter 4 (CI4), being lightweight, fast, and modular, provides an excellent framework for building secure and scalable APIs. In this article, we’ll go beyond the basics and explore how to design a robust RESTful API in CI4 — complete with JWT authentication, rate limiting, and activity logging.

This guide is for developers who already know the fundamentals of CodeIgniter and want to implement production-grade API practices.

1. Setting Up the Project Structure

Start with a clean CodeIgniter 4 installation:

composer create-project codeigniter4/appstarter ci4-api
cd ci4-api
php spark serve

CI4’s directory structure is ideal for REST API organization. However, to make things modular and scalable, it’s best to follow this structure:

app/
├── Controllers/
│ ├── Api/
│ │ └── V1/
│ │ └── Users.php
├── Models/
│ └── UserModel.php
├── Filters/
│ ├── AuthFilter.php
│ ├── RateLimitFilter.php
├── Helpers/
│ └── jwt_helper.php
├── Libraries/
│ └── ActivityLogger.php

This modular separation allows you to extend API versions easily, such as /api/v1/ and /api/v2/ later.

2. Creating RESTful Routes

Define your API routes inside app/Config/Routes.php:

$routes->group('api/v1', ['namespace' => 'App\Controllers\Api\V1'], static function ($routes) {
$routes->post('login', 'Auth::login');
$routes->post('register', 'Auth::register');
$routes->get('users', 'Users::index', ['filter' => 'authfilter']);
$routes->get('users/(:num)', 'Users::show/$1', ['filter' => 'authfilter']);
});

We’re defining a versioned API group (api/v1) to future-proof the design.
Notice the use of authfilter — this will handle JWT authentication for protected routes.

3. Building the User Controller

Let’s create our first controller at app/Controllers/Api/V1/Users.php:

namespace App\Controllers\Api\V1;

use App\Controllers\BaseController;
use App\Models\UserModel;

class Users extends BaseController
{
protected $userModel;

public function __construct()
{
$this->userModel = new UserModel();
}

public function index()
{
$users = $this->userModel->findAll();
return $this->response->setJSON(['status' => 'success', 'data' => $users]);
}

public function show($id)
{
$user = $this->userModel->find($id);
if (!$user) {
return $this->response->setJSON(['status' => 'error', 'message' => 'User not found'])->setStatusCode(404);
}
return $this->response->setJSON(['status' => 'success', 'data' => $user]);
}
}

This simple REST controller lists and retrieves users — a good foundation before we layer in authentication and security.

4. Implementing JWT Authentication

JWT (JSON Web Token) is ideal for stateless authentication.
Let’s create a helper file at app/Helpers/jwt_helper.php:


use Firebase\JWT\JWT;
use Firebase\JWT\Key;

function generate_jwt($payload)
{
    $key = getenv('JWT_SECRET');
    $payload['iat'] = time();
    $payload['exp'] = time() + 3600; // 1 hour
    return JWT::encode($payload, $key, 'HS256');
}

function verify_jwt($token)
{
    try {
        $key = getenv('JWT_SECRET');
        return JWT::decode($token, new Key($key, 'HS256'));
    } catch (Exception $e) {
        return null;
    }
}

Now, add your secret key to .env:


JWT_SECRET = "YOUR_SUPER_SECRET_KEY"

Finally, create the AuthFilter in app/Filters/AuthFilter.php


namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class AuthFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $authHeader = $request->getHeaderLine('Authorization');
        if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {
            return Services::response()->setJSON(['message' => 'Unauthorized'])->setStatusCode(401);
        }

        $token = substr($authHeader, 7);
        $decoded = verify_jwt($token);
        if (!$decoded) {
            return Services::response()->setJSON(['message' => 'Invalid or expired token'])->setStatusCode(401);
        }

        $request->user = $decoded;
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // nothing here
    }
}

Add this filter in app/Config/Filters.php:


public array $aliases = [
    'authfilter' => \App\Filters\AuthFilter::class,
];

Now your protected routes can only be accessed with a valid JWT.

5. Adding Rate Limiting

To prevent abuse and DoS attacks, let’s implement rate limiting.

Create app/Filters/RateLimitFilter.php:


namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;

class RateLimitFilter implements FilterInterface
{
    private $maxRequests = 100;
    private $window = 3600; // 1 hour

    public function before(RequestInterface $request, $arguments = null)
    {
        $ip = $request->getIPAddress();
        $cache = cache("ratelimit_$ip") ?? ['count' => 0, 'time' => time()];

        if (time() - $cache['time'] > $this->window) {
            $cache = ['count' => 1, 'time' => time()];
        } else {
            $cache['count']++;
        }

        cache()->save("ratelimit_$ip", $cache, $this->window);

        if ($cache['count'] > $this->maxRequests) {
            return Services::response()
                ->setJSON(['error' => 'Too many requests'])
                ->setStatusCode(429);
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) {}
}

Then enable this filter in routes where appropriate:


$routes->get('users', 'Users::index', ['filter' => 'authfilter, ratelimit']);

This way, each IP is restricted to 100 requests per hour.

6. Logging API Activity

Logging is critical for debugging and analytics.

Create a simple logger class at app/Libraries/ActivityLogger.php:


namespace App\Libraries;

class ActivityLogger
{
    public static function log($action, $details)
    {
        $data = [
            'action' => $action,
            'details' => json_encode($details),
            'ip' => $_SERVER['REMOTE_ADDR'],
            'created_at' => date('Y-m-d H:i:s'),
        ];

        file_put_contents(WRITEPATH . 'logs/api_activity.log', json_encode($data) . PHP_EOL, FILE_APPEND);
    }
}

Use it in your controller:


use App\Libraries\ActivityLogger;

public function index()
{
    ActivityLogger::log('User Fetch', ['endpoint' => 'users']);
    $users = $this->userModel->findAll();
    return $this->response->setJSON(['status' => 'success', 'data' => $users]);
}

You’ll now have a detailed record of API actions for analysis and debugging.

7. Testing the API

Use Postman or cURL to test:

  1. Login (POST) – /api/v1/login
  2. Receive JWT token
  3. Send GET request to /api/v1/users with header:
    Authorization: Bearer your_jwt_token_here

You should receive a secure JSON response.

8. Security Best Practices

To make your API production-ready:

  • Always use HTTPS.
  • Keep JWT secrets private and rotate keys periodically.
  • Add CORS headers for controlled front-end access.
  • Use pagination to avoid heavy data loads.
  • Store logs securely and consider using a database for structured analytics.

Conclusion

Building a RESTful API in CodeIgniter 4 goes far beyond CRUD endpoints.
By implementing JWT authentication, rate limiting, and activity logging, you can deliver a secure, scalable, and production-grade API that meets modern standards.

This modular architecture allows future expansion — for example, integrating with mobile apps, third-party systems, or React-based dashboards — while keeping your backend stable and secure.

If you’re a developer looking to master backend design, CodeIgniter 4 offers a clean, flexible, and powerful foundation for professional-grade APIs.

Let’s Connect Beyond the Blog