CodeIgniter4: CRUD Data untuk Manajemen Postingan Blog

10 minute read

Published:

Implementasi CRUD (Create, Read, Update, Delete) merupakan bagian penting dalam pengembangan aplikasi web karena memungkinkan interaksi yang dinamis dengan data di database. Pada artikel ini kita mempraktikkan pengelolaan data postingan blog melalui panel admin, dimulai dari menampilkan daftar, menambah, mengubah, hingga menghapus data.

Praktikum ini melanjutkan project MyBlog dari artikel sebelumnya. Kita akan menerapkan CRUD data untuk mengelola tabel posts melalui panel admin. Pembahasan meliputi:

  • Membuat admin post (route group dan controller)
  • Menampilkan data (Read)
  • Menambah data (Create)
  • Mengubah data (Update)
  • Menghapus data (Delete)

1. Membuat Admin Post

Pertama, buat route group untuk admin. Buka file app/Config/Routes.php dan tambahkan kode berikut:

$routes->group('admin', function($routes){
    $routes->get('post', 'PostAdmin::index');
    $routes->get('post/(:segment)/preview', 'PostAdmin::preview/$1');
    $routes->add('post/new', 'PostAdmin::create');
    $routes->add('post/(:segment)/edit', 'PostAdmin::edit/$1');
    $routes->get('post/(:segment)/delete', 'PostAdmin::delete/$1');
});

Kode di atas membuat group route untuk bagian admin, artinya setiap route di dalam grup harus diawali dengan /admin. Misalnya, untuk membuka form create, alamatnya adalah /admin/post/new.

Ada dua metode yang digunakan pada route:

  • get() — hanya dapat diakses dengan metode HTTP GET.
  • add() — dapat diakses menggunakan GET maupun POST. Digunakan pada route create dan edit karena selain menampilkan form (GET), kita juga mengirim data (POST).

Membuat Controller PostAdmin

Buka terminal dan jalankan:

php spark make:controller PostAdmin

Output jika berhasil:

File created: APPPATH/Controllers/PostAdmin.php

Buka file app/Controllers/PostAdmin.php dan isi dengan kode lengkap berikut:

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use CodeIgniter\HTTP\ResponseInterface;
use App\Models\PostModel;
use CodeIgniter\Exceptions\PageNotFoundException;

class PostAdmin extends BaseController
{
    public function index()
    {
        $post = new PostModel();
        $data['posts'] = $post->findAll();
        echo view('admin/admin_post_list', $data);
    }

    //--------------------------------------------------------------

    public function preview($id)
    {
        $post = new PostModel();
        $data['post'] = $post->where('id', $id)->first();

        if(!$data['post']){
            throw PageNotFoundException::forPageNotFound();
        }
        echo view('post_detail', $data);
    }

    //--------------------------------------------------------------

    public function create()
    {
        // lakukan validasi
        $validation =  \Config\Services::validation();
        $validation->setRules(['title' => 'required']);
        $isDataValid = $validation->withRequest($this->request)->run();

        // jika data valid, simpan ke database
        if($isDataValid){
            $post = new PostModel();
            $post->insert([
                "title" => $this->request->getPost('title'),
                "content" => $this->request->getPost('content'),
                "status" => $this->request->getPost('status'),
                "slug" => url_title($this->request->getPost('title'), '-', TRUE)
            ]);
            return redirect('admin/post');
        }

        // tampilkan form create
        echo view('admin/admin_post_create');
    }

    //--------------------------------------------------------------

    public function edit($id)
    {
        // ambil artikel yang akan diedit
        $post = new PostModel();
        $data['post'] = $post->where('id', $id)->first();

        // lakukan validasi data artikel
        $validation =  \Config\Services::validation();
        $validation->setRules([
            'id' => 'required',
            'title' => 'required'
        ]);
        $isDataValid = $validation->withRequest($this->request)->run();

        // jika data valid, simpan ke database
        if($isDataValid){
            $post->update($id, [
                "title" => $this->request->getPost('title'),
                "content" => $this->request->getPost('content'),
                "status" => $this->request->getPost('status')
            ]);
            return redirect('admin/post');
        }

        // tampilkan form edit
        echo view('admin/admin_post_update', $data);
    }

    //--------------------------------------------------------------

