Tutorial CodeIgniter 4 untuk Pemula: Membuat Aplikasi Web dari Nol sampai Production

By | September 27, 2025

 

Tutorial CodeIgniter 4 untuk Pemula: Membuat Aplikasi Web dari Nol sampai Production

Pernah nggak sih kamu pengen bikin aplikasi web tapi bingung mulai dari mana? Atau sudah coba belajar framework modern seperti Laravel tapi malah overwhelmed dengan complexity-nya? Nah, CodeIgniter 4 bisa jadi jawabannya! Framework PHP yang satu ini terkenal dengan kesederhanaannya, performanya yang ringan, dan learning curve yang gentle banget untuk pemula.

Bayangin CodeIgniter 4 itu seperti motor matic—mudah dikendarai, irit bahan bakar, dan maintenance-nya simpel. Berbeda dengan framework lain yang kadang rasanya seperti harus belajar menyetir truk trailer dulu sebelum bisa jalan. Dengan CodeIgniter 4, kamu bisa fokus ke logika aplikasinya tanpa terlalu pusing dengan setup yang ribet.

Di tutorial lengkap ini, kita akan jalan bareng membuat aplikasi web lengkap dari nol sampai siap deploy. Kita akan bikin sistem manajemen tugas sederhana yang mencakup CRUD operations, authentication, dan fitur-fitur essential lainnya. Siap untuk coding? Let’s get started!

Kenapa Pilih CodeIgniter 4 untuk Pemula?

Sebelum kita mulai, mari pahami dulu kenapa CodeIgniter 4 worth untuk dipelajari:

  • Learning Curve yang Landai: Tidak butuh pengetahuan advanced untuk mulai
  • Documentation yang Excellent: Manualnya sangat lengkap dan mudah dipahami
  • Performance yang Ringan: Footprint kecil, loading cepat
  • MVC Architecture: Mengajarkan pattern yang standard di industry
  • Community Support yang Solid: Banyak resources dan help available

Perbandingan Cepat dengan Framework Lain

Framework Kelebihan Kekurangan
CodeIgniter 4 Simple, fast, easy to learn Fewer built-in features
Laravel Feature-rich, modern, great ecosystem Steeper learning curve
Symfony Enterprise-ready, highly flexible Complex for beginners

Persiapan Development Environment

1. Requirements yang Diperlukan

  • PHP 7.4 atau lebih tinggi
  • Composer (PHP package manager)
  • Web server (Apache/Nginx) atau PHP built-in server
  • Database (MySQL/PostgreSQL/SQLite)

2. Install CodeIgniter 4 via Composer

composer create-project codeigniter4/appstarter ci4-tutorial
cd ci4-tutorial

3. Konfigurasi Dasar

Edit file .env dan sesuaikan dengan environment kamu:

# Environment
CI_ENVIRONMENT = development

# Database
database.default.hostname = localhost
database.default.database = ci4_tutorial
database.default.username = root
database.default.password = 
database.default.DBDriver = MySQLi

Struktur Project CodeIgniter 4

Mari pahami struktur folder yang akan kita gunakan:

ci4-tutorial/
├── app/
│   ├── Config/          # File konfigurasi
│   ├── Controllers/     # Controller classes
│   ├── Models/          # Model classes  
│   ├── Views/           # Template views
│   └── Database/        # Migration files
├── public/
│   └── index.php        # Front controller
├── writable/            # Storage untuk cache, logs
└── .env                 # Environment variables

Membuat Aplikasi Task Manager

Kita akan membuat aplikasi sederhana untuk mengelola tugas sehari-hari. Fitur yang akan kita bangun:

  • Authentication (Login/Register)
  • CRUD Tasks (Create, Read, Update, Delete)
  • Task Categories
  • User-specific tasks

Step 1: Setup Database

Buat database baru dan jalankan SQL berikut:

CREATE DATABASE ci4_tutorial;

