Building RESTful APIs with CodeIgniter 4 and PHP 8.2

🚀 RESTful API Development

Modern API Design with CodeIgniter 4

Building robust RESTful APIs is crucial for modern web applications. CodeIgniter 4 provides excellent tools for API development, especially when paired with PHP 8.2's enhanced features.

🏗️ API Structure Setup

Resource Controller Creation

<?php
namespace App\Controllers\Api;

use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\HTTP\ResponseInterface;

class UsersController extends ResourceController
{
    protected $modelName = 'App\Models\UserModel';
    protected $format = 'json';
    
    /**
     * Get all users
     * GET /api/users
     */
    public function index(): ResponseInterface
    {
        $page = (int) $this->request->getGet('page') ?? 1;
        $limit = (int) $this->request->getGet('limit') ?? 10;
        $search = $this->request->getGet('search');
        
        $users = $this->model
            ->select('id, username, email, created_at, status')
            ->when($search, function($query) use ($search) {
                return $query->like('username', $search)
                           ->orLike('email', $search);
            })
            ->paginate($limit, 'default', $page);
        
        return $this->respond([
            'status' => 'success',
            'data' => $users,
            'pagination' => [
                'current_page' => $page,
                'per_page' => $limit,
                'total' => $this->model->countAllResults(),
                'last_page' => ceil($this->model->countAllResults() / $limit)
            ]
        ]);
    }
    
    /**
     * Get single user
     * GET /api/users/{id}
     */
    public function show($id = null): ResponseInterface
    {
        $user = $this->model->select('id, username, email, created_at, status')->find($id);
        
        if (!$user) {
            return $this->failNotFound('User not found');
        }
        
        return $this->respond([
            'status' => 'success',
            'data' => $user
        ]);
    }
    
    /**
     * Create new user
     * POST /api/users
     */
    public function create(): ResponseInterface
    {
        $rules = [
            'username' => 'required|min_length[3]|max_length[50]|is_unique[users.username]',
            'email' => 'required|valid_email|is_unique[users.email]',
            'password' => 'required|min_length[8]'
        ];
        
        if (!$this->validate($rules)) {
            return $this->failValidationErrors($this->validator->getErrors());
        }
        
        $data = [
            'username' => $this->request->getPost('username'),
            'email' => $this->request->getPost('email'),
            'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
            'status' => 'active'
        ];
        
        $userId = $this->model->insert($data);
        
        if (!$userId) {
            return $this->fail('Failed to create user');
        }
        
        return $this->respondCreated([
            'status' => 'success',
            'message' => 'User created successfully',
            'data' => ['id' => $userId]
        ]);
    }
    
    /**
     * Update user
     * PUT /api/users/{id}
     */
    public function update($id = null): ResponseInterface
    {
        if (!$this->model->find($id)) {
            return $this->failNotFound('User not found');
        }
        
        $rules = [
            'username' => "required|min_length[3]|max_length[50]|is_unique[users.username,id,{$id}]",
            'email' => "required|valid_email|is_unique[users.email,id,{$id}]"
        ];
        
        if (!$this->validate($rules)) {
            return $this->failValidationErrors($this->validator->getErrors());
        }
        
        $data = [
            'username' => $this->request->getVar('username'),
            'email' => $this->request->getVar('email')
        ];
        
        if ($this->model->update($id, $data)) {
            return $this->respond([
                'status' => 'success',
                'message' => 'User updated successfully'
            ]);
        }
        
        return $this->fail('Failed to update user');
    }
    
    /**
     * Delete user
     * DELETE /api/users/{id}
     */
    public function delete($id = null): ResponseInterface
    {
        if (!$this->model->find($id)) {
            return $this->failNotFound('User not found');
        }
        
        if ($this->model->delete($id)) {
            return $this->respondDeleted([
                'status' => 'success',
                'message' => 'User deleted successfully'
            ]);
        }
        
        return $this->fail('Failed to delete user');
    }
}

🔐 API Authentication

JWT Token Implementation

<?php
// Install: composer require firebase/php-jwt

namespace App\Controllers\Api;

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

class AuthController extends BaseController
{
    public function login()
    {
        $email = $this->request->getPost('email');
        $password = $this->request->getPost('password');
        
        // Validate credentials
        $userModel = new \App\Models\UserModel();
        $user = $userModel->where('email', $email)->first();
        
        if (!$user || !password_verify($password, $user['password'])) {
            return $this->response->setJSON([
                'status' => 'error',
                'message' => 'Invalid credentials'
            ])->setStatusCode(401);
        }
        
        // Generate JWT token
        $key = getenv('JWT_SECRET');
        $payload = [
            'iss' => base_url(),
            'aud' => base_url(),
            'iat' => time(),
            'exp' => time() + (60 * 60 * 24), // 24 hours
            'user_id' => $user['id'],
            'username' => $user['username']
        ];
        
        $token = JWT::encode($payload, $key, 'HS256');
        
        return $this->response->setJSON([
            'status' => 'success',
            'token' => $token,
            'expires_in' => 86400
        ]);
    }
    
