Kelas Programmer: CodeIgniter Masterclass untuk Web Developer – Bangun Aplikasi Professional

By | September 27, 2025

 

Kelas Programmer: CodeIgniter Masterclass untuk Web Developer – Bangun Aplikasi Professional

Pernah nggak sih kamu lihat aplikasi web seperti sistem inventory, CRM, atau portal perusahaan dan bertanya-tanya: “Framework apa ya yang cocok untuk bangun aplikasi seperti itu dengan cepat dan maintainable?” Jawabannya seringkali adalah CodeIgniter – framework PHP yang powerful tapi tetap simpel dan mudah dipelajari!

Saya masih inget banget project pertama saya dengan CodeIgniter tahun 2015. Waktu itu diminta bikin sistem manajemen konten untuk client yang butuh deploy cepat. Dengan CodeIgniter, dalam 2 minggu aplikasinya sudah live dan sampai sekarang masih jalan dengan stabil. Rahasianya? Simplicity dan performance yang menjadi filosofi utama CodeIgniter.

Di masterclass ini, kita tidak hanya belajar CodeIgniter dasar. Kita akan bangun aplikasi manajemen proyek lengkap dengan architecture yang scalable, security yang robust, dan code yang production-ready. Siap-siap transformasi dari CodeIgniter user menjadi CodeIgniter expert!

Mengapa CodeIgniter Masih Relevan di 2024?

Sebelum kita deep dive, mari clear beberapa misconception tentang CodeIgniter:

Fakta tentang CodeIgniter 4:

  • Performance: Lebih cepat 20-50% dibanding Laravel untuk aplikasi sederhana
  • Learning Curve: Paling landai di antara framework PHP lainnya
  • Footprint: Ringan (≈2MB) cocok untuk shared hosting
  • Community: Aktif dan supportive dengan dokumentasi yang excellent
  • Compatibility: PHP 7.4+ dengan fitur modern namespaces, composer, dll

Kapan memilih CodeIgniter?

  • Butuh development cepat untuk MVP
  • Project kecil sampai medium complexity
  • Team dengan skill PHP murni yang kuat
  • Environment dengan resource terbatas
  • Legacy system migration

Setup Development Environment Modern

1. Install CodeIgniter 4 dengan Composer

# Install Composer (jika belum ada)
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

# Install CodeIgniter 4
composer create-project codeigniter4/appstarter ci-project
cd ci-project

# Development server
php spark serve

2. Structure Project yang Professional

ci-project/
├── app/
│   ├── Config/           # Configuration files
│   ├── Controllers/      # Application controllers
│   ├── Models/           # Data models
│   ├── Views/            # Template views
│   ├── Filters/          # Request filters
│   ├── Helpers/          # Custom helpers
│   ├── Libraries/        # Custom libraries
│   └── Database/         # Migrations & seeds
├── public/
│   ├── index.php        # Front controller
│   ├── assets/          # CSS, JS, images
│   └── uploads/         # File uploads
├── tests/               # Unit tests
├── writable/           # Logs, cache, sessions
└── env                 # Environment variables

3. Environment Configuration

# .env file
CI_ENVIRONMENT = development
# database.default.hostname = localhost
# database.default.database = ci_app
# database.default.username = root
# database.default.password = 
# database.default.DBDriver = MySQLi

app.baseURL = 'http://localhost:8080/'

# Security
encryption.key = your-secret-key-here

Architecture Pattern: MVC yang Proper

1. Model: Database Abstraction Layer

<?php
namespace App\Models;

use CodeIgniter\Model;

class ProjectModel extends Model
{
    protected $table            = 'projects';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'array';
    protected $useSoftDeletes   = true;
    
    protected $allowedFields = [
        'title', 'description', 'status', 
        'start_date', 'end_date', 'budget', 
        'client_id', 'manager_id'
    ];
    
    protected $useTimestamps = true;
    protected $dateFormat    = 'datetime';
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';
    protected $deletedField  = 'deleted_at';
    
    // Validation rules
    protected $validationRules = [
        'title'       => 'required|min_length[3]|max_length[255]',
        'description' => 'permit_empty|max_length[500]',
        'status'      => 'required|in_list[planning,in_progress,completed,cancelled]',
        'start_date'  => 'required|valid_date',
        'budget'      => 'permit_empty|decimal'
    ];
    
