Membuat Fitur Datatable dengan Techstack Laravel + Next.js

Membuat Aplikasi Fullstack Dengan Laravel dan Next.js — Pt.3

Kode Bagus
7 min readMar 24, 2023
Laravel + Next.js

Halo gaes, dalam aplikasi web tentu sudah tidak asing lagi dengan yang namanya datatable.

Yup, datatable berfungsi untuk menampilkan data-data — biasanya dari database — ke dalam bentuk table di frontend nya.

Pada postingan kali ini kita akan membahas bagaimana cara membuat fitur datatable ini dengan menggunakan techstack Laravel + Next.js dengan pagination yang diproses di server-side.

Postingan ini merupakan lanjutan dari postingan sebelumnya yang membahas tentang cara insert data pada techstack Laravel + Next.js.

Baik, kita langsung saja ke pembuatan fitur nya ya.

Backend

Pertama kita akan membuat api untuk meng-handle request dan mengembalikan response yang biasa digunakan pada datatable.

Model

Edit file model App/Models/Product.php, lalu tambahkan kode protected $appends danfunction imageUrl seperti di bawah ini:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
use HasFactory;

protected $fillable = ['name', 'description', 'image', 'price', 'stock',];

protected $appends = ['image_url'];

/**
* Get the image URL.
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function imageUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->image ? asset(str_replace('public/', 'storage/', $this->image)) : $this->image,
);
}

}

Kode tersebut berfungsi untuk menambahkan atribut image_url pada model Product dan mereplace teks public/ dengan storage/ agar image bisa sudah diupload sebelumnya ke folder storage diakses melalui url.

Resource

Selanjutnya kita akan membuat resource agar response nya bisa sesuai dengan standar format output API dan lebih mudah untuk dimanage.

Pastikan kita berada di folder laranext-be, lalu jalankan perintah:

php artisan make:resource ProductResource 

Perintah tersebut akan membuat file app/Http/Resources/ProductResource.php

Dan pastikan isinya seperti berikut:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

Resource adalah format untuk output single data, sedangkan untuk output yang bentuknya list kita perlu membuat resource collection.

Jalankan perintah:

php artisan make:resource ProductCollection --collection

Perintah tersebut akan membuat file app/Http/Resources/ProductCollection.php

Dan pastikan juga isinya seperti berikut:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class ProductCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

Controller

Setelah resource nya siap, kita akan membuat logic untuk menerima request dan mengembalikan response untuk kebutuhan datatable nya.

Kita akan membuat dan mengedit function-function pada classProductController seperti berikut ini:

use App\Http\Resources\ProductResource;

// ... other codes

/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$product = app(Product::class);
$product = $this->resolveGlobalFilter($product, $request);
$product = $this->resolveSort($product, $request);
$products = $product->paginate($this->resolvePageSize($request));
return ProductResource::collection($products);
}

function resolveGlobalFilter($model, Request $request)
{
if (!is_null($request->globalFilter) && trim($request->globalFilter) != '') {
$model = $model->where('name', 'like', '%' . $request->globalFilter . '%')->orWhere('description', 'like', '%' . $request->globalFilter . '%');
}
return $model;
}

function resolveSort($model, Request $request)
{
if (!is_null($request->sortColumn)) {
$sortDirection = $request->sortDirection == 'asc' ? 'asc' : 'desc';
$model = $model->orderBy($request->sortColumn, $sortDirection);
}
return $model;
}

function resolvePageSize(Request $request)
{

if (is_null($request->pageSize) or $request->pageSize > 100) {
return 10; // set default to 10 and limit pageSize to 100 to avoid overload
} else {
return $request->pageSize;
}
}

Function utama untuk kebutuhan datatable adalah function index. Function tersebut memproses request dari client dan mengembalikan response berupa collection dari ProductResource yang sudah di paginasi per halaman. Jumlah item per halamanya diset default 10 dan maksimal 100 oleh function resolvePageSize agar server tidak overload.

Selain per halaman, sudah dihandle juga filter untuk pencarian pada column name dan description oleh function resolveGlobalFilter

Tidak lupa sort by column tertentu baik ascending maupun descending sudah dihandle oleh function resolveSort

Frontend

Setelah backend ready, kita lanjut ke kodingan untuk frontend nya agar user bisa berinteraksi dengan datatable nya

Library Datatable

Library datatable yang akan kita gunakan untuk case kali ini adalah react-data-table-component karena library tersebut cukup mudah untuk digunakan dengan fitur yang cukup untuk kebutuhan datatable pada umumnya.

Install library yang dibutuhkan dengan perintah:

npm install react-data-table-component styled-components react-debounce-input

react-data-table-component adalah library utama yang diperlukan untuk membuat datatable.

styled-components adalah library untuk style tampilan table-nya yang dibutuhkan oleh react-data-table-component

react-debounce-input adalah library untuk kebutuhan searching realtime pada sebuah input html agar proses searching dibatasi (debounced) sehingga tidak memberatkan server.

Navigation

Lalu tambahkan kode berikut pada file src/components/Layouts/Navigation.js setelah baris <NavLink href="dashboard" … seperti berikut:

<NavLink
href="/products"
active={router.pathname === '/products'}>
Products
</NavLink>

Page

Buat file src/pages/products/index.js lalu isi dengan kode berikut:

import { useAuth } from "@/hooks/auth"
import axios from "@/lib/axios"
import Head from "next/head"
import Link from "next/link"
import { useMemo, useState, useEffect } from "react"
import DataTable from "react-data-table-component"
import { DebounceInput } from "react-debounce-input"

function Products() {

const { user } = useAuth({ middleware: 'guest' })

const [data, setData] = useState([])
const [globalFilter, setGlobalFilter] = useState('')
const [loading, setLoading] = useState(false)
const [page, setPage] = useState(1)
const [perPage, setPerPage] = useState(10)
const [sortColumn, setSortColumn] = useState()
const [sortDirection, setSortDirection] = useState()
const [totalRows, setTotalRows] = useState(0)

const columns = useMemo(() => [
{
name: 'ID',
selector: row => row.id,
sortable: true,
},
{
name: 'Name',
selector: row => row.name,
sortable: true,
},
{
name: 'Description',
selector: row => row.description,
sortable: true,
},
{
name: 'Image',
sortable: false,
cell: (row, index, column, id) => {
return row.image_url ? <img src={row.image_url} alt={row.name} /> : '';
},
},
{
name: 'Price',
selector: row => row.price,
sortable: true,
format: (row, index) => {
return Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(row.price);
},
right: true,
},
{
name: 'Stock',
selector: row => row.stock,
sortable: true,
format: (row, index) => {
return Intl.NumberFormat('id-ID').format(row.stock);
},
right: true,
},
])

const fetchData = async (page, perPage, sortColumn, sortDirection, globalFilter) => {
setLoading(true)

const response = await axios.get(
`/api/products`, { params: { page: page, pageSize: perPage, sortColumn: sortColumn, sortDirection: sortDirection, globalFilter: globalFilter } }
)

setData(response.data.data)
setTotalRows(response.data.meta.total)

setLoading(false)
}

const handlePageChange = page => {
setPage(page)
}

const handlePerRowsChange = (newPerPage, page) => {
setPerPage(newPerPage)
setPage(page)
}

const handleGlobalFilterChange = (value) => {
setGlobalFilter(value)
}

const handleSortChange = (column, sortDirection) => {
setSortColumn(column.name)
setSortDirection(sortDirection)
}

useEffect(() => {
fetchData(page, perPage, sortColumn, sortDirection, globalFilter)
}, [page, perPage, sortColumn, sortDirection, globalFilter])

const customStyles = {
headCells: {
style: {
fontWeight: 'bold',
},
}
};

return (
<>
<Head>
<title>Laranext - Products</title>
</Head>

<div className="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
<div className="hidden fixed top-0 right-0 px-6 py-4 sm:block">
{user ? (
<Link
href="/dashboard"
className="ml-4 text-sm text-gray-700 underline">
Dashboard
</Link>
) : (
<>
<Link
href="/login"
className="text-sm text-gray-700 underline">
Login
</Link>

<Link
href="/register"
className="ml-4 text-sm text-gray-700 underline">
Register
</Link>
</>
)}
</div>

<div className="max-w-6xl mx-auto sm:px-6 lg:px-8 overflow-x-auto ">

<div className="py-2">
<Link
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
href="/products/create">
Create New Product
</Link>
</div>

<DebounceInput
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 my-4"
placeholder="Search..."
minLength={1}
debounceTimeout={500}
onChange={event => (handleGlobalFilterChange(event.target.value))}
/>

<div className="rounded-lg p-10 bg-white">
<DataTable
title="Products"
columns={columns}
data={data}
// progressPending={loading}
pagination
paginationServer
paginationTotalRows={totalRows}
sortServer
onSort={handleSortChange}
onChangeRowsPerPage={handlePerRowsChange}
onChangePage={handlePageChange}
customStyles={customStyles}
/>
</div>
</div>
</div>
</>
)
}

export default Products

Komponen datatable utamanya adalah DataTable

Dan function utama yang memanggil API backend adalah fetchData

Di dalam komponen DataTable menyediakan props yang bisa dipanggil ketika terjadi perubahan pada parameter seperti page, sort, dll

Di dalam komponen page, kita menyiapkan state yang bisa menyimpan parameter-parameter tersebut dengan hook useState. Dan ketika terjadi perubahan pada state, otomatis akan dipanggil api fetchData dengan hook useEffect

Result

Jalankan dan pastikan backend dan front-end nya running seperti pada tutorial sebelumnya.

Lalu buka alamat ini pada browser http://localhost:3000/products/

Tampilan datatable dengan Laravel + Next.js

Data yang ada pada database akan ditampilkan dalam bentuk datatable seperti pada contoh di gambar.

Jika belum ada data, coba klik tombol Create New Product yang akan meredirect ke fitur form create yang sudah kita buat sebelumnya. Lalu isi beberapa data sample.

Coba juga fitur sort nya dengan mengklik salah satu header table nya:

Sort pada datatable

Lalu coba juga fitur filter nya dengan mengetik teks yang dicari pada search box:

Filter atau search pada datatable

Penutup

Demikian cara menampilkan data dalam bentuk datatable dengan techstack Laravel + Next.js.

Cukup simple dan mudah kan? 👍

Silakan komentar jika ada pertanyaan ataupun hal lain yang ingin disampaikan.

Jangan lupa untuk clap dan follow agar tidak ketinggalan info jika ada postingan baru ya☺️

Akhir kata, semoga sharingan kecil ini bisa bermanfaat bagi pembaca semua, khusunya bagi penulis sendiri.

Keep coding dan sampai jumpa di postingan berikutnya…

Wassalam…

--

--

Kode Bagus
Kode Bagus

Written by Kode Bagus

Software Engineering, Music Production, Graphic Design, Business

No responses yet