Why Use Server-Side Processing in DataTables?
When displaying tabular data on a website, especially in admin panels, dashboards, or reports built with CodeIgniter 4, jQuery DataTables is a go-to solution for its powerful features like pagination, searching, and sorting. However, the default client-side processing mode loads the entire dataset into the browser at once. This works fine for small tables (under 10,000 rows), but it quickly becomes problematic with larger datasets.
That's where server-side processing shines. Instead of sending all records to the client upfront, DataTables makes an AJAX request to your server (in this case, your CodeIgniter 4 controller) for each user action—such as changing pages, searching, or sorting. The server handles the heavy lifting: querying the database, applying filters, ordering results, and returning only the visible page (e.g., 10-50 rows). This approach dramatically improves performance and user experience.
Here are the key benefits of using server-side processing in DataTables:
- Lightning-fast loading: No need to wait for thousands or millions of rows to download initially.
- Scalability: Easily handle 100,000+ records without browser slowdowns, memory issues, or crashes.
- Security & efficiency: Only requested data is transmitted, reducing bandwidth and keeping sensitive information server-side.
- Better SEO & accessibility: Faster page loads improve Core Web Vitals and user retention.
- Real-time data: Perfect for dynamic datasets that change frequently (e.g., live logs or e-commerce orders).
In contrast, client-side processing relies on JavaScript to handle everything in the browser, which can lead to sluggish performance on mobile devices or older hardware. By switching to server-side processing in CodeIgniter 4, you leverage the power of PHP and MySQL for optimized queries, ensuring your application remains responsive even as your data grows.