    protected $validationMessages = [
        'title' => [
            'required' => 'Project title is required',
            'min_length' => 'Title must be at least 3 characters'
        ]
    ];
    
    protected $skipValidation = false;
    
    // Custom methods
    public function getActiveProjects()
    {
        return $this->where('status !=', 'completed')
                    ->where('status !=', 'cancelled')
                    ->findAll();
    }
    
    public function getProjectsWithClients()
    {
        return $this->select('projects.*, clients.name as client_name')
                    ->join('clients', 'clients.id = projects.client_id')
                    ->findAll();
    }
    
    public function getProjectProgress($projectId)
    {
        $builder = $this->db->table('tasks');
        return $builder->select('COUNT(*) as total_tasks')
                      ->select('SUM(CASE WHEN status = "completed" THEN 1 ELSE 0 END) as completed_tasks')
                      ->where('project_id', $projectId)
                      ->get()
                      ->getRowArray();
    }
}

2. Controller: Business Logic Handler

<?php
namespace App\Controllers;

use App\Models\ProjectModel;
use App\Models\ClientModel;
use CodeIgniter\API\ResponseTrait;

class Projects extends BaseController
{
    use ResponseTrait;
    
    protected $projectModel;
    protected $clientModel;
    protected $helpers = ['form', 'url'];
    
    public function __construct()
    {
        $this->projectModel = new ProjectModel();
        $this->clientModel = new ClientModel();
    }
    
    public function index()
    {
        $data = [
            'title' => 'Project Management',
            'projects' => $this->projectModel->getProjectsWithClients(),
            'clients' => $this->clientModel->findAll()
        ];
        
        return view('projects/index', $data);
    }
    
    public function create()
    {
        if ($this->request->getMethod() === 'POST') {
            $rules = $this->projectModel->getValidationRules();
            
            if (!$this->validate($rules)) {
                return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
            }
            
            $projectData = [
                'title' => $this->request->getPost('title'),
                'description' => $this->request->getPost('description'),
                'status' => $this->request->getPost('status'),
                'start_date' => $this->request->getPost('start_date'),
                'end_date' => $this->request->getPost('end_date'),
                'budget' => $this->request->getPost('budget'),
                'client_id' => $this->request->getPost('client_id')
            ];
            
            if ($this->projectModel->save($projectData)) {
                return redirect()->to('/projects')->with('success', 'Project created successfully');
            } else {
                return redirect()->back()->withInput()->with('error', 'Failed to create project');
            }
        }
        
        return view('projects/create', [
            'title' => 'Create New Project',
            'clients' => $this->clientModel->findAll()
        ]);
    }
    
    public function edit($id)
    {
        $project = $this->projectModel->find($id);
        
        if (!$project) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }
        
        if ($this->request->getMethod() === 'POST') {
            $rules = $this->projectModel->getValidationRules();
            
            if (!$this->validate($rules)) {
                return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
            }
            
            $projectData = [
                'id' => $id,
                'title' => $this->request->getPost('title'),
                'description' => $this->request->getPost('description'),
                'status' => $this->request->getPost('status'),
                'start_date' => $this->request->getPost('start_date'),
                'end_date' => $this->request->getPost('end_date'),
                'budget' => $this->request->getPost('budget'),
                'client_id' => $this->request->getPost('client_id')
            ];
            
            if ($this->projectModel->save($projectData)) {
                return redirect()->to('/projects')->with('success', 'Project updated successfully');
            } else {
                return redirect()->back()->withInput()->with('error', 'Failed to update project');
            }
        }
        
        return view('projects/edit', [
            'title' => 'Edit Project',
            'project' => $project,
            'clients' => $this->clientModel->findAll()
        ]);
    }
    
    public function delete($id)
    {
        if ($this->projectModel->delete($id)) {
            return redirect()->to('/projects')->with('success', 'Project deleted successfully');
        } else {
            return redirect()->to('/projects')->with('error', 'Failed to delete project');
        }
    }
    
    // API Endpoint
    public function apiProjects()
    {
        $projects = $this->projectModel->findAll();
        return $this->respond($projects);
    }
}

3. View: Template dengan Layout System

<?php // app/Views/templates/header.php ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= $title ?? 'CodeIgniter App' ?></title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="<?= base_url() ?>">ProjectManager</a>
        </div>
    </nav>

    <div class="container mt-4">
        <?php if (session()->getFlashdata('success')): ?>
            <div class="alert alert-success">
                <?= session()->getFlashdata('success') ?>
            </div>
        <?php endif; ?>

        <?php if (session()->getFlashdata('error')): ?>
            <div class="alert alert-danger">
                <?= session()->getFlashdata('error') ?>
            </div>
        <?php endif; ?>

