How to send AJAX request using jQuery in CakePHP 4

AJAX (Asynchronous JavaScript and XML) is an important part when building a dynamic web application. You can send requests to a server and update parts of a web page without reloading the whole page.

In CakePHP 4 CSRF protection is already enabled, you can use this to securely send AJAX requests.

In this tutorial, I show how you can send AJAX request using jQuery with and without a CSRF token in CakePHP 4 project.

How to send AJAX request using jQuery in CakePHP 4


Table of Content

  1. Create a Table and Insert Data
  2. Update Database Configuration
  3. Creating a Model
  4. Creating a Controller
  5. Include jQuery and CSRF token
  6. Create a Template
  7. Output
  8. Conclusion

1. Create a Table and Insert Data

In the example, I am using users table. It has the following structure –

CREATE TABLE `users` (
    `id` int(11) NOT NULL,
    `username` varchar(50) NOT NULL,
    `name` varchar(60) NOT NULL,
    `gender` varchar(10) NOT NULL,
    `email` varchar(60) NOT NULL,
    `city` varchar(80) NOT NULL
)

INSERT INTO `users` (`id`, `username`, `name`, `gender`, `email`, `city`) VALUES
(1, 'yssyogesh', 'Yogesh singh', 'male', 'yogesh@makitweb.com', 'Bhopal'),
(2, 'bsonarika', 'Sonarika Bhadoria', 'female', 'bsonarika@makitweb.com', 'Indore'),
(3, 'sunil', 'Sunil singh', 'male', 'sunil@makitweb.com', 'Pune'),
(4, 'vishal', 'Vishal Sahu', 'male', 'vishal@makitweb.com', 'Bhopal'),
(5, 'jiten', 'jitendra singh', 'male', 'jitendra@makitweb.com', 'Delhi'),
(6, 'shreya', 'Shreya joshi', 'female', 'shreya@makitweb.com', 'Indore'),
(7, 'abhilash', 'Abhilash namdev', 'male', 'abhilash@makitweb.com', 'Pune'),
(8, 'ekta', 'Ekta patidar', 'female', 'ekta@makitweb.com', 'Bhopal'),
(9, 'deepak', 'Deepak singh', 'male', 'deepak@makitweb.com', 'Delhi'),
(10, 'rohit', 'Rohit Kumar', 'male', 'rohit@makitweb.com', 'Bhopal');

2. Update Database Configuration

  • Open config/app_local.php file.
  • Specify your database configuration details in the Datasources default.
'Datasources' => [
     'default' => [
          'host' => '127.0.0.1',
          /*
          * CakePHP will use the default DB port based on the driver selected
          * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
          * the following line and set the port accordingly
          */
          //'port' => 'non_standard_port_number',

          'username' => 'root',
          'password' => 'root',

          'database' => 'cakephp4',
          /*
          * If not using the default 'public' schema with the PostgreSQL driver
          * set it here.
          */
          //'schema' => 'myapp',

          /*
          * You can use a DSN string to set the entire configuration
          */
          'url' => env('DATABASE_URL', null),
     ],

     /*
     * The test connection is used during the test suite.
     */
     'test' => [
          'host' => 'localhost',
          //'port' => 'non_standard_port_number',
          'username' => 'my_app',
          'password' => 'secret',
          'database' => 'test_myapp',
          //'schema' => 'myapp',
          'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tests.sqlite'),
     ],
],

3. Creating a Model

  • Create Users Model.
bin/cake bake model Users
  • This command will generate 2 files –
src/Model/Entity/User.php
src/Model/Table/UsersTable.php

src/Model/Entity/User.php

This file contains your Entity class, where you can specify the field names that are eligible for insertion and updating. If there are fields you want to exclude from this process, you have two options:

  • Remove the field name entirely from the Entity class.
  • Set the field to false if you want to disallow insertion or updating for that specific field.

Completed Code

<?php
declare(strict_types=1);

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{

     protected $_accessible = [
          'username' => true,
          'name' => true,
          'gender' => true,
          'email' => true,
          'city' => true,
     ];
}

src/Model/Table/UsersTable.php

This class serves a crucial role in your application’s Object-Relational Mapping (ORM). It informs the ORM which database table to use and defines validation rules for your fields.

Completed Code

<?php
declare(strict_types=1);

namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class UsersTable extends Table
{

     public function initialize(array $config): void
     {
          parent::initialize($config);

          $this->setTable('users');
          $this->setDisplayField('name');
          $this->setPrimaryKey('id');
     }

     public function validationDefault(Validator $validator): Validator
     {
          $validator
              ->scalar('username')
              ->maxLength('username', 50)
              ->requirePresence('username', 'create')
              ->notEmptyString('username');

          $validator
              ->scalar('name')
              ->maxLength('name', 60)
              ->requirePresence('name', 'create')
              ->notEmptyString('name');

          $validator
              ->scalar('gender')
              ->maxLength('gender', 10)
              ->requirePresence('gender', 'create')
              ->notEmptyString('gender');

          $validator
              ->email('email')
              ->requirePresence('email', 'create')
              ->notEmptyString('email');

          $validator
              ->scalar('city')
              ->maxLength('city', 80)
              ->requirePresence('city', 'create')
              ->notEmptyString('city');

          return $validator;
     }

     public function buildRules(RulesChecker $rules): RulesChecker
     {
          $rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
          $rules->add($rules->isUnique(['email']), ['errorField' => 'email']);

          return $rules;
     }
}

4. Creating a Controller

  • Create a UsersController.php file in src/Controller/ folder.
  • Create UsersController Class that extends AppController class.
  • Create 3 methods –
    • index()
    • fetchAllUsers() – Using method handle AJAX request.

Create an object of the Users Table class. Use this object to fetch all records from the users table and store them in a  $usersList variable.

Return the $usersList in JSON format.

    • fetchUserById() – This method also handles AJAX requests.

Read POST userid value and assign it to $userid variable. Create an object of Users Table class to fetch a record from the users table where id = $userid.

Assign the fetched record to $userList and return it in JSON format.

<?php
declare(strict_types=1);

namespace App\Controller;

class UsersController extends AppController
{

     public function index(){

     }

     // Fetch all records
     public function fetchAllUsers(){

          $users = $this->getTableLocator()->get('Users');
          $query = $users->find('all')->order(['id' => 'ASC']);
          $usersList = $query->toArray();

          echo json_encode($usersList);
          die;
     }

     // Fetch record by id
     public function fetchUserById(){
          // POST value
          $userid = $this->request->getData()['userid'];

          // Fetch record
          $users = $this->getTableLocator()->get('Users');
          $query = $users->find('all')
               ->where(['id ' => $userid]);
          $userList = $query->toArray();

          echo json_encode($userList);
          die;
     }

}

5. Include jQuery and CSRF token

I am including jQuery and CSRF token on templates/layout/default.php file.

Stored CSRF token in <meta > tag –

<?= $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); ?>

and jQuery in <head > section –

<?= $this->Html->script('https://code.jquery.com/jquery.min.js'); ?>

Completed Code

<?php
$cakeDescription = 'CakePHP: the rapid development php framework';
?>
<!DOCTYPE html>
<html>
<head>
     <?= $this->Html->charset() ?>
     <meta name="viewport" content="width=device-width, initial-scale=1">

     <!-- CSRF Token -->
     <?= $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); ?>
     <title>
           <?= $cakeDescription ?>:
           <?= $this->fetch('title') ?>
     </title>
     <?= $this->Html->meta('icon') ?>

     <link href="https://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet">

     <?= $this->Html->css(['normalize.min', 'milligram.min', 'cake']) ?>

     <!-- jQuery -->
     <?= $this->Html->script('https://code.jquery.com/jquery.min.js'); ?>

     <?= $this->fetch('meta') ?>
     <?= $this->fetch('css') ?>
     <?= $this->fetch('script') ?>
</head>
<body>
     <nav class="top-nav">
          <div class="top-nav-title">
               <a href="<?= $this->Url->build('/') ?>"><span>Cake</span>PHP</a>
          </div>
          <div class="top-nav-links">
               <a target="_blank" rel="noopener" href="https://book.cakephp.org/4/">Documentation</a>
               <a target="_blank" rel="noopener" href="https://api.cakephp.org/">API</a>
          </div>
     </nav>
     <main class="main">
          <div class="container">
               <?= $this->Flash->render() ?>
               <?= $this->fetch('content') ?>
          </div>
     </main>
     <footer>
     </footer>
</body>
</html>

6. Create a Template

Create a new Users folder in templates/ folder. Now in the Users folder create index.php file – (templates/Users/index.php).