USE ci4_tutorial;

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE categories (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    color VARCHAR(7) DEFAULT '#007bff',
    user_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE tasks (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    due_date DATE,
    priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
    status ENUM('pending', 'in_progress', 'completed') DEFAULT 'pending',
    category_id INT,
    user_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (category_id) REFERENCES categories(id),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Step 2: Membuat Model

Buat model untuk User, Task, dan Category.

app/Models/UserModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table = 'users';
    protected $primaryKey = 'id';
    
    protected $allowedFields = ['name', 'email', 'password'];
    
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $updatedField = 'updated_at';
    
    // Hash password sebelum insert
    protected $beforeInsert = ['hashPassword'];
    protected $beforeUpdate = ['hashPassword'];
    
    protected function hashPassword(array $data)
    {
        if (!isset($data['data']['password'])) return $data;
        
        $data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
        return $data;
    }
    
    public function verifyPassword($password, $hashedPassword)
    {
        return password_verify($password, $hashedPassword);
    }
}

app/Models/TaskModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class TaskModel extends Model
{
    protected $table = 'tasks';
    protected $primaryKey = 'id';
    
    protected $allowedFields = [
        'title', 'description', 'due_date', 'priority', 
        'status', 'category_id', 'user_id'
    ];
    
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $updatedField = 'updated_at';
    
    public function getTasksByUser($userId, $filters = [])
    {
        $builder = $this->db->table('tasks t');
        $builder->select('t.*, c.name as category_name, c.color as category_color');
        $builder->join('categories c', 'c.id = t.category_id', 'left');
        $builder->where('t.user_id', $userId);
        
        // Filter by status
        if (isset($filters['status']) && $filters['status'] !== 'all') {
            $builder->where('t.status', $filters['status']);
        }
        
        // Filter by category
        if (isset($filters['category_id']) && $filters['category_id'] !== 'all') {
            $builder->where('t.category_id', $filters['category_id']);
        }
        
        // Search
        if (isset($filters['search']) && !empty($filters['search'])) {
            $builder->like('t.title', $filters['search']);
            $builder->orLike('t.description', $filters['search']);
        }
        
        $builder->orderBy('t.due_date', 'ASC');
        
        return $builder->get()->getResultArray();
    }
}

app/Models/CategoryModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class CategoryModel extends Model
{
    protected $table = 'categories';
    protected $primaryKey = 'id';
    
    protected $allowedFields = ['name', 'color', 'user_id'];
    protected $useTimestamps = true;
    
    public function getCategoriesByUser($userId)
    {
        return $this->where('user_id', $userId)->findAll();
    }
}

Step 3: Membuat Controller

app/Controllers/AuthController.php

<?php

namespace App\Controllers;

use App\Models\UserModel;
use CodeIgniter\Controller;

class AuthController extends Controller
{
    protected $userModel;
    
    public function __construct()
    {
        $this->userModel = new UserModel();
        helper(['form', 'url']);
    }
    
    public function register()
    {
        if ($this->request->getMethod() === 'post') {
            $rules = [
                'name' => 'required|min_length[3]|max_length[100]',
                'email' => 'required|valid_email|is_unique[users.email]',
                'password' => 'required|min_length[6]',
                'password_confirm' => 'matches[password]'
            ];
            
            if ($this->validate($rules)) {
                $userData = [
                    'name' => $this->request->getPost('name'),
                    'email' => $this->request->getPost('email'),
                    'password' => $this->request->getPost('password')
                ];
                
                if ($this->userModel->save($userData)) {
                    session()->setFlashdata('success', 'Registration successful! Please login.');
                    return redirect()->to('/login');
                }
            } else {
                $data['validation'] = $this->validator;
            }
        }
        
        return view('auth/register');
    }
    
    public function login()
    {
        if ($this->request->getMethod() === 'post') {
            $rules = [
                'email' => 'required|valid_email',
                'password' => 'required'
            ];
            
            if ($this->validate($rules)) {
                $email = $this->request->getPost('email');
                $password = $this->request->getPost('password');
                
                $user = $this->userModel->where('email', $email)->first();
                
                if ($user && $this->userModel->verifyPassword($password, $user['password'])) {
                    // Set session
                    $userData = [
                        'user_id' => $user['id'],
                        'name' => $user['name'],
                        'email' => $user['email'],
                        'logged_in' => true
                    ];
                    session()->set($userData);
                    
                    return redirect()->to('/dashboard');
                } else {
                    session()->setFlashdata('error', 'Invalid email or password');
                }
            }
        }
        
        return view('auth/login');
    }
    
    public function logout()
    {
        session()->destroy();
        return redirect()->to('/login');
    }
}

app/Controllers/DashboardController.php

<?php

namespace App\Controllers;

use App\Models\TaskModel;
use App\Models\CategoryModel;
use CodeIgniter\Controller;

class DashboardController extends Controller
{
    protected $taskModel;
    protected $categoryModel;
    
    public function __construct()
    {
        $this->taskModel = new TaskModel();
        $this->categoryModel = new CategoryModel();
        helper(['form', 'url']);
        
        // Check login
        if (!session()->get('logged_in')) {
            return redirect()->to('/login');
        }
    }
    
    public function index()
    {
        $userId = session()->get('user_id');
        $filters = [
            'status' => $this->request->getGet('status') ?? 'all',
            'category_id' => $this->request->getGet('category_id') ?? 'all',
            'search' => $this->request->getGet('search') ?? ''
        ];
        
        $data = [
            'tasks' => $this->taskModel->getTasksByUser($userId, $filters),
            'categories' => $this->categoryModel->getCategoriesByUser($userId),
            'filters' => $filters,
            'title' => 'Dashboard'
        ];
        
        return view('dashboard/index', $data);
    }
    
    public function createTask()
    {
        if ($this->request->getMethod() === 'post') {
            $rules = [
                'title' => 'required|min_length[3]|max_length[255]',
                'due_date' => 'required|valid_date'
            ];
            
            if ($this->validate($rules)) {
                $taskData = [
                    'title' => $this->request->getPost('title'),
                    'description' => $this->request->getPost('description'),
                    'due_date' => $this->request->getPost('due_date'),
                    'priority' => $this->request->getPost('priority'),
                    'category_id' => $this->request->getPost('category_id'),
                    'user_id' => session()->get('user_id')
                ];
                
                if ($this->taskModel->save($taskData)) {
                    session()->setFlashdata('success', 'Task created successfully!');
                    return redirect()->to('/dashboard');
                }
            }
        }
        
        $data = [
            'categories' => $this->categoryModel->getCategoriesByUser(session()->get('user_id')),
            'title' => 'Create New Task'
        ];
        
        return view('dashboard/create_task', $data);
    }
    
    public function updateTaskStatus($taskId)
    {
        $task = $this->taskModel->find($taskId);
        
        if ($task && $task['user_id'] == session()->get('user_id')) {
            $newStatus = $this->request->getPost('status');
            $this->taskModel->update($taskId, ['status' => $newStatus]);
            
            return $this->response->setJSON(['success' => true]);
        }
        
        return $this->response->setJSON(['success' => false]);
    }
}

Step 4: Membuat Views

app/Views/auth/login.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login - Task Manager</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
    <div class="container">
        <div class="row justify-content-center min-vh-100 align-items-center">
            <div class="col-md-6">
                <div class="card shadow">
                    <div class="card-body p-5">
                        <h2 class="text-center mb-4">Login</h2>
                        
                        <?php if (session()->getFlashdata('error')): ?>
                            <div class="alert alert-danger">
                                <?= session()->getFlashdata('error') ?>
                            </div>
                        <?php endif; ?>
                        
                        <?php if (session()->getFlashdata('success')): ?>
                            <div class="alert alert-success">
                                <?= session()->getFlashdata('success') ?>
                            </div>
                        <?php endif; ?>
                        
                        <form method="post">
                            <div class="mb-3">
                                <label for="email" class="form-label">Email</label>
                                <input type="email" class="form-control" id="email" name="email" 
                                       value="<?= old('email') ?>" required>
                            </div>
                            
                            <div class="mb-3">
                                <label for="password" class="form-label">Password</label>
                                <input type="password" class="form-control" id="password" name="password" required>
                            </div>
                            
                            <button type="submit" class="btn btn-primary w-100">Login</button>
                        </form>
                        
                        <div class="text-center mt-3">
                            <a href="<?= site_url('register') ?>">Don't have an account? Register here</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

app/Views/dashboard/index.php

<?= $this->extend('layouts/main') ?>

<?= $this->section('content') ?>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <div class="card">
                <div class="card-header">
                    <h5>Filters</h5>
                </div>
                <div class="card-body">
                    <form method="get">
                        <div class="mb-3">
                            <label class="form-label">Status</label>
                            <select name="status" class="form-select" onchange="this.form.submit()">
                                <option value="all" <?= $filters['status'] == 'all' ? 'selected' : '' ?>>All Status</option>
                                <option value="pending" <?= $filters['status'] == 'pending' ? 'selected' : '' ?>>Pending</option>
                                <option value="in_progress" <?= $filters['status'] == 'in_progress' ? 'selected' : '' ?>>In Progress</option>
                                <option value="completed" <?= $filters['status'] == 'completed' ? 'selected' : '' ?>>Completed</option>
                            </select>
                        </div>
                        
                        <div class="mb-3">
                            <label class="form-label">Category</label>
                            <select name="category_id" class="form-select" onchange="this.form.submit()">
                                <option value="all">All Categories</option>
                                <?php foreach ($categories as $category): ?>
                                    <option value="<?= $category['id'] ?>" 
                                            <?= $filters['category_id'] == $category['id'] ? 'selected' : '' ?>>
                                        <?= $category['name'] ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                        
                        <div class="mb-3">
                            <label class="form-label">Search</label>
                            <div class="input-group">
                                <input type="text" name="search" class="form-control" 
                                       value="<?= $filters['search'] ?>" placeholder="Search tasks...">
                                <button type="submit" class="btn btn-outline-secondary">Search</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
            
            <div class="mt-3">
                <a href="<?= site_url('dashboard/create') ?>" class="btn btn-success w-100">
                    + Add New Task
                </a>
            </div>
        </div>
        
        <div class="col-md-9">
            <?php if (session()->getFlashdata('success')): ?>
                <div class="alert alert-success">
                    <?= session()->getFlashdata('success') ?>
                </div>
            <?php endif; ?>
            
            <div class="row">
                <?php foreach ($tasks as $task): ?>
                    <div class="col-md-6 mb-3">
                        <div class="card task-card">
                            <div class="card-body">
                                <div class="d-flex justify-content-between align-items-start">
                                    <h5 class="card-title"><?= $task['title'] ?></h5>
                                    <span class="badge bg-<?= $task['priority'] ?>">
                                        <?= ucfirst($task['priority']) ?>
                                    </span>
                                </div>
                                
                                <?php if ($task['category_name']): ?>
                                    <span class="badge" style="background-color: <?= $task['category_color'] ?>">
                                        <?= $task['category_name'] ?>
                                    </span>
                                <?php endif; ?>
                                
                                <p class="card-text mt-2"><?= $task['description'] ?></p>
                                
                                <div class="d-flex justify-content-between align-items-center">
                                    <small class="text-muted">
                                        Due: <?= date('M j, Y', strtotime($task['due_date'])) ?>
                                    </small>
                                    
                                    <select class="form-select form-select-sm status-select" 
                                            data-task-id="<?= $task['id'] ?>" 
                                            style="width: auto;">
                                        <option value="pending" <?= $task['status'] == 'pending' ? 'selected' : '' ?>>
                                            Pending
                                        </option>
                                        <option value="in_progress" <?= $task['status'] == 'in_progress' ? 'selected' : '' ?>>
                                            In Progress
                                        </option>
                                        <option value="completed" <?= $task['status'] == 'completed' ? 'selected' : '' ?>>
                                            Completed
                                        </option>
                                    </select>
                                </div>
                            </div>
                        </div>
                    </div>
                <?php endforeach; ?>
            </div>
        </div>
    </div>
</div>

<script>
document.querySelectorAll('.status-select').forEach(select => {
    select.addEventListener('change', function() {
        const taskId = this.dataset.taskId;
        const newStatus = this.value;
        
        fetch('<?= site_url("dashboard/update-status/") ?>' + taskId, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: 'status=' + newStatus
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // Optional: Add visual feedback
                const card = this.closest('.task-card');
                card.style.opacity = '0.9';
                setTimeout(() => card.style.opacity = '1', 300);
            }
        });
    });
});
</script>
<?= $this->endSection() ?>