<?php // app/Views/projects/index.php ?>
<?= $this->extend('templates/header') ?>

<?= $this->section('content') ?>
<div class="d-flex justify-content-between align-items-center mb-4">
    <h1><i class="fas fa-project-diagram me-2"></i>Project Management</h1>
    <a href="<?= base_url('projects/create') ?>" class="btn btn-primary">
        <i class="fas fa-plus me-1"></i>New Project
    </a>
</div>

<div class="row">
    <?php foreach ($projects as $project): ?>
    <div class="col-md-6 col-lg-4 mb-4">
        <div class="card h-100">
            <div class="card-header d-flex justify-content-between align-items-center">
                <span class="badge bg-<?= 
                    $project['status'] == 'completed' ? 'success' : 
                    ($project['status'] == 'in_progress' ? 'warning' : 'secondary')
                ?>">
                    <?= ucfirst($project['status']) ?>
                </span>
                <div>
                    <a href="<?= base_url("projects/edit/{$project['id']}") ?>" 
                       class="btn btn-sm btn-outline-primary">
                        <i class="fas fa-edit"></i>
                    </a>
                    <button onclick="confirmDelete(<?= $project['id'] ?>)" 
                            class="btn btn-sm btn-outline-danger">
                        <i class="fas fa-trash"></i>
                    </button>
                </div>
            </div>
            <div class="card-body">
                <h5 class="card-title"><?= esc($project['title']) ?></h5>
                <p class="card-text text-muted"><?= esc($project['description']) ?></p>
                <div class="mb-2">
                    <small class="text-muted">
                        <i class="fas fa-user me-1"></i>
                        Client: <?= esc($project['client_name']) ?>
                    </small>
                </div>
                <div class="mb-2">
                    <small class="text-muted">
                        <i class="fas fa-calendar me-1"></i>
                        Start: <?= date('M d, Y', strtotime($project['start_date'])) ?>
                    </small>
                </div>
                <?php if ($project['budget']): ?>
                <div class="mb-2">
                    <small class="text-muted">
                        <i class="fas fa-dollar-sign me-1"></i>
                        Budget: $<?= number_format($project['budget'], 2) ?>
                    </small>
                </div>
                <?php endif; ?>
            </div>
        </div>
    </div>
    <?php endforeach; ?>
</div>

<?php if (empty($projects)): ?>
<div class="text-center py-5">
    <i class="fas fa-inbox fa-3x text-muted mb-3"></i>
    <p class="text-muted">No projects found.</p>
    <a href="<?= base_url('projects/create') ?>" class="btn btn-primary">
        Create Your First Project
    </a>
</div>
<?php endif; ?>

<script>
function confirmDelete(projectId) {
    if (confirm('Are you sure you want to delete this project?')) {
        window.location.href = `<?= base_url('projects/delete') ?>/${projectId}`;
    }
}
</script>
<?= $this->endSection() ?>

<?= $this->extend('templates/footer') ?>

Database Migration dan Seeding

1. Migration untuk Version Control Database

<?php
// app/Database/Migrations/2024-01-15-001000_CreateProjectsTable.php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateProjectsTable extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 11,
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'title' => [
                'type' => 'VARCHAR',
                'constraint' => '255',
            ],
            'description' => [
                'type' => 'TEXT',
                'null' => true,
            ],
            'status' => [
                'type' => 'ENUM',
                'constraint' => ['planning', 'in_progress', 'completed', 'cancelled'],
                'default' => 'planning',
            ],
            'start_date' => [
                'type' => 'DATE',
            ],
            'end_date' => [
                'type' => 'DATE',
                'null' => true,
            ],
            'budget' => [
                'type' => 'DECIMAL',
                'constraint' => '10,2',
                'null' => true,
            ],
            'client_id' => [
                'type' => 'INT',
                'constraint' => 11,
                'unsigned' => true,
            ],
            'manager_id' => [
                'type' => 'INT',
                'constraint' => 11,
                'unsigned' => true,
                'null' => true,
            ],
            'created_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
            'updated_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
            'deleted_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
        ]);
        
        $this->forge->addKey('id', true);
        $this->forge->addForeignKey('client_id', 'clients', 'id', 'CASCADE', 'CASCADE');
        $this->forge->addForeignKey('manager_id', 'users', 'id', 'SET_NULL', 'SET_NULL');
        $this->forge->createTable('projects');
    }

    public function down()
    {
        $this->forge->dropTable('projects');
    }
}

