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 = truedi 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! 🚀
