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:
- Login (POST) – /api/v1/login
- Receive JWT token
- 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.