2. Seeding untuk Sample Data

<?php
// app/Database/Seeds/ProjectSeeder.php

namespace App\Database\Seeds;

use CodeIgniter\Database\Seeder;

class ProjectSeeder extends Seeder
{
    public function run()
    {
        $data = [
            [
                'title' => 'Website Redesign',
                'description' => 'Complete redesign of company website with modern UI/UX',
                'status' => 'in_progress',
                'start_date' => '2024-01-01',
                'end_date' => '2024-03-01',
                'budget' => 15000.00,
                'client_id' => 1,
                'manager_id' => 1,
                'created_at' => date('Y-m-d H:i:s'),
            ],
            [
                'title' => 'Mobile App Development',
                'description' => 'iOS and Android app for customer engagement',
                'status' => 'planning',
                'start_date' => '2024-02-01',
                'end_date' => '2024-06-01',
                'budget' => 50000.00,
                'client_id' => 2,
                'manager_id' => 1,
                'created_at' => date('Y-m-d H:i:s'),
            ]
        ];

        $this->db->table('projects')->insertBatch($data);
    }
}

Authentication dan Authorization System

1. Custom Authentication Library

<?php
// app/Libraries/Auth.php

namespace App\Libraries;

use App\Models\UserModel;

class Auth
{
    protected $userModel;
    protected $session;
    
    public function __construct()
    {
        $this->userModel = new UserModel();
        $this->session = \Config\Services::session();
    }
    
    public function attemptLogin($email, $password)
    {
        $user = $this->userModel->where('email', $email)->first();
        
        if ($user && password_verify($password, $user['password'])) {
            if ($user['is_active']) {
                $this->setUserSession($user);
                return true;
            }
        }
        
        return false;
    }
    
    public function setUserSession($user)
    {
        $sessionData = [
            'user_id' => $user['id'],
            'user_email' => $user['email'],
            'user_name' => $user['name'],
            'user_role' => $user['role'],
            'logged_in' => true
        ];
        
        $this->session->set($sessionData);
    }
    
    public function isLoggedIn()
    {
        return $this->session->get('logged_in') === true;
    }
    
    public function getUser()
    {
        if ($this->isLoggedIn()) {
            return $this->userModel->find($this->session->get('user_id'));
        }
        
        return null;
    }
    
    public function hasRole($role)
    {
        return $this->session->get('user_role') === $role;
    }
    
    public function logout()
    {
        $this->session->destroy();
    }
    
    public function register($userData)
    {
        $userData['password'] = password_hash($userData['password'], PASSWORD_DEFAULT);
        $userData['is_active'] = true;
        
        return $this->userModel->insert($userData);
    }
}

2. Filter untuk Route Protection

<?php
// app/Filters/AuthFilter.php

namespace App\Filters;

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

class AuthFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $auth = service('auth');
        
        if (!$auth->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Please login first');
        }
        
        // Check role-based access
        if (!empty($arguments)) {
            $userRole = session()->get('user_role');
            if (!in_array($userRole, $arguments)) {
                throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
            }
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // Do something here after response is sent
    }
}

RESTful API Development

1. API Controller dengan JWT Authentication

<?php
namespace App\Controllers\Api;