Table Of Content
1 Prerequisites
- PHP ≥ 8.1
- Composer
- MySQL database
- Basic knowledge of CodeIgniter 4 (Models, Controllers, Views, Migrations)
2 Introduction
In this tutorial, we'll implement server-side processing step-by-step using AJAX and the MVC structure—making it easy to scale your tables for real-world applications.
3 Create a New CodeIgniter 4 Project
3.1 Install Codeigniter 4 Project
Use the following command to install new Codeigniter Project.
composer create-project codeigniter4/appstarter ci4-datatables
Then, navigate to your project directory:
cd ci4-datatables
3.2 Configure Environment and MySql Database
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ci4_datatable
DB_USERNAME=root
DB_PASSWORD=
Create the database ci4_datatable.
4 Install Required Assets (via CDN)
We'll use Bootstrap 5 and DataTables CDN for simplicity.
Download DataTables by either directly downloading the files or using a package manager like npm or CDN.
Using CDN:
In your view file, add the following lines to include the DataTables library:
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.1/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/1.11.4/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.22/js/dataTables.bootstrap5.min.js"></script>
5 Create New Controller: UserController
php spark make:controller UserController
In app/Controllers/UserController.php:
<?php
namespace App\Controllers;
use App\Models\UserModel;
use CodeIgniter\HTTP\RequestInterface;
class UserController extends BaseController
{
public function index()
{
return view('users');
}
public function getUsers()
{
$request = service('request');
$model = new UserModel();
$draw = $request->getPost('draw');
$start = $request->getPost('start');
$length = $request->getPost('length');
$searchValue = $request->getPost('search')['value'];
$orderColumnIndex = $request->getPost('order')[0]['column'] ?? 0;
$orderDir = $request->getPost('order')[0]['dir'] ?? 'asc';
$columns = $request->getPost('columns');
$orderColumn = $columns[$orderColumnIndex]['data'] ?? 'id';
// Total records
$totalRecords = $model->countAll();
// Filtered query
$builder = $model->builder();
if (!empty($searchValue)) {
$builder->like('name', $searchValue)
->orLike('email', $searchValue);
}
$totalFiltered = $builder->countAllResults(false); // Count without limit
// Ordering & Pagination
$builder->orderBy($orderColumn, $orderDir);
$data = $builder->limit($length, $start)->get()->getResultArray();
$jsonData = [
'draw' => intval($draw),
'recordsTotal' => intval($totalRecords),
'recordsFiltered' => intval($totalFiltered),
'data' => $data
];
return $this->response->setJSON($jsonData);
}
}
?>
6 Create Migration and Model
php spark make:migration AddUser
Edit the migration file to define the table structure:
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddUser extends Migration
{
public function up()
{
$this->forge->addField([
'id' => ['type' => 'BIGINT', 'unsigned' => true, 'auto_increment' => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 255],
'email' => ['type' => 'LONGTEXT'],
'created_at' => ['type' => 'TIMESTAMP', 'null' => true],
'updated_at' => ['type' => 'TIMESTAMP', 'null' => true],
]);
$this->forge->addKey('id', true);
$this->forge->createTable('users');
}
public function down()
{
$this->forge->dropTable('users');
}
}
Run the migration:
php spark migrate
php spark make:model UserModel
Configure the UserModel:
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $table = 'users';
protected $primaryKey = 'id';
protected $allowedFields = ['name', 'email', 'created_at', 'updated_at'];
}
7 Create Database Seeder
php spark make:seeder UserSeeder
In app/Database/Seeds/UserSeeder.php:
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
use CodeIgniter\I18n\Time;
use App\Models\UserModel;
class UserSeeder extends Seeder
{
public function run()
{
$faker = \Faker\Factory::create();
$model = new \App\Models\UserModel();
for ($i = 0; $i < 100; $i++) {
$model->insert([
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'created_at' => \CodeIgniter\I18n\Time::now(),
'updated_at' => \CodeIgniter\I18n\Time::now(),
]);
}
}
}
Run the seeder:
php spark db:seed UserSeeder
8 Create the View with DataTables
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Users - Server-Side DataTables</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/2.0.0/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/2.0.0/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.0/js/dataTables.bootstrap5.min.js"></script>
</head>
<body class="container mt-5">
<h1>Users List (Server-Side Processing)</h1>
<table id="userTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Created At</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script>
$(document).ready(function() {
$('#userTable').DataTable({
processing: true,
serverSide: true,
ajax: {
url: '<?= base_url('usercontroller/getusers') ?>',
type: 'POST'
},
columns: [
{ data: 'id' },
{ data: 'name' },
{ data: 'email' },
{ data: 'created_at' }
]
});
});
</script>
</body>
</html>
9 Define a Route
use CodeIgniter\Router\RouteCollection;
/**
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');
$routes->get('users', 'UserController::index');
$routes->post('usercontroller/getusers', 'UserController::getUsers');
10 Folder Structure
11 Run Web Server to Test the App
php spark serve
Visit: http://localhost:8080/users
You should see a searchable, paginated, sortable table loading data via AJAX.
12 Conclusion
- Use libraries like Hermawan\DataTables or irsyadulibad/ci4-datatables → one-liner JSON output.
- Add CSRF protection for POST requests.
- Implement column-specific search.
- Add action buttons (edit/delete) with proper security.
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
You need PHP 8.1 or higher, Composer, MySQL database, and a fresh CodeIgniter 4 installation configured in .env.
Create a 'users' table migration with columns: id (BIGINT unsigned auto_increment primary key), name (VARCHAR 255), email (LONGTEXT), created_at and updated_at (TIMESTAMP nullable). Run migration and seed fake data.
jQuery, Bootstrap 5 CSS/JS, DataTables core, and DataTables Bootstrap 5 integration (jquery.dataTables.min.js and dataTables.bootstrap5.min.js).
In getUsers(), retrieve POST parameters (draw, start, length, search[value], order). Calculate total/filtered records, apply global search with like/orLike on name and email, order dynamically, limit/offset results, and return JSON with draw, recordsTotal
Yes, global search on name and email columns using LIKE/OR LIKE. Ordering is dynamic based on column index and direction from DataTables.
Pagination uses 'start' (offset) and 'length' (limit) from POST data. The model fetches limited results with findAll($limit, $start) after applying search and order.
Common issues: Incorrect AJAX URL or route, database connection errors in .env, missing POST type in ajax, case-sensitive controller name, or no seeded data. Check browser console/network tab for errors.
No, only global search is implemented. For individual column search, add more POST parameters and extend the query builder.
Add columns in DataTables init and nest action buttons in the data loop (e.g., $nestedData['action'] = ''). Render as HTML in the table.
In the view JavaScript: $('#userTable').DataTable({ processing: true, serverSide: true, ajax: { url: base_url('usercontroller/getusers'), type: 'POST' }, columns: [{data:'id'}, {data:'name'}, {data:'email'}, {data:'created_at'}] });
