Complete CRUD CodeIgniter 4 and React JS Tutorial (Full-Stack)

Complete CRUD CodeIgniter 4 and React JS Tutorial (Full-Stack)

In this tutorial you will learn how to create a Create-Read-Update-Delete (CRUD) application using CodeIgniter 4 on the backend and React JS on the frontend.

Not only that,

I will also share with you how to use Bulma CSS to style the User Interface (UI).

Thus, the application that is built becomes more elegant, responsive, and user friendly.

Let's get started.

 

Step #1. CodeIgniter 4 Installation

To install CodeIgniter 4 can be done in 2 ways, namely: Manual installation and Installation using composer.

In this tutorial, I will use composer.

If you don't have composer, please visit the following URL to download composer, then install it on your computer:

https://getcomposer.org/

After composer is installed on your computer and to make sure composer is installed properly on your computer, open a terminal or Command Prompt (CMD).

Then type the following command:

composer -v

Like the following picture:

composer

Once composer is properly installed on your computer, then you can create a CodeIgniter 4 project using composer.

Create a folder on your web server, here I name it "fullstack".

If you are using WAMPSERVER, create it in folder:

C:/wamp64/www

If you are using XAMPP, create it in the folder:

C:/xampp/htdocs

In this tutorial, I'm using XAMPP.

Then open the "fullstack" folder using the Code Editor, here I am using Visual Studio Code.

After that, integrate with the terminal on Visual Studio Code.

Then type the following command in the terminal to create a CodeIgniter 4 project:

composer create-project codeigniter4/appstarter backend

Wait until the installation process is complete.

After the installation is completed, go to the "backend" folder by typing the following command in the terminal:

cd backend

Then type the following command in the terminal to run the project:

php spark serve

If it goes well, it will looks like the following picture:

spark serve

 

Step #2. Create a Database and Table

Create a new database in MySQL, you can use tools such as SQLyog, PHPMyAdmin or similar tools.

Here I create a database with the name "fullstack_db".

If you create a database with the same name it's even better.

To create a database in MySQL, it can be done by executing the following query:

CREATE DATABASE fullstack_db;

The SQL command above will create a database with the name "fullstack_db".

Next, create a connection between the database and the CodeIgniter project.

Find the env file in the project root, then rename it to .env and open the file.

Then find the following code:

# database.default.hostname = localhost
# database.default.database = ci4
# database.default.username = root
# database.default.password = root
# database.default.DBDriver = MySQLi

Change it to like the following:

database.default.hostname = localhost
database.default.database = fullstack_db
database.default.username = root
database.default.password = 
database.default.DBDriver = MySQLi

Next, create a table on the database "fullstack_db".

In this tutorial, I will use the migration feature in CodeIgniter 4 to create a table.

Type the following command in Terminal:

php spark make:migration Products

Then CodeIgniter will create a file with the initials "Products" in the "app/Database/Migrations" folder.

Like the following picture:

migration

Open the file, then type the following code:

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Products extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 5,
                'auto_increment' => true
            ],
            'title' => [
                'type' => 'VARCHAR',
                'constraint' => 200,
            ],
            'price' => [
                'type' => 'INT',
                'constraint' => 11,
            ],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('products');
    }

    public function down()
    {
        //
    }
}

In the code above, we only add code to the function up().

Function up, serves to create a table in the database with the specified fields.

In the above case, we create a table with the name "products" with fields id, title, and price along with the data type and other attributes.

If you're not familiar with migrations, it may look complicated, but it's really not.

Next, type the following command at the Terminal/Command Prompt:

php spark migrate

Press Enter, then CodeIgniter will automatically create a "products" table with fields id, title, and price in the database "fullstack_db".

 

Step #3. Models

Create a model file by typing the following command in the terminal:

php spark make:model ProductModel

Then CodeIgniter will automatically create a model file named "ProductModel.php" in the "app/Models" folder.

Then, open the file and change the code to be like this:

<?php

namespace App\Models;

use CodeIgniter\Model;

class ProductModel extends Model
{
    protected $DBGroup              = 'default';
    protected $table                = 'products';
    protected $primaryKey           = 'id';
    protected $useAutoIncrement     = true;
    protected $insertID             = 0;
    protected $returnType           = 'array';
    protected $useSoftDeletes       = false;
    protected $protectFields        = true;
    protected $allowedFields        = ['title','price'];