    public function validateToken()
    {
        $token = $this->getTokenFromHeader();
        
        if (!$token) {
            return $this->response->setJSON([
                'status' => 'error',
                'message' => 'Token not provided'
            ])->setStatusCode(401);
        }
        
        try {
            $key = getenv('JWT_SECRET');
            $decoded = JWT::decode($token, new Key($key, 'HS256'));
            
            return $this->response->setJSON([
                'status' => 'success',
                'user_id' => $decoded->user_id,
                'username' => $decoded->username
            ]);
        } catch (\Exception $e) {
            return $this->response->setJSON([
                'status' => 'error',
                'message' => 'Invalid token'
            ])->setStatusCode(401);
        }
    }
    
    private function getTokenFromHeader(): ?string
    {
        $header = $this->request->getHeader('Authorization');
        
        if ($header && strpos($header->getValue(), 'Bearer ') === 0) {
            return substr($header->getValue(), 7);
        }
        
        return null;
    }
}

📊 API Response Formatting

Custom Response Trait

<?php
namespace App\Traits;

trait ApiResponseTrait
{
    protected function apiResponse(
        $data = null, 
        string $message = '', 
        int $statusCode = 200,
        array $meta = []
    ) {
        $response = [
            'status' => $statusCode < 400 ? 'success' : 'error',
            'message' => $message,
            'timestamp' => date('Y-m-d H:i:s'),
        ];
        
        if ($data !== null) {
            $response['data'] = $data;
        }
        
        if (!empty($meta)) {
            $response['meta'] = $meta;
        }
        
        return $this->response
            ->setJSON($response)
            ->setStatusCode($statusCode);
    }
    
    protected function successResponse($data = null, string $message = 'Success', array $meta = [])
    {
        return $this->apiResponse($data, $message, 200, $meta);
    }
    
    protected function createdResponse($data = null, string $message = 'Created successfully')
    {
        return $this->apiResponse($data, $message, 201);
    }
    
    protected function errorResponse(string $message = 'Error occurred', int $statusCode = 400)
    {
        return $this->apiResponse(null, $message, $statusCode);
    }
    
    protected function validationErrorResponse(array $errors)
    {
        return $this->apiResponse($errors, 'Validation failed', 422);
    }
}

🛡️ Rate Limiting

Throttle Filter

<?php
namespace App\Filters;

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

class ThrottleFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $cache = \Config\Services::cache();
        $key = 'api_throttle_' . $request->getIPAddress();
        
        $attempts = $cache->get($key) ?? 0;
        $maxAttempts = 100; // requests per hour
        $timeWindow = 3600; // 1 hour in seconds
        
        if ($attempts >= $maxAttempts) {
            return \Config\Services::response()
                ->setJSON([
                    'status' => 'error',
                    'message' => 'Rate limit exceeded. Try again later.'
                ])
                ->setStatusCode(429);
        }
        
        $cache->save($key, $attempts + 1, $timeWindow);
    }
    
    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // Add rate limit headers
        $cache = \Config\Services::cache();
        $key = 'api_throttle_' . $request->getIPAddress();
        $attempts = $cache->get($key) ?? 0;
        
        $response->setHeader('X-RateLimit-Limit', '100');
        $response->setHeader('X-RateLimit-Remaining', max(0, 100 - $attempts));
        $response->setHeader('X-RateLimit-Reset', time() + 3600);
    }
}

🎯 API Best Practices

  • ✅ Use proper HTTP status codes
  • ✅ Implement consistent error handling
  • ✅ Add API versioning (/api/v1/)
  • ✅ Use pagination for large datasets
  • ✅ Implement request/response logging
  • ✅ Add comprehensive API documentation

📖 API Documentation

Routes Configuration

// app/Config/Routes.php
$routes->group('api/v1', ['namespace' => 'App\Controllers\Api', 'filter' => 'throttle'], function($routes) {
    // Authentication
    $routes->post('login', 'AuthController::login');
    $routes->post('register', 'AuthController::register');
    
    // Protected routes
    $routes->group('', ['filter' => 'jwt'], function($routes) {
        // Users CRUD
        $routes->resource('users', ['controller' => 'UsersController']);
        
        // Custom endpoints
        $routes->get('users/(:num)/posts', 'UsersController::getUserPosts/$1');
        $routes->post('users/(:num)/avatar', 'UsersController::uploadAvatar/$1');
    });
});
#API #REST #CodeIgniter4 #WebServices

Building modern APIs with CodeIgniter 4 gives you the flexibility and power needed for today's web applications. Remember to always validate input, handle errors gracefully, and document your API endpoints thoroughly.

Happy API building! 🚀

Post a Comment

0 Comments