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:
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:
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:
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:
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:
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:
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:
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:
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:
Then, open your browser and visit the following URL:
http://localhost:3000
Then it will appear as follows:
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:
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:
Click the "Add New" button, a form like the following will appear:
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:
To update data, click one of the "Edit" buttons, a form like the following will appear:
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:
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)