    // Dates
    protected $useTimestamps        = false;
    protected $dateFormat           = 'datetime';
    protected $createdField         = 'created_at';
    protected $updatedField         = 'updated_at';
    protected $deletedField         = 'deleted_at';

    // Validation
    protected $validationRules      = [];
    protected $validationMessages   = [];
    protected $skipValidation       = false;
    protected $cleanValidationRules = true;

    // Callbacks
    protected $allowCallbacks       = true;
    protected $beforeInsert         = [];
    protected $afterInsert          = [];
    protected $beforeUpdate         = [];
    protected $afterUpdate          = [];
    protected $beforeFind           = [];
    protected $afterFind            = [];
    protected $beforeDelete         = [];
    protected $afterDelete          = [];
}

In the code above, we only make a few changes, namely to allowedFields.

 

Step #4. Controllers

Create a controller file by typing the following command in the terminal:

php spark make:controller Products --restful

Then CodeIgniter will automatically create a controller file named "Products.php" in the "app/Controllers" folder.

Then, open the file and change the code to be like this:

<?php

namespace App\Controllers;

use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\API\ResponseTrait;
use App\Models\ProductModel;

class Products extends ResourceController
{
    /**
     * Return an array of resource objects, themselves in array format
     *
     * @return mixed
     */
    use ResponseTrait;
    public function index()
    {
        $model = new ProductModel();
        $data = $model->findAll();
        return $this->respond($data);
    }

    /**
     * Return the properties of a resource object
     *
     * @return mixed
     */
    public function show($id = null)
    {
        $model = new ProductModel();
        $data = $model->find(['id' => $id]);
        if(!$data) return $this->failNotFound('No Data Found');
        return $this->respond($data[0]);
    }

    /**
     * Create a new resource object, from "posted" parameters
     *
     * @return mixed
     */
    public function create()
    {
        helper(['form']);
        $rules = [
            'title' => 'required',
            'price' => 'required'
        ];
        $data = [
            'title' => $this->request->getVar('title'),
            'price' => $this->request->getVar('price')
        ];
        if(!$this->validate($rules)) return $this->fail($this->validator->getErrors());
        $model = new ProductModel();
        $model->save($data);
        $response = [
            'status' => 201,
            'error' => null,
            'messages' => [
                'success' => 'Data Inserted'
            ]
        ];
        return $this->respondCreated($response);
    }

    /**
     * Add or update a model resource, from "posted" properties
     *
     * @return mixed
     */
    public function update($id = null)
    {
        helper(['form']);
        $rules = [
            'title' => 'required',
            'price' => 'required'
        ];
        $data = [
            'title' => $this->request->getVar('title'),
            'price' => $this->request->getVar('price')
        ];
        if(!$this->validate($rules)) return $this->fail($this->validator->getErrors());
        $model = new ProductModel();
        $findById = $model->find(['id' => $id]);
        if(!$findById) return $this->failNotFound('No Data Found');
        $model->update($id, $data);
        $response = [
            'status' => 200,
            'error' => null,
            'messages' => [
                'success' => 'Data Updated'
            ]
        ];
        return $this->respond($response);
    }

    /**
     * Delete the designated resource object from the model
     *
     * @return mixed
     */
    public function delete($id = null)
    {
        $model = new ProductModel();
        $findById = $model->find(['id' => $id]);
        if(!$findById) return $this->failNotFound('No Data Found');
        $model->delete($id);
        $response = [
            'status' => 200,
            'error' => null,
            'messages' => [
                'success' => 'Data Deleted'
            ]
        ];
        return $this->respond($response);
    }
}

 

Step #5. Configure Routes.php

Do a little configuration on the “Routes.php” file located in the "app/Config" folder.

Open the “Routes.php” file in the “app/Config” folder, then find the following code:

$routes->get('/', 'Home::index');

Then, change to the following:

$routes->resource('products');

 

Step #6. CORS (Cross-Origin Resources Sharing)

This is important!

Create a filter file for CORS (Cross-Origin Resource Sharing) so that our API can be accessed from outside the domain.

To create a CORS filter, run the following command in the terminal:

php spark make:filter Cors

Then CodeIgniter will automatically create a filter file named "Cors.php" in the "app/Filters" folder.

Then, open the "Cors.php" file and change the code to be like this:

<?php

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