use App\Controllers\BaseController;
use App\Models\ProjectModel;
use CodeIgniter\API\ResponseTrait;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class ProjectsApi extends BaseController
{
    use ResponseTrait;
    
    protected $projectModel;
    
    public function __construct()
    {
        $this->projectModel = new ProjectModel();
        $this->validateToken();
    }
    
    protected function validateToken()
    {
        $token = $this->request->getHeaderLine('Authorization');
        
        if (!$token) {
            return $this->failUnauthorized('Token required');
        }
        
        try {
            $token = str_replace('Bearer ', '', $token);
            $decoded = JWT::decode($token, new Key(getenv('JWT_SECRET'), 'HS256'));
            $this->userId = $decoded->user_id;
        } catch (\Exception $e) {
            return $this->failUnauthorized('Invalid token');
        }
    }
    
    public function index()
    {
        $page = $this->request->getGet('page') ?? 1;
        $perPage = $this->request->getGet('per_page') ?? 10;
        
        $projects = $this->projectModel->paginate($perPage, 'default', $page);
        $pager = $this->projectModel->pager;
        
        return $this->respond([
            'data' => $projects,
            'pagination' => [
                'current_page' => $pager->getCurrentPage(),
                'total_pages' => $pager->getPageCount(),
                'total_items' => $pager->getTotal(),
                'per_page' => $perPage
            ]
        ]);
    }
    
    public function show($id)
    {
        $project = $this->projectModel->find($id);
        
        if (!$project) {
            return $this->failNotFound('Project not found');
        }
        
        return $this->respond($project);
    }
    
    public function create()
    {
        $rules = $this->projectModel->getValidationRules();
        
        if (!$this->validate($rules)) {
            return $this->failValidationErrors($this->validator->getErrors());
        }
        
        $projectData = $this->request->getJSON(true);
        $projectData['manager_id'] = $this->userId;
        
        if ($this->projectModel->insert($projectData)) {
            return $this->respondCreated([
                'message' => 'Project created successfully',
                'project_id' => $this->projectModel->getInsertID()
            ]);
        } else {
            return $this->failServerError('Failed to create project');
        }
    }
}

Advanced Features dan Best Practices

1. Custom Helpers untuk Business Logic

<?php
// app/Helpers/project_helper.php

if (!function_exists('format_project_status')) {
    function format_project_status($status)
    {
        $statuses = [
            'planning' => ['label' => 'Planning', 'class' => 'secondary'],
            'in_progress' => ['label' => 'In Progress', 'class' => 'warning'],
            'completed' => ['label' => 'Completed', 'class' => 'success'],
            'cancelled' => ['label' => 'Cancelled', 'class' => 'danger']
        ];
        
        return $statuses[$status] ?? ['label' => 'Unknown', 'class' => 'dark'];
    }
}

if (!function_exists('calculate_project_progress')) {
    function calculate_project_progress($projectId)
    {
        $projectModel = new \App\Models\ProjectModel();
        $progress = $projectModel->getProjectProgress($projectId);
        
        if ($progress['total_tasks'] > 0) {
            return round(($progress['completed_tasks'] / $progress['total_tasks']) * 100, 1);
        }
        
        return 0;
    }
}

2. Event System untuk Decoupled Architecture

<?php
// app/Config/Events.php

use App\Events\ProjectCreated;
use CodeIgniter\Events\Events;

Events::on('project.created', [ProjectCreated::class, 'handle']);

// app/Events/ProjectCreated.php
namespace App\Events;

use App\Models\ProjectModel;

class ProjectCreated
{
    public static function handle($projectId)
    {
        $projectModel = new ProjectModel();
        $project = $projectModel->find($projectId);
        
        // Send notification
        $email = \Config\Services::email();
        $email->setTo('manager@company.com');
        $email->setSubject('New Project Created');
        $email->setMessage("A new project '{$project['title']}' has been created.");
        $email->send();
        
        // Log activity
        log_message('info', "Project {$projectId} created: {$project['title']}");
    }
}

// Trigger event dalam controller
Events::trigger('project.created', $projectId);

Deployment dan Production Ready

1. Environment Production Configuration

# .env.production
CI_ENVIRONMENT = production

app.baseURL = 'https://yourdomain.com'

# Database
database.default.hostname = 'localhost'
database.default.database = 'production_db'
database.default.username = 'db_user'
database.default.password = 'secure_password'
database.default.DBDriver = MySQLi

# Security
encryption.key = 'your-production-secret-key'

# Cache
cache.handler = 'redis'
cache.redis.host = '127.0.0.1'
cache.redis.password = 'redis_password'

2. Performance Optimization

# app/Config/Cache.php
public $handler = 'redis';
public $redis = [
    'host'     => '127.0.0.1',
    'password' => null,
    'port'     => 6379,
    'timeout'  => 0,
    'database' => 0,
];

# app/Config/Images.php
public $libraryPath = '/usr/bin/';

# .htaccess untuk Apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

# Security headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"

Testing dan Quality Assurance

1. Unit Testing dengan PHPUnit

<?php
// tests/ProjectModelTest.php

use App\Models\ProjectModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;

class ProjectModelTest extends CIUnitTestCase
{
use DatabaseTestTrait;

protected $projectModel;
protected $