    public function delete($id)
    {
        $post = new PostModel();
        $post->delete($id);
        return redirect('admin/post');
    }
}

Controller PostAdmin memiliki lima method:

  • index() — menampilkan daftar artikel.
  • preview($id) — menampilkan pratinjau artikel berdasarkan $id.
  • create() — membuat artikel baru.
  • edit($id) — mengedit artikel berdasarkan $id.
  • delete($id) — menghapus artikel berdasarkan $id.

2. Menampilkan Data (Read)

Pada controller PostAdmin, fitur read terdapat pada method index() dan preview().

Method index() mengambil semua data menggunakan findAll() dan mengirimnya ke view admin_post_list:

public function index()
{
    $post = new PostModel();
    $data['posts'] = $post->findAll();
    echo view('admin/admin_post_list', $data);
}

Method preview($id) mengambil satu artikel berdasarkan $id menggunakan where() dan first(), lalu menampilkannya di view post_detail:

public function preview($id)
{
    $post = new PostModel();
    $data['post'] = $post->where('id', $id)->first();

    if(!$data['post']){
        throw PageNotFoundException::forPageNotFound();
    }
    echo view('post_detail', $data);
}

View Admin Post List

Buat folder baru admin di dalam app/Views, kemudian buat file admin_post_list.php:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MyBlog</title>
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="<?= base_url('css/bootstrap.min.css') ?>" />
</head>
<body>
    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <div class="container">
            <a class="navbar-brand" href="<?= base_url() ?>">MyBlog</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
                data-bs-target="#navbarNav" aria-controls="navbarNav"
                aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse justify-content-between" id="navbarNav">
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a class="nav-link" href="<?= base_url('admin/post') ?>">Blog</a>
                    </li>
                </ul>
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a href="<?= base_url('admin/post/new') ?>"
                           class="btn btn-primary mr-3">New Post</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="<?= base_url('admin/setting') ?>">Setting</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="<?= base_url('auth/logout') ?>">Logout</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="p-5 mb-4 bg-light rounded-3">
        <div class="container py-5">
            <h1 class="display-5 fw-bold">Blog > Admin</h1>
        </div>
    </div>

    <div class="container">
        <table class="table">
            <thead>
                <tr>
                    <th>#</th>
                    <th>Title</th>
                    <th>Status</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach($posts as $post): ?>
                <tr>
                    <td><?= $post['id'] ?></td>
                    <td>
                        <strong><?= $post['title'] ?></strong><br>
                        <small class="text-muted"><?= $post['created_at'] ?></small>
                    </td>
                    <td>
                        <?php if($post['status'] === 'published'): ?>
                        <small class="text-success"><?= $post['status'] ?></small>
                        <?php else: ?>
                        <small class="text-muted"><?= $post['status'] ?></small>
                        <?php endif ?>
                    </td>
                    <td>
                        <a href="<?= base_url('admin/post/'.$post['id'].'/preview') ?>"
                           class="btn btn-sm btn-outline-secondary" target="_blank">Preview</a>
                        <a href="<?= base_url('admin/post/'.$post['id'].'/edit') ?>"
                           class="btn btn-sm btn-outline-secondary">Edit</a>
                        <a href="#"
                           data-href="<?= base_url('admin/post/'.$post['id'].'/delete') ?>"
                           onclick="confirmToDelete(this)"
                           class="btn btn-sm btn-outline-danger">Delete</a>
                    </td>
                </tr>
                <?php endforeach ?>
            </tbody>
        </table>

        <!-- Modal Konfirmasi Delete -->
        <div id="confirm-dialog" class="modal fade" tabindex="-1" role="dialog">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-body">
                        <h2 class="h2">Are you sure?</h2>
                        <p>The data will be deleted and lost forever</p>
                    </div>
                    <div class="modal-footer">
                        <a href="#" role="button" id="delete-button"
                           class="btn btn-danger">Delete</a>
                        <button type="button" class="btn btn-secondary"
                                data-bs-dismiss="modal">Cancel</button>
                    </div>
                </div>
            </div>
        </div>

        <script>
            function confirmToDelete(el) {
                document.getElementById("delete-button")
                    .setAttribute("href", el.dataset.href);
                var myModal = new bootstrap.Modal(
                    document.getElementById('confirm-dialog'), {
                    keyboard: false
                });
                myModal.show();
            }
        </script>
    </div>

    <div class="container py-4">
        <footer class="pt-3 mt-4 text-muted border-top">
            <div class="container">
                &copy; <?= Date('Y') ?>
            </div>
        </footer>
    </div>

    <!-- jQuery dan Bootstrap JS -->
    <script src="<?= base_url('js/jquery.min.js') ?>"></script>
    <script src="<?= base_url('js/bootstrap.bundle.min.js') ?>"></script>