class Cors implements FilterInterface
{
    /**
     * Do whatever processing this filter needs to do.
     * By default it should not return anything during
     * normal execution. However, when an abnormal state
     * is found, it should return an instance of
     * CodeIgniterHTTPResponse. If it does, script
     * execution will end and that Response will be
     * sent back to the client, allowing for error pages,
     * redirects, etc.
     *
     * @param RequestInterface $request
     * @param array|null       $arguments
     *
     * @return mixed
     */
    public function before(RequestInterface $request, $arguments = null)
    {
        header("Access-Control-Allow-Origin: *");
        header("Access-Control-Allow-Headers: X-API-KEY, Origin,X-Requested-With, Content-Type, Accept, Access-Control-Requested-Method, Authorization");
        header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PATCH, PUT, DELETE");
        $method = $_SERVER['REQUEST_METHOD'];
        if($method == "OPTIONS"){
            die();
        }
    }

    /**
     * Allows After filters to inspect and modify the response
     * object as needed. This method does not allow any way
     * to stop execution of other after filters, short of
     * throwing an Exception or Error.
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param array|null        $arguments
     *
     * @return mixed
     */
    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        //
    }
}

Then, open the "Filters.php" file located in the "app/Config" folder, then add the “cors” filter as follows:

public $aliases = [
    'csrf'     => CSRF::class,
    'toolbar'  => DebugToolbar::class,
    'honeypot' => Honeypot::class,
    'cors'     => App\Filters\Cors::class,
];

Next, define "cors" in public $globals as follows:

public $globals = [
    'before' => [
        // 'honeypot',
        // 'csrf',
        'cors'
    ],
    'after' => [
        'toolbar',
        // 'honeypot',
    ],
];

 

Step #7. Front-End (React JS)

For the front-end, I will use React JS.

If you are not familiar with React JS, I suggest you to lern the “React JS Tutorial For Beginners” first.

There are many ways to create a React project. However, the easiest way is "create react app".

To be able to run the command “create-react-app”, you just need to install node.js on your computer.

You download node.js from the following link and install it on your computer:

https://nodejs.org/en/

After node.js is installed on your computer, open Command Prompt or terminal then type the following command to make sure node.js is installed properly on your computer:

node -v

Then type the following command to make sure NPM (Node Package Manager) is also installed properly:

npm -v

Look at the following picture for more details:

node npm

After node.js and npm are installed properly on your computer, then open a new terminal and create a new react project by typing the following command in the terminal:

npx create-react-app frontend

And make sure you are in the “fullstack” folder, like the following picture:

create-react-app

If the installation is complete, there will be a "frontend" folder in the "fullstack" folder.

So that in the "fullstack" folder there are two folders, namely: "backend" and "frontend" as shown below:

fullstack folder

The “backend” folder is the application folder that was built previously using CodeIgniter 4, while the “frontend” is the application folder that was created using React JS.

Next, go to the “frontend” folder by typing the following command in the terminal:

cd frontend

After that, install react-router-dom, axios, and bulma by typing the following command in the terminal:

npm install react-router-dom axios bulma

After the installation is complete, and to make sure everything goes well, type the following command to run the project:

npm start

If it goes well, it will look like the following image:

npm start

Then, open your browser and visit the following URL:

http://localhost:3000

Then it will appear as follows:

react running

 

Step #8. Components

Create a folder called “components” inside the “frontend/src” folder.

Then, create component files “ProductList.js”, “AddProduct.js”, and “EditProduct.js” in the “frontend/src/components” folder.

Like the following picture:

components

Then open the "ProductList.js" file, then type the following code:

/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react'
import axios from "axios";
import { Link } from "react-router-dom";

const ProductList = () => {
    const [products, setProducts] = useState([]);

    useEffect(() => {
        getProducts();
    },[]);

    const getProducts = async () => {
        const products = await axios.get('http://localhost:8080/products');
        setProducts(products.data);
    }

    const deleteProduct = async (id) =>{
        await axios.delete(`http://localhost:8080/products/${id}`);
        getProducts();
    }

    return (
        <div>
            <Link to="/add" className="button is-primary mt-5">Add New</Link>
            <table className="table is-striped is-fullwidth">
                <thead>
                    <tr>
                        <th>No</th>
                        <th>Title</th>
                        <th>Price</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    { products.map((product, index) => (
                        <tr key={product.id}>
                            <td>{index + 1}</td>
                            <td>{product.title}</td>
                            <td>{product.price}</td>
                            <td>
                                <Link to={`/edit/${product.id}`} className="button is-small is-info">Edit</Link>
                                <button onClick={() => deleteProduct(product.id)} className="button is-small is-danger">Delete</button>
                            </td>
                        </tr>
                    )) }
                    
                </tbody>
            </table>
        </div>
    )
}

