CodeIgniter is a modern PHP framework that’s easy to learn and use. It’s a powerful tool for building web applications. MongoDB Atlas is a cloud-based database service that provides a simple, flexible, and reliable way to store, manage, and query data. In this tutorial, we'll learn how to connect CodeIgniter and MongoDB Atlas to build a full-featured CRUD (Create, Read, Update, Delete) application. The source code for this project is available on GitHub.
In this tutorial, we'll build a reading journal application. The application will allow users to track their reading progress. Users will be able to add books, update their progress, and see a list of all books they've been reading. Here's a demonstration of the finished application:
You can follow this tutorial step by step and build your application from scratch. No preexisting knowledge of CodeIgniter or MongoDB is required. However, you will need a couple of things:
The following sections will guide you through the required setup. If you want the final code instead, you can find it on GitHub.
For this tutorial, we'll be using MongoDB Atlas to store our data. Since Atlas is cloud-based and offers a forever-free cluster, we won't need to worry about installing and maintaining a local copy of MongoDB. Follow the Get Started with Atlas guide to create a free account and set up a cluster.
We'll build our application with CodeIgniter4 which, at the moment of writing, is the latest version of CodeIgniter. Since CodeIgniter4 is a PHP framework, we need to make sure we've got PHP version 7.3 or newer installed on our machine. To verify your PHP version, run the following command in your terminal emulator:
php --version
To set up a new CodeIgniter4 project, we'll use Composer. Composer is a package manager for PHP. It allows you to manage your dependencies and install them with one command. To install Composer, follow the official installation instructions.
Once you've installed Composer, run the following command to create a new CodeIngiter4 project:
composer create-project codeigniter4/appstarter reading-journal-codeigniter-mongodb
cd reading-journal-codeigniter-mongodb
To connect our CodeIgniter application to MongoDB, we'll need to install the MongoDB PHP Driver. The driver consists of two components—the extension and the library.
First, we'll install the extension and load it in the php.ini
file:
pecl install mongodb
echo "extension=mongodb.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
Next, we'll install the library with Composer:
composer require mongodb/mongodb
Finally, we'll create an .env
file in the root directory of our project (note the leading dot). This file will contain server-specific settings, such as the MongoDB Atlas connection string and the database name. You can choose any name you want for your database but make sure your connection string and credentials are correct.
reading-journal-codeigniter-mongodb/.env
CI_ENVIRONMENT = development
ATLAS_URI = 'mongodb+srv://<username>:<password>@sandbox.pv0l7.mongodb.net/?retryWrites=true&w=majority'
DATABASE = 'codeigniter'
Now, we can run the application with PHP's built-in server:
php spark serve
Open your browser and navigate to http://localhost:8000/. You see the CodeIgniter4 getting started page:
Great job! We've finished our setup and are ready to start building our “Reading Journal” application!
First, we'll need to connect our CodeIgniter application to MongoDB Atlas. We already installed the MongoDB PHP Driver with Composer. We'll create a DatabaseConnector
library class that will connect to the database using the driver. The class will also expose a database
object for the rest of the application. Create a new DatabaseConnector.php
file in the app/Libraries
directory and add the following code:
reading-journal-codeigniter-mongodb/app/Libraries/DatabaseConnector.php
<?php
namespace App\Libraries;
class DatabaseConnector {
private $client;
private $database;
function __construct() {
$uri = getenv('ATLAS_URI');
$database = getenv('DATABASE');
if (empty($uri) || empty($database)) {
show_error('You need to declare ATLAS_URI and DATABASE in your .env file!');
}
try {
$this->client = new \MongoDB\Client($uri);
} catch(MongoDB\Driver\Exception\MongoConnectionException $ex) {
show_error('Couldn\'t connect to database: ' . $ex->getMessage(), 500);
}
try {
$this->database = $this->client->selectDatabase($database);
} catch(MongoDB\Driver\Exception\RuntimeException $ex) {
show_error('Error while fetching database with name: ' . $database . $ex->getMessage(), 500);
}
}
function getDatabase() {
return $this->database;
}
}
Next, we'll create a books model that will handle the CRUD operations for our books. Create a new BooksModel.php
file in the app/Models
directory and add the following code:
reading-journal-codeigniter-mongodb/app/Models/BooksModel.php
<?php
namespace App\Models;
use App\Libraries\DatabaseConnector;
class BooksModel {
private $collection;
function __construct() {
$connection = new DatabaseConnector();
$database = $connection->getDatabase();
$this->collection = $database->books;
}
function getBooks($limit = 10) {
try {
$cursor = $this->collection->find([], ['limit' => $limit]);
$books = $cursor->toArray();
return $books;
} catch(\MongoDB\Exception\RuntimeException $ex) {
show_error('Error while fetching books: ' . $ex->getMessage(), 500);
}
}
function getBook($id) {
try {
$book = $this->collection->findOne(['_id' => new \MongoDB\BSON\ObjectId($id)]);
return $book;
} catch(\MongoDB\Exception\RuntimeException $ex) {
show_error('Error while fetching book with ID: ' . $id . $ex->getMessage(), 500);
}
}
function insertBook($title, $author, $pages) {
try {
$insertOneResult = $this->collection->insertOne([
'title' => $title,
'author' => $author,
'pages' => $pages,
'pagesRead' => 0,
]);
if($insertOneResult->getInsertedCount() == 1) {
return true;
}
return false;
} catch(\MongoDB\Exception\RuntimeException $ex) {
show_error('Error while creating a book: ' . $ex->getMessage(), 500);
}
}
function updateBook($id, $title, $author, $pagesRead) {
try {
$result = $this->collection->updateOne(
['_id' => new \MongoDB\BSON\ObjectId($id)],
['$set' => [
'title' => $title,
'author' => $author,
'pagesRead' => $pagesRead,
]]
);
if($result->getModifiedCount()) {
return true;
}
return false;
} catch(\MongoDB\Exception\RuntimeException $ex) {
show_error('Error while updating a book with ID: ' . $id . $ex->getMessage(), 500);
}
}
function deleteBook($id) {
try {
$result = $this->collection->deleteOne(['_id' => new \MongoDB\BSON\ObjectId($id)]);
if($result->getDeletedCount() == 1) {
return true;
}
return false;
} catch(\MongoDB\Exception\RuntimeException $ex) {
show_error('Error while deleting a book with ID: ' . $id . $ex->getMessage(), 500);
}
}
}
In the constructor of the BooksModel
class, we're instantiating a new DatabaseConnector
object to connect to the database. We're using the getDatabase()
method to access the database
object and the books
collection. We're using the books
collection throughout the model to perform CRUD operations.
Note: If you have multiple models in your application, you should reuse the database connection instead of creating a new connection for each model. An easy way to do that is by converting the DatabaseConnector
class to a singleton.
In the BooksModel
class methods, we're using collection methods from the MongoDB PHP driver API to fetch, insert, update, and delete books:
find()
method fetches a $limit
number of documents from the books collection.findOne()
method fetches a single document from the books collection based on the _id
field.insertOne()
method inserts a single document into the books collection.updateOne()
method updates a single document in the books collection based on the _id
field.deleteOne()
method deletes a single document from the books collection based on the _id
field.Following the MVC (model-view-controller) pattern, next we need to create views for our applications pages:
list.php
view will display a list of books.create.php
view will allow users to create a new book.details.php
view will display a single book.edit.php
view will allow users to edit a book.To complement the pages, we'll also create header.php
and footer.php
templates that will be included on each page. These templates will include the navigation menu, the footer copyright, and the Bootstrap library for styling.
The final piece of the MVC pattern will be the Books
controller class. The controller will fetch data using the BooksModel
and pass it to the views. It will also be responsible for validating the user input and submitting form data to the model.
Let's get started!
The header.php
and footer.php
templates will be respectively the beginning and the end of each page. First, create the header.php
template in app/Views/templates
with the following content:
reading-journal-codeigniter-mongodb/app/Views/templates/header.php
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Reading Journal: MongoDB & CodeIgniter CRUD Tutorial</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">Reading Journal</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/books">Books List</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/books/create">Add a Book</a>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
Then, add the footer.php
template to the same directory:
reading-journal-codeigniter-mongodb/app/Views/templates/footer.php
</div>
<footer class="text-center">
<small>© 2022</small>
</footer>
<!-- Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
First, we'll create the list.php
view in the app/Views/books
directory.
reading-journal-codeigniter-mongodb/app/Views/books/list.php
<?php if (! empty($books) && is_array($books)): ?>
<table class="table table-striped table-bordered mt-4">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Progress</th>
</tr>
</thead>
<tbody>
<?php foreach ($books as $book): ?>
<tr>
<td>
<a href="<?= '/books/' . $book['_id'] ?>">
<?= esc($book['title']) ?>
</a>
</td>
<td><?= esc($book['author']) ?></td>
<td><?= round(esc($book['pagesRead']) / esc($book['pages']) * 100, 2) ?>%</td>
<td>
<a class="btn btn-primary" href="<?= '/books/edit/' . $book['_id'] ?>">Edit</a>
<a class="btn btn-danger" href="<?= '/books/delete/' . $book['_id'] ?>">Delete</a>
</td>
</tr>
<?php endforeach ?>
</table>
<?php else: ?>
<h2>You don't have any books yet!</h3>
<?php endif ?>
<a href="books/create">
<button class="btn btn-primary mt-4">Add a book</button>
</a>
The $books
variable will contain the list of books passed from the controller. The foreach
loop will iterate over the list of books and display each one in a table row.
Next, let's add the create.php
view.
reading-journal-codeigniter-mongodb/app/Views/books/create.php.
<h2><?= esc($title) ?></h2>
<?= session()->getFlashdata('error') ?>
<?= service('validation')->listErrors() ?>
<form action="/books/create" method="post">
<?= csrf_field() ?>
<div class="mt-3">
<label class="form-label" for="title">Title</label>
<input class="form-control" type="input" name="title" />
</div>
<div class="mt-3">
<label class="form-label" for="author">Author</label>
<input class="form-control" type="input" name="author" />
</div>
<div class="mt-3">
<label class="form-label" for="pages">Pages</label>
<input class="form-control" type="number" name="pages" />
</div>
<input class="btn btn-success mt-4" type="submit" name="submit" value="Add Book" />
</form>
For the create.php
view, we'll need to add the csrf_field()
helper function to prevent cross-site request forgery. To load this helper, we'll add the following line to the app/Config/Filters.php
file in the $methods
array:
reading-journal-codeigniter-mongodb/app/Config/Filters.php
public $methods = [
'post' => ['csrf'], // add this line
];
The next view we'll create is the edit.php
view.
reading-journal-codeigniter-mongodb/app/Views/books/edit.php
<h2><?= esc($title) ?></h2>
<?= session()->getFlashdata('error') ?>
<?= service('validation')->listErrors() ?>
<form action="<?= '/books/edit/' . $book['_id'] ?>" method="post" autocomplete="off">
<?= csrf_field() ?>
<div class="form-floating mb-3">
<input value="<?= $book['title'] ?>" class="form-control" type="text" name="title" placeholder="Title" required>
<label for="title">Title</label>
</div>
<div class="form-floating mb-3">
<input value="<?= $book['author'] ?>" class="form-control" type="text" name="author" placeholder="Author" required>
<label for="author">Author</label>
</div>
<div class="form-floating mb-3">
<input value="<?= $book['pagesRead'] ?>" class="form-control" type="number" name="pagesRead" value="0" required>
<label for="pagesRead">Read Pages</label>
</div>
<button class="btn btn-primary" name="submit">Edit Book</button>
</form>
Finally, we'll add the details.php
view.
reading-journal-codeigniter-mongodb/app/Views/books/details.php
<h2><?= esc($book['title']) ?></h2>
<div class="mt-4">
<p>Author: <?= esc($book['author']) ?></p>
<p>Pages: <?= esc($book['pages']) ?></p>
<p>Pages read: <?= esc($book['pagesRead']) ?></p>
<p id="reading-progress">Your reading progress:</p>
<div class="progress">
<div
class="progress-bar"
role="progressbar"
style="width: <?= $book['progress'] ?>%"
aria-valuenow="<?= $book['progress'] ?>"
aria-valuemin="0"
aria-valuemax="100"
aria-labelledby="reading-progress"
><?= $book['progress'] ?>%</div>
</div>
<a href=".." class="btn btn-link mt-3">Go back</a>
</div>
We're all done with the views! Next up, let's implement the books controller that will manage the communication between the views and the model.
Create the Books.php
class in the app/Controllers
directory.
reading-journal-codeigniter-mongodb/app/Controllers/Books.php
<?php
namespace App\Controllers;
helper('inflector');
use App\Models\BooksModel;
class Books extends BaseController
{
public function index()
{
$model = model(BooksModel::class);
$data = [
'books' => $model->getBooks(),
];
echo view('templates/header', $data);
echo view('books/list', $data);
echo view('templates/footer', $data);
}
public function details($segment = null)
{
$model = model(BooksModel::class);
$data['book'] = $model->getBook($segment);
if (empty($data['book'])) {
throw new \CodeIgniter\Exceptions\PageNotFoundException('Cannot find book with ID: ' . $segment);
}
$data['title'] = $data['book']['title'];
$data['book']['progress'] = round($data['book']['pagesRead'] / $data['book']['pages'] * 100, 2);
echo view('templates/header', $data);
echo view('books/details', $data);
echo view('templates/footer', $data);
}
public function create()
{
$model = model(BooksModel::class);
if ($this->request->getMethod() === 'post' && $this->validate([
'title' => 'required|min_length[1]|max_length[255]',
'author' => 'required|min_length[1]|max_length[255]',
'pages' => 'required|is_natural_no_zero',
])) {
$model->insertBook(
$this->request->getPost('title'),
$this->request->getPost('author'),
$this->request->getPost('pages'),
);
return redirect()->to('books');
} else {
echo view('templates/header');
echo view('books/create', ['title' => 'Add a new book']);
echo view('templates/footer');
}
}
public function edit($segment = null)
{
$model = model(BooksModel::class);
$data['book'] = $model->getBook($segment);
if (empty($data['book'])) {
throw new \CodeIgniter\Exceptions\PageNotFoundException('Cannot find book with ID: ' . $segment);
}
$data['title'] = $data['book']['title'];
if ($this->request->getMethod() === 'post' && $this->validate([
'title' => 'required|min_length[1]|max_length[255]',
'author' => 'required|min_length[1]|max_length[255]',
'pagesRead' => 'required|is_natural',
])) {
$model->updateBook(
$data['book']['_id'],
$this->request->getPost('title'),
$this->request->getPost('author'),
$this->request->getPost('pagesRead'),
);
return redirect()->to('books');
} else {
echo view('templates/header', $data);
echo view('books/edit', $data);
echo view('templates/footer', $data);
}
}
public function delete($segment = null) {
if (!empty($segment) && $this->request->getMethod() == 'get') {
$model = model(BooksModel::class);
$model->deleteBook($segment);
}
return redirect()->to('books');
}
}
For every action in the controller, we're using the BooksModel
to query the database or modify the data. We're also validating the form data using the CodeIgniter built-in validation helpers.
Finally, we need to set up the routes. Open the app/Config/Routes.php
file and replace its contents with the following:
reading-journal-codeigniter-mongodb/app/Config/Routes.php
<?php
namespace Config;
// Create a new instance of our RouteCollection class.
$routes = Services::routes();
// Load the system's routing file first, so that the app and ENVIRONMENT
// can override as needed.
if (file_exists(SYSTEMPATH . 'Config/Routes.php')) {
require SYSTEMPATH . 'Config/Routes.php';
}
/*
* --------------------------------------------------------------------
* Router Setup
* --------------------------------------------------------------------
*/
$routes->setDefaultNamespace('App\Controllers');
$routes->setDefaultController('Books');
$routes->setDefaultMethod('index');
$routes->setTranslateURIDashes(false);
$routes->set404Override();
$routes->setAutoRoute(false);
/*
* --------------------------------------------------------------------
* Route Definitions
* --------------------------------------------------------------------
*/
$routes->match(['get', 'post'], 'books/create', 'Books::create');
$routes->match(['get', 'post'], 'books/edit/(:segment)', 'Books::edit/$1');
$routes->get('books/delete/(:segment)', 'Books::delete/$1');
$routes->get('books/(:segment)', 'Books::details/$1');
$routes->get('books', 'Books::index');
$routes->addRedirect('/', 'books');
/*
* --------------------------------------------------------------------
* Additional Routing
* --------------------------------------------------------------------
*
* There will often be times that you need additional routing and you
* need it to be able to override any defaults in this file. Environment
* based routes is one such time. require() additional route files here
* to make that happen.
*
* You will have access to the $routes object within that file without
* needing to reload it.
*/
if (file_exists(APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php')) {
require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
}
Save the file, and you're ready to go!
Refresh your browser window and play around with the application. You can add a new book, edit an existing book, and delete a book.
In this step-by-step tutorial, we learned how to create a CRUD application using CodeIgniter4 and MongoDB. CodeIgniter4 is a modern PHP framework. Combining it with MongoDB's document model allows you to create modern data-driven web applications with ease. We learned how to use the MongoDB PHP driver to query and modify data from our database.
We also saw how easy it is to use MongoDB Atlas, the data platform by MongoDB. If you haven’t already, you can create a free account and your first forever-free cluster today!