</body>
</html>

Tampilan admin post list menampilkan daftar postingan dalam tabel dilengkapi tombol aksi: Preview, Edit, dan Delete. Tampilan preview sama seperti view post_detail pada sisi user, hanya saja URL-nya berbeda yaitu admin/post/{id}/preview.

Admin Post List

3. Menambah Data (Create)

Method create() menangani dua kondisi: menampilkan form (GET) dan menyimpan data (POST).

Proses validasi:

$validation =  \Config\Services::validation();
$validation->setRules(['title' => 'required']);
$isDataValid = $validation->withRequest($this->request)->run();
  1. Load library validasi data.
  2. Tetapkan aturan validasi: field title harus diisi (required).
  3. Jalankan validasi terhadap data dari request.

Jika data valid, simpan ke database:

if($isDataValid){
    $post = new PostModel();
    $post->insert([
        "title" => $this->request->getPost('title'),
        "content" => $this->request->getPost('content'),
        "status" => $this->request->getPost('status'),
        "slug" => url_title($this->request->getPost('title'), '-', TRUE)
    ]);
    return redirect('admin/post');
}
  • Data diambil dari input form menggunakan $this->request->getPost().
  • slug dihasilkan otomatis dari judul menggunakan helper url_title().
  • Setelah data tersimpan, pengguna dialihkan ke halaman daftar postingan.

Jika data belum valid atau pertama kali membuka halaman, tampilkan form:

echo view('admin/admin_post_create');

View Form Create

Buat file app/Views/admin/admin_post_create.php:

<div class="container">
    <form action="" method="post" id="text-editor">
        <div class="form-group mb-2">
            <label for="title">Title</label>
            <input type="text" name="title" class="form-control"
                   placeholder="Post title" required>
        </div>
        <div class="form-group mb-2">
            <textarea name="content" class="form-control" cols="30" rows="10"
                      placeholder="Write a great post!"></textarea>
        </div>
        <div class="form-group">
            <button type="submit" name="status" value="published"
                    class="btn btn-primary">Publish</button>
            <button type="submit" name="status" value="draft"
                    class="btn btn-secondary">Save to Draft</button>
        </div>
    </form>
</div>

Form ini memiliki dua tombol submit:

  • Publish — menyimpan artikel dengan status published.
  • Save to Draft — menyimpan artikel dengan status draft.

4. Mengubah Data (Update)

Method edit($id) menangani pengambilan data lama, validasi, dan penyimpanan data yang diperbarui.

Ambil data artikel yang akan diedit:

$post = new PostModel();
$data['post'] = $post->where('id', $id)->first();

Lakukan validasi (field id dan title wajib diisi):

$validation =  \Config\Services::validation();
$validation->setRules([
    'id' => 'required',
    'title' => 'required'
]);
$isDataValid = $validation->withRequest($this->request)->run();

Jika valid, update data di database:

if($isDataValid){
    $post->update($id, [
        "title" => $this->request->getPost('title'),
        "content" => $this->request->getPost('content'),
        "status" => $this->request->getPost('status')
    ]);
    return redirect('admin/post');
}

// tampilkan form update
echo view('admin/admin_post_update', $data);

View Form Update

Buat file app/Views/admin/admin_post_update.php:

<div class="container">
    <form action="" method="post" id="text-editor">
        <input type="hidden" name="id" value="<?= $post['id'] ?>" />
        <div class="form-group mb-2">
            <label for="title">Title</label>
            <input type="text" name="title" class="form-control"
                   placeholder="Post title" value="<?= $post['title'] ?>" required>
        </div>
        <div class="form-group mb-2">
            <textarea name="content" class="form-control" cols="30" rows="10"
                      placeholder="Write a great post!"><?= $post['content'] ?></textarea>
        </div>
        <div class="form-group mb-2">
            <button type="submit" name="status" value="published"
                    class="btn btn-primary">Publish</button>
            <button type="submit" name="status" value="draft"
                    class="btn btn-secondary">Save to Draft</button>
        </div>
    </form>