Step 5: Setup Routes

Edit app/Config/Routes.php:

$routes->get('/', 'AuthController::login');
$routes->get('/login', 'AuthController::login');
$routes->post('/login', 'AuthController::login');
$routes->get('/register', 'AuthController::register');
$routes->post('/register', 'AuthController::register');
$routes->get('/logout', 'AuthController::logout');

$routes->get('/dashboard', 'DashboardController::index');
$routes->get('/dashboard/create', 'DashboardController::createTask');
$routes->post('/dashboard/create', 'DashboardController::createTask');
$routes->post('/dashboard/update-status/(:num)', 'DashboardController::updateTaskStatus/$1');

Fitur-Fitur Lanjutan yang Bisa Ditambahkan

1. Email Notifikasi

// Menggunakan CodeIgniter Email Library
$email = \Config\Services::email();
$email->setTo($userEmail);
$email->setSubject('Task Reminder');
$email->setMessage('You have a task due soon: ' . $taskTitle);
$email->send();

2. File Upload untuk Task Attachments

// Dalam controller
$file = $this->request->getFile('attachment');
if ($file->isValid() && !$file->hasMoved()) {
    $newName = $file->getRandomName();
    $file->move(WRITEPATH . 'uploads', $newName);
}

3. API Endpoints untuk Mobile App