Create an input element for entering the user id for searching and create 2 buttons. 1st button is use to fetch all records and 2nd button is use to fetch record of entered userid in the search box.

Using <table id="usersTable"> to list fetched records using jQuery AJAX.


Script –

Read CSRF token from <meta > tag and assign it to csrfToken variable.

Define click event on #btnfetchall and #btnsearch

  • #btnfetchall – From here send AJAX GET request.

Send AJAX GET request to $this->Url->build(['controller' => 'Users','action' => 'fetchAllUsers']), set dataType: 'json'. On successful callback pass response to createTableRows() to add new <tr> to <table >.

  • #btnsearch – From here send AJAX request with CSRF token.

Send AJAX POST request to $this->Url->build(['controller' => 'Users','action' => 'fetchUserById']), pass userid as data, set dataType: 'json'.

Pass CSRF token using header – 'X-CSRF-Token': csrfToken.

On successful callback pass response to createTableRows() to add new <tr> to <table>.

Create 1 function –

  • createTableRows() – Emtpy the <table id="usersTable" > <tbody>. Check response length. If it is greater than 0 then loop on the response and append <tr> to <table id="usersTable" > <tbody>.

Completed Code

<div class="row">
    <div class="col-6">

        <?php 

        echo $this->Form->control('search',['label' => 'Enter User id','type'=>'number','id'=>'search','class' => 'form-control']);
        echo $this->Form->button('Fetch all', ['type' => 'button','id'=>'btnfetchall']);
        echo $this->Form->button('Search', ['type' => 'button','id'=>'btnsearch','style'=>'margin-left: 15px;']);

        ?>

        <!-- Users list -->
        <table id="usersTable">
            <thead>
                 <tr>
                     <th>ID</th>
                     <th>Username</th>
                     <th>Name</th>
                     <th>Gender</th>
                     <th>Email</th>
                     <th>City</th>
                 </tr>
            </thead>
            <tbody>

            </tbody>
        </table>

    </div>

</div>

<!-- Script -->
<script type="text/javascript">

// Read CSRF Token
var csrfToken = $('meta[name="csrfToken"]').attr('content');

jQuery(document).ready(function(){

     // Fetch all records
     $('#btnfetchall').click(function(){

           // AJAX request
           $.ajax({
                url: "<?= $this->Url->build(['controller' => 'Users','action' => 'fetchAllUsers']) ?>",
                type: "get",
                dataType: 'json',
                success: function(response){
                     // Add rows to <table >
                     createTableRows(response);
                }
           });
     });

     // Fetch single record
     $('#btnsearch').click(function(){
           var userid = $('#search').val();

           // AJAX request
           $.ajax({
               url: "<?= $this->Url->build(['controller' => 'Users','action' => 'fetchUserById']) ?>",
               type: "post",
               data: {userid: userid},
               dataType: 'json',
               headers:{
                    'X-CSRF-Token': csrfToken
               },
               success: function(response){
                    // Add rows to <table >
                    createTableRows(response);

               }
           });
     });

});

// Create table rows
function createTableRows(response){
     var len = 0;
     $('#usersTable tbody').empty(); // Empty <tbody>
     if(response != null){
          len = response.length;
     }

     if(len > 0){
          for(var i=0; i<len; i++){
               var id = response[i].id;
               var username = response[i].username;
               var name = response[i].name;
               var gender = response[i].gender;
               var email = response[i].email;
               var city = response[i].city;

               var tr_str = "<tr>" +
                   "<td align='center'>" + id + "</td>" +
                   "<td align='center'>" + username + "</td>" +
                   "<td align='center'>" + name + "</td>" +
                   "<td align='center'>" + gender + "</td>" +
                   "<td align='center'>" + email + "</td>" +
                   "<td align='center'>" + city + "</td>" +
               "</tr>";

               $("#usersTable tbody").append(tr_str);
          }
     }else{
          var tr_str = "<tr>" +
              "<td align='center' colspan='4'>No record found.</td>" +
          "</tr>";

          $("#usersTable tbody").append(tr_str);
     }
} 
</script>

7. Output

View Output


8. Conclusion

In the example, I showed how you can send AJAX GET and POST request to fetch records from the MySQL database and read their data on the client side. You have to send CSRF token when sending AJAX POST request otherwise server returns a 403 error.

If your required response not returning from the AJAX request or getting an error then use the browser console or network tab to debug.

If you found this tutorial helpful then don't forget to share.