</div>

Perbedaan utama dengan form create:

  • Terdapat input hidden id untuk mengidentifikasi artikel yang diedit.
  • Field title dan textarea sudah terisi data lama dari database (value <?= $post['title'] ?> dan <?= $post['content'] ?>).

5. Menghapus Data (Delete)

Method delete($id) menerima parameter $id, menghapus data dari tabel posts, lalu mengalihkan ke halaman daftar:

public function delete($id)
{
    $post = new PostModel();
    $post->delete($id);
    return redirect('admin/post');
}

Pada view admin_post_list.php, tombol delete tidak langsung menghapus data. Tombol ini memicu konfirmasi melalui modal Bootstrap:

<a href="#"
   data-href="<?= base_url('admin/post/'.$post['id'].'/delete') ?>"
   onclick="confirmToDelete(this)"
   class="btn btn-sm btn-outline-danger">Delete</a>

Modal konfirmasi:

<div id="confirm-dialog" class="modal fade" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-body">
                <h2 class="h2">Are you sure?</h2>
                <p>The data will be deleted and lost forever</p>
            </div>
            <div class="modal-footer">
                <a href="#" role="button" id="delete-button"
                   class="btn btn-danger">Delete</a>
                <button type="button" class="btn btn-secondary"
                        data-bs-dismiss="modal">Cancel</button>
            </div>
        </div>
    </div>
</div>

Fungsi JavaScript confirmToDelete(el):

function confirmToDelete(el) {
    document.getElementById("delete-button")
        .setAttribute("href", el.dataset.href);
    var myModal = new bootstrap.Modal(
        document.getElementById('confirm-dialog'), {
        keyboard: false
    });
    myModal.show();
}
  1. Mengambil elemen delete-button dan mengatur atribut href dengan nilai data-href dari tombol yang diklik.
  2. Membuat instance Bootstrap modal untuk confirm-dialog dan menonaktifkan penutupan modal via keyboard.
  3. Menampilkan modal menggunakan method show() agar pengguna dapat mengkonfirmasi penghapusan.

Delete Post

6. Struktur Direktori Views Admin

Setelah semua view dibuat, struktur direktori app/Views menjadi:

app/Views/
  admin/
    admin_post_list.php       ← Daftar semua postingan (Read)
    admin_post_create.php     ← Form tambah postingan (Create)
    admin_post_update.php     ← Form ubah postingan (Update)
  home.php
  about.php
  contact.php
  faqs.php
  post.php
  post_detail.php             ← Juga digunakan untuk preview admin

Mapping Route Admin ke Controller dan View:

RouteMethodAksi
/admin/postPostAdmin::indexMenampilkan daftar artikel
/admin/post/(:segment)/previewPostAdmin::preview/$1Pratinjau artikel
/admin/post/newPostAdmin::createForm tambah & simpan artikel
/admin/post/(:segment)/editPostAdmin::edit/$1Form ubah & simpan artikel
/admin/post/(:segment)/deletePostAdmin::delete/$1Hapus artikel

7. Ringkasan

Praktik ini menerapkan CRUD (Create, Read, Update, Delete) pada panel admin untuk mengelola data postingan blog menggunakan CodeIgniter 4:

  • Route Group — semua route admin dikelompokkan dalam group admin agar terorganisir.
  • Read — method index() menampilkan seluruh daftar artikel dengan findAll(), dan preview($id) menampilkan detail satu artikel dengan first().
  • Create — method create() melakukan validasi input, menyimpan data baru ke database, dan menghasilkan slug otomatis dari judul.
  • Update — method edit($id) mengambil data lama, memvalidasi perubahan, lalu menyimpan data yang diperbarui.
  • Delete — method delete($id) menghapus data berdasarkan ID, dilengkapi modal konfirmasi Bootstrap sebelum eksekusi.
  • Validasi — setiap proses create dan update dilengkapi validasi menggunakan \Config\Services::validation() untuk memastikan data yang masuk sesuai aturan.

Dengan menguasai alur kerja CRUD ini, aplikasi web dapat mengelola data secara efisien dan responsif sesuai kebutuhan pengguna.