export default ProductList

Next, open the “AddProduct.js” file, then type the following code:

import React, { useState } from 'react'
import { useHistory } from 'react-router-dom';
import axios from "axios";

const AddProduct = () => {
    const [title, setTitle] = useState('');
    const [price, setPrice] = useState('');
    const history = useHistory();

    const saveProduct = async (e) => {
        e.preventDefault();
        await axios.post('http://localhost:8080/products',{
            title: title,
            price: price
        });
        history.push("/");
    }

    return (
        <div>
            <form onSubmit={ saveProduct }>
                <div className="field">
                    <label className="label">Title</label>
                    <input 
                        type="text" 
                        className="input" 
                        value={ title } 
                        onChange={ (e) => setTitle(e.target.value) }
                        placeholder="Title"
                    />
                </div>
                <div className="field">
                    <label className="label">Price</label>
                    <input 
                        type="text" 
                        className="input" 
                        value={ price } 
                        onChange={ (e) => setPrice(e.target.value) }
                        placeholder="Price"
                    />
                </div>
                <div className="field">
                    <button className="button is-primary">Save</button>
                </div>
            </form>
        </div>
    )
}

export default AddProduct

Next, open the “EditProduct.js” file, then type the following code:

/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react'
import { useHistory, useParams } from 'react-router-dom';
import axios from "axios";

const EditProduct = () => {
    const [title, setTitle] = useState('');
    const [price, setPrice] = useState('');
    const history = useHistory();
    const { id } = useParams();

    const updateProduct = async (e) => {
        e.preventDefault();
        await axios.patch(`http://localhost:8080/products/${id}`,{
            title: title,
            price: price
        });
        history.push("/");
    }

    useEffect(() => {
        getProductById();
    },[]);

    const getProductById = async () => {
        const response = await axios.get(`http://localhost:8080/products/${id}`);
        setTitle(response.data.title);
        setPrice(response.data.price);
    }

    return (
        <div>
            <form onSubmit={ updateProduct }>
                <div className="field">
                    <label className="label">Title</label>
                    <input 
                        type="text" 
                        className="input" 
                        value={ title } 
                        onChange={ (e) => setTitle(e.target.value) }
                        placeholder="Title"
                    />
                </div>
                <div className="field">
                    <label className="label">Price</label>
                    <input 
                        type="text" 
                        className="input" 
                        value={ price } 
                        onChange={ (e) => setPrice(e.target.value) }
                        placeholder="Price"
                    />
                </div>
                <div className="field">
                    <button className="button is-primary">Update</button>
                </div>
            </form>
        </div>
    )
}

export default EditProduct

 

Step #9. App.js

Open the "App.js" file in the "frontend/src" folder, then change it to the following:

import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import ProductList from "./components/ProductList";
import AddProduct from "./components/AddProduct";
import EditProduct from "./components/EditProduct";

function App() {
  return (
    <Router>
      <div className="container">
        <Switch>
          <Route exact path="/">
            <ProductList />
          </Route>
          <Route path="/add">
            <AddProduct />
          </Route>
          <Route path="/edit/:id">
            <EditProduct />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

export default App;

 

Step #10. index.js

Open the "index.js" file in the "frontend/src" folder, then change it to the following:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import "bulma/css/bulma.css";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

 

Step #11. Testing

Go back to the browser and visit the following URL:

http://localhost:3000

If it goes well, it will look like the following image:

list

Click the "Add New" button, a form like the following will appear:

add new

Enter “Title” and “Price”, then click the “SAVE” button.

If the insert is successful, you will see the addition of data to the product list as shown below:

added

To update data, click one of the "Edit" buttons, a form like the following will appear:

edit

Change the “Title” or “Price”, then click the “UPDATE” button.

If the update is successful, you will see changes in the data in the product list as shown below:

updated

To delete data, click one of the "Delete" buttons.

If the delete is successful, then the data will be lost from the product list.

 

Conclusion:

The discussion this time is how to create a full stack application with a CodeIgniter 4 in backend and a frontend using React JS.

That way, you have an idea of how to create a modern web application that separates the backend and frontend.

So what are you waiting for, Let's Coding!

Download Source Code (Back-End) Download Source Code (Front-End)

Comments (0)

Leave a Comment