Building a Custom Server-Side DataTable in Laravel Without Packages


When dealing with large datasets in Laravel applications, client-side rendering can quickly become inefficient. In this article, I’ll demonstrate how to implement server-side DataTables processing from scratch without relying on packages like Yajra Laravel DataTables.



Why Build a Custom Solution?

While packages provide convenience, building your own implementation offers:

  • Complete control over the data processing pipeline
  • Better understanding of the underlying mechanics
  • No dependencies on third-party packages
  • Customized to your specific application needs



Project Architecture

Our implementation consists of:

  1. Backend Service – Handles data processing and filtering
  2. Controller – Processes AJAX requests
  3. View – Contains the DataTable HTML structure
  4. JavaScript – Configures and initializes the DataTable



Step 1: Create a DataTable Service

First, let’s create a dedicated service to handle server-side processing:



namespace App\Services;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;

class DataTableService
{
    public function processDataTable($query, Request $request, $columns): array
    {
        // Get the table name from the query
        $tableName = $query->getModel()->getTable();

        // Handle search
        if ($request->has('search"https://dev.to/cammanhhoang/) && !empty($request->input('search"https://dev.to/cammanhhoang/)['value"https://dev.to/cammanhhoang/])) {
            $searchValue = $request->input('search"https://dev.to/cammanhhoang/)['value"https://dev.to/cammanhhoang/];
            $query->where(function ($query) use ($searchValue, $columns, $tableName) {
                foreach ($columns as $column) {
                    // Check if the column belongs to a related table
                    if (str_contains($column, '."https://dev.to/cammanhhoang/)) {
                        // Split the column name into relation and column
                        [$relation, $relatedColumn] = explode('."https://dev.to/cammanhhoang/, $column);

                        // Add a condition on the related table
                        $query->orWhereHas($relation, function ($query) use ($relatedColumn, $searchValue) {
                            $query->where($relatedColumn, 'like"https://dev.to/cammanhhoang/, '%"https://dev.to/cammanhhoang/.$searchValue.'%"https://dev.to/cammanhhoang/);
                        });
                    } else {
                        // Skip columns that don't exist in the table
                        if (Schema::hasColumn($tableName, $column)) {
                            $query->orWhere($column, 'like"https://dev.to/cammanhhoang/, '%"https://dev.to/cammanhhoang/.$searchValue.'%"https://dev.to/cammanhhoang/);
                        }
                    }
                }
            });
        }

        // Handle ordering
        if ($request->has('order"https://dev.to/cammanhhoang/)) {
            $order = $request->input('order"https://dev.to/cammanhhoang/)[0];
            $orderByColumn = $columns[$order['column"https://dev.to/cammanhhoang/]];
            $orderDirection = $order['dir"https://dev.to/cammanhhoang/];

            if (Schema::hasColumn($tableName, $orderByColumn)) {
                $query->orderBy($orderByColumn, $orderDirection);
            }
        }

        // Get total count before pagination
        $totalFiltered = $query->count();

        // Handle pagination
        $start = $request->input('start"https://dev.to/cammanhhoang/, 0);
        $length = $request->input('length"https://dev.to/cammanhhoang/, 10);
        $query->skip($start)->take($length);

        // Get filtered count
        $totalData = $query->count();

        // Prepare response data
        return [
            'draw' => intval($request->input('draw"https://dev.to/cammanhhoang/)),
            'recordsTotal' => $totalData,
            'recordsFiltered' => $totalFiltered,
            'data' => $query->get(),
        ];
    }
}
Enter fullscreen mode

Exit fullscreen mode

This service handles core functionality including:

  • Searching across columns
  • Relation-based searching
  • Sorting data
  • Implementing pagination
  • Formatting the response for DataTables



Step 2: Implement the Controller

Next, create a controller method to process DataTable requests:

public function index(Request $request)
{
    if ($request->ajax()) {
        $columns = ['id"https://dev.to/cammanhhoang/, 'family_name"https://dev.to/cammanhhoang/, 'first_name"https://dev.to/cammanhhoang/, 'email"https://dev.to/cammanhhoang/, 'created_at"https://dev.to/cammanhhoang/, 'id"https://dev.to/cammanhhoang/];
        $data = User::select($columns);

        $response = $this->dataTableService->processDataTable($data, $request, $columns);
        // Add URLs and format fields for each record
        $response['data"https://dev.to/cammanhhoang/] = $response['data"https://dev.to/cammanhhoang/]->map(function ($user) {
            $user->edit_url = route('admin.users.edit"https://dev.to/cammanhhoang/, $user->id);
            $user->destroy_url = route('admin.users.destroy"https://dev.to/cammanhhoang/, $user->id);
            $user->email = ' . $user->email . '">' . $user->email . '"https://dev.to/cammanhhoang/;
            return $user;
        });

        return response()->json($response);
    }

    return view('admin.users.index"https://dev.to/cammanhhoang/);
}
Enter fullscreen mode

Exit fullscreen mode

The controller:

  1. Checks if the request is an AJAX call
  2. Sets up the columns to query
  3. Passes the query builder to our service
  4. Enhances the response with additional data (URLs, formatted content)
  5. Returns either JSON for AJAX requests or the view for normal requests



Step 3: Create the Blade View with Inline JavaScript

Now let’s create a complete Blade view with the DataTable initialization code included directly:

@extends('admin.layouts.master')
@section('title')
    Users
@endsection
@section('page-header')
    @component('admin.components.page-header')
        @slot('title')
            Users List
        @endslot
        @slot('subtitle')
            Users
        @endslot
        @slot('button')
            
        @endslot
    @endcomponent
@endsection

@section('center-scripts')
    "https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"https://dev.to/cammanhhoang/>
    "https://cdn.datatables.net/buttons/2.3.6/js/dataTables.buttons.min.js"https://dev.to/cammanhhoang/>
@endsection

@section('content')
    
class="content"https://dev.to/cammanhhoang/>
class="card"https://dev.to/cammanhhoang/> class="table datatable-selection-single"id="user-table"https://dev.to/cammanhhoang/>
ID Last Name First Name Email Created At class="text-center"https://dev.to/cammanhhoang/>Actions
@endsection @section('scripts')