// Buat controller khusus API
$routes->get('api/tasks', 'Api\TaskController::index');
$routes->post('api/tasks', 'Api\TaskController::create');

Deployment ke Production

1. Environment Production

# Ubah di .env
CI_ENVIRONMENT = production

2. Security Best Practices

  • Set app.forceGlobalSecureRequests = true di production
  • Gunakan strong encryption key
  • Setup proper file permissions
  • Enable CSRF protection

3. Performance Optimization

  • Enable caching (database, view caching)
  • Optimize images dan assets
  • Use CDN untuk static files

Kesalahan Umum Pemula dan Solusinya

Masalah Penyebab Solusi
White screen Error reporting off di production Check logs di writable/logs/
Database error Konfigurasi database salah Periksa file .env
Route not found Route belum didefinisikan Periksa app/Config/Routes.php
CSRF error Form tanpa CSRF field Tambahkan <?= csrf_field() ?> di form

Resources untuk Belajar Lebih Lanjut

  • Official Documentation: codeigniter.com/user_guide
  • CodeIgniter Forum: forum.codeigniter.com
  • YouTube Tutorials: Traversy Media, The CodeIgniter Channel
  • GitHub Repository: github.com/codeigniter4/CodeIgniter4

Dengan menyelesaikan tutorial ini, kamu sudah memiliki foundation yang kuat dalam CodeIgniter 4. Ingat, kunci mahir adalah practice dan experimentation. Coba tambahkan fitur-fitur baru, explore library tambahan, dan terus belajar dari project nyata.

Selamat coding! 🚀