25
How to Use MongoDB With Laravel
This article was originally posted on my personal blog.
Laravel is usually used with relational databases like MySQL and provides easy interfaces, facades, and methods to access, insert, update and delete the data in it.
Laravel does not come with native support for MongoDB. In order to use MongoDB with Laravel, some configurations and libraries are required to successfully integrate the two.
In this tutorial, we'll learn how to integrate MongoDB with Laravel. We'll create a simple blog with authentication and posts management.
You can find the code for this tutorial on this GitHub Repository.
Before going through the tutorial, make sure you have the following installed on your machine:
In addition, you need to install MongoDB's PHP extension. You can do so with the following command:
pecl install mongodb
If you're running on a Mac OS with Apple M1 and you get an error about pcre2.h
, run the following command:
cp /opt/homebrew/Cellar/pcre2/10.36/include/pcre2.h /opt/homebrew/Cellar/php\@7.*/7.*.*/include/php/ext/pcre/pcre2.h
Where 7.*
and 7.*.*
depend on your PHP version installed.
These dependencies are essential for development. So, make sure you have them all installed.
The following are not required, but are optional to be able to follow with the article:
In your terminal, run the following command to create a new Laravel website:
composer create-project laravel/laravel laravel-mongodb
Then, change to the directory of the website you just created:
cd laravel-mongodb
Try to start the server:
php artisan serve
If everything is ok, the website will be available at localhost:8000
.
In order to integrate MongoDB with Laravel, we need to use the package jenssegers/mongodb. So, we'll install it with Composer:
composer require jenssegers/mongodb
After installing the library, we need to configure the connection to our MongoDB server.
Make sure that your MongoDB server is running. Then, go to .env
in your Laravel project and update the following keys:
DB_CONNECTION=mongodb
DB_HOST=127.0.0.1
DB_PORT=27017
DB_DATABASE=blog
DB_USERNAME=
DB_PASSWORD=
Notice that we're using the default values for DB_HOST
and DB_PORT
for a local MongoDB server.
Make sure to add DB_USERNAME
and DB_PASSWORD
based on your MongoDB username and password. If you don't have any, then you can leave it empty.
Next, open config/database.php
and add the following inside the array of the connections
key:
'connections' => [
....,
'mongodb' => [
'driver' => 'mongodb',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 27017),
'database' => env('DB_DATABASE', 'blog'),
'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', '')
],
],
That's all that we need to do to create the connection between MongoDB and Laravel.
To test it out, we can migrate the default migrations that come with a Laravel project and see if the database will be created in MongoDB:
php artisan migrate
If the connection was done correctly, the migrations will run successfully with no errors.
To check that the database and collections have been created successfully, you can go to your MongoDB server whether by using the command line/terminal or using a GUI like Mingo. You'll see that the database has been created successfully with the necessary collections.
By default, Laravel uses Eloquent for all its models. As Eloquent does not support MongoDB, we need to change the class that our models extend. The class is provided from the package jenssegers/mongodb
that we installed earlier. It allows us to use our models and access data just like we would when using MySQL or other supported databases.
Currently, we only have one model, which is the User
model. Go to app/Models/User.php
and change the class it extends by changing the Authenticatable
class used at the beginning of the file:
use Jenssegers\Mongodb\Auth\User as Authenticatable;
As the User
is a model that can undergo authentication like registering and logging in, it should extend Jenssegers\Mongodb\Auth\User
.
The next change we need to make is related to casting dates. In order to use dates as Carbon objects, add the following inside the User
class:
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $dates = ['email_verified_at'];
That's all that is required to make a model compatible with both Laravel's Eloquent and MongoDB. We'll see in the next sections how we can access, add, modify and delete the data.
We'll add authentication to our website to allow users to register and log in. To do this and save time on creating the authentication forms and routes, we'll use Laravel's Breeze.
Breeze provides sleek-looking authentication forms using Tailwind CSS and AlpineJS. You don't need to know either to go through the tutorial.
The next few parts of the tutorial that are related to installing and configuring Breeze are optional. If you're not interested in following this part of the tutorial you can skip it.
First, we'll install Breeze:
php artisan breeze:install
Then, we need to install the required NPM dependencies and compile the Breeze assets:
npm install && npm run dev
That's it! Now, we'll have nice-looking authentication forms and routes.
Start the server if it isn't already started and go to localhost:8000/register
. You'll see a registration form that includes the basic user fields required by default in Laravel.
Try creating a user. You'll then be redirected to localhost:8000/dashboard
, which is the default in Breeze.
By default, Breeze redirects authenticated users to the route /dashboard
. We'll change it to the home page.
Go to routes/web.php
and change the content to the following:
Route::get('/', [PostController::class, 'home'])->middleware(['auth'])->name('home');
Now, when the user goes to localhost:8000
and they're not logged in, they will be redirected to the sign-in form. If they're logged in, they can access the blog.
Next, create the controller that will handle this request:
php artisan make:controller PostController
Inside the controller, add the following method:
public function home() {
return view('home');
}
This will just return the view home
. This view is actually now named dashboard.blade.php
and it's in resources/views
. So, rename it to home.blade.php
.
Then, change the link for the home page in the navigation by replacing route('dashboard')
in resources/views/layouts/navigation.blade.php
with route('home')
everywhere it's used. Additionally, replace texts that have Dashboard
in them with Home
.
Finally, change the route that should be redirected to when the user is authenticated in app/Providers/RouteServiceProvider.php
:
public const HOME = '/';
If you try to access the website on localhost:8000
, if you're still logged in you'll see the page we saw earlier after signing up.
In this section, we'll see how to create a new model that's compatible with MongoDB and perform Create, Read, Update and Delete (CRUD) operations.
First, create the migration which will create a new posts
collection in the database:
php artisan make:migration create_posts_table
This will create the migration file database/migration/YEAR_MONTH_DAY_TIME_create_posts_table
, where YEAR
, MONTH
, DAY
, and TIME
depend on when you create this migration.
Open the file and inside the up
method, change the code to the following:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->longText('content');
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete()->cascadeOnUpdate();
$table->timestamps();
});
This will create a collection where documents inside will have the fields _id
, title
, content
, user_id
which will act as a foreign key towards the users
table, and timestamps fields like created_at
and updated_at
.
Run the following to execute the migration:
php artisan migrate
This will add a new collection posts
in your database.
Next, we'll create the Post
model for the posts
table. Run the following command:
php artisan make:model Post
As we did with the User
model, we need to change the class the model extends. For User
, we used Jenssegers\Mongodb\Auth\User
as the model was authenticatable.
The Post
model is not authenticatable. So, it will just extend the class Jenssegers\Mongodb\Eloquent\Model
.
At the beginning of app/Models/Post.php
change the class Model
used to the following:
use Jenssegers\Mongodb\Eloquent\Model;
Inside the class, add the following to define the model's fields:
protected $fillable = [
'title',
'content',
'user_id'
];
protected $dates = ['created_at', 'updated_at'];
public function user () {
return $this->belongsTo(User::class);
}
We have set the fillable fields to be title
, content
, and user_id
. We've also set the dates to be created_at
and updated_at
. Finally, we've added a belongsTo
relationship between Post
and User
.
We'll add a blade component that will be used to display posts. Create the file resources/views/components/post.blade.php
with the following content:
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h1 class="text-xl md:text-2xl">{{ $post['title']}}</h1>
<p class="my-2">{{ $post['content'] }}</p>
<small class="text-gray-500">{{ $post['user']['name'] }} - {{ $post['created_at'] }}</small>
</div>
</div>
We're just showing the title, content, name of the user, and the date the post is created.
Next, we'll change the home page to display all posts, if there are any.
Go to resources/views/home.blade.php
and change the content to the following:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Home') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
@empty($posts)
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
There are no posts
</div>
</div>
@endempty
@foreach ($posts as $post)
@component('components.post', ['post' => $post])
@endcomponent
@endforeach
</div>
</div>
</x-app-layout>
Now, if there are any posts, they'll be each displayed as cards. If there aren't any, the message "There are no posts" will be shown.
Finally, we need to pass the $posts
variable from the controller to the view. Change the home
method to the following:
public function home() {
$posts = Post::with(['user'])->get();
return view('home', ['posts' => $posts->toArray()]);
}
We are able to read data from our MongoDB database the same way we would with the MySQL database, using the same Eloquent methods.
If you try opening the blog now, you'll see that the message "there are no posts is showing."
Create Post
We'll create a form that allows the logged-in user to create a post.
First, add the route in web/routes.php
:
Route::get('/posts/create', [PostController::class, 'createForm'])->middleware(['auth'])->name('post.form');
Then, add the createForm
method in PostController
:
public function createForm() {
return view('post_form');
}
Finally, create the view file resources/view/post_form.blade.php
with the following content:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ isset($post) ? __('Edit Post') :__('Create Post') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<!-- Validation Errors -->
<x-auth-validation-errors class="mb-4" :errors="$errors" />
<form method="POST" action="{{ route('post.save') }}">
@csrf
@if (isset($post))
<input type="hidden" name="id" value="{{ $post->_id }}" />
@endif
<div>
<x-label for="title" :value="__('Title')" />
<x-input id="title" class="block mt-1 w-full" type="text" name="title" :value="old('title') ?: (isset($post) ? $post->title : '')" required autofocus />
</div>
<div class="mt-3">
<x-label for="content" :value="__('Content')" />
<textarea id="content" name="content" class="block mt-1 w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" rows="5">{{ old('content') ?: (isset($post) ? $post->content : '') }}</textarea>
</div>
<div class="flex items-center justify-end mt-4">
<x-button>
{{ __('Save') }}
</x-button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
Note that we're making the form ready for editing as well (which we'll go over later). The form has 2 fields, title
and content
.
Our form is now ready, the last thing we need to do is add a link to it in the navigation bar. In resources/views/layouts/navigation.blade.php
add the following under the link for "Home":
<x-nav-link :href="route('post.form')" :active="request()->routeIs('post.form')">
{{ __('Create Post') }}
</x-nav-link>
If you go on the website now, you'll see a new link in the navigation bar for "Create Post".
Go to that page. You'll see the form we created which has the 2 fields we mentioned.
Next, we need to implement the post method that will handle saving the post.
In routes/web.php
, add the following new route:
Route::post('/posts/create', [PostController::class, 'save'])->middleware(['auth'])->name('post.save');
Then, add the save
method in PostController
:
public function save(Request $request) {
Validator::validate($request->all(), [
'id' => 'nullable|exists:posts,_id',
'title' => 'required|min:1',
'content' => 'required|min:1'
]);
/** @var User $user */
$user = Auth::user();
$id = $request->get('id');
if ($id) {
$post = Post::query()->find($id);
if ($post->user->_id !== $user->_id) {
return redirect()->route('home');
}
} else {
$post = new Post();
$post->user()->associate($user);
}
$post->title = $request->get('title');
$post->content = $request->get('content');
$post->save();
return redirect()->route('home');
}
In this method, we're first validating the fields that are required, which are title
and content
. We're also validating id
which will be passed only when the post is being edited. So, it can be nullable, but when it's available, it should exist in the posts
collection in the field _id
.
Suggested Read: Beginner’s Guide to Validation in Laravel
Next, if the post is being edited, we're validating that the user editing this post is actually the user that created it. The post's user can be easily accessed through the relationship we defined in the class.
Finally, we're creating or updating the post, setting the title, content, and user creating it. Then, we redirect back home.
You can now try adding a post. Open the form and enter a title and content, then click Save. If everything is done correctly, you'll be redirected to the home page and you can see the new post added.
You can also check on your MongoDB server if the new post has been added successfully.
Edit Post
As mentioned earlier, the form is ready to be used for editing a post.
We'll add an edit button for posts that will allow the user to edit a post.
First, create the file resources/views/components/edit.blade.php
with the following content:
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
This component is just an edit icon from Heroicons.
Now, we'll use this icon to add an edit button. Change resources/views/components/post.blade.php
to the following:
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h1 class="text-xl md:text-2xl">{{ $post['title']}}</h1>
<p class="my-2">{{ $post['content'] }}</p>
<small class="text-gray-500">{{ $post['user']['name'] }} - {{ $post['created_at'] }}</small>
@if(\Auth::user()->_id === $post['user']['_id'])
<a href="{{ route('post.edit.form', ['id' => $post['_id']]) }}" class="inline-block align-middle pb-1 text-decoration-none text-gray-600">
@component('components.edit')
@endcomponent
</a>
@endif
</div>
</div>
This will add the link to edit the post only when the post belongs to the current user.
Next, add a new route to go to the form and edit the post:
Route::get('/posts/{id}/edit', [PostController::class, 'editForm'])->middleware(['auth'])->name('post.edit.form');
Finally, add the method editForm
in PostController
:
public function editForm(Request $request, $id) {
$post = Post::query()->find($id);
if (!$post) {
return redirect()->route('home');
}
return view('post_form', ['post' => $post]);
}
That's all we need to add to be able to edit a post. Try opening the website and clicking on the edit icon for any of the posts. It will take you to the same post form, but with the values filled.
Try making edits to the post and click save. You'll be redirected to the home page and you can see the post has been updated.
The last thing we need to add is the ability to delete a post.
Create the file resources/views/components/delete.blade.php
with the following content:
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Similar to the edit button, we're creating a component for a delete icon to use for the button.
In resources/views/components/post.blade.php
add the following after the edit link:
<form method="POST" action="{{ route('post.delete') }}" class="inline-block align-middle">
@csrf
<input type="hidden" name="id" value="{{ $post['_id'] }}" />
<button type="submit" class="border-0 bg-transparent text-red-400">
@component('components.delete')
@endcomponent
</button>
</form>
Now, add a new route in routes/web.php
:
Route::post('/posts/delete', [PostController::class, 'delete'])->middleware(['auth'])->name('post.delete');
Finally, create the delete
method in PostController
:
public function delete(Request $request) {
Validator::validate($request->all(), [
'id' => 'exists:posts,_id'
]);
$post = Post::query()->find($request->get('id'));
$post->delete();
return redirect()->route('home');
}
First, we're validating that the ID is sent in the request and that it's a post that exists. Then, we're deleting the post.
Notice how to delete we can use the same eloquent methods we use in Laravel.
For simplicity, we're omitting the validation check to make sure that the post belongs to the logged-in user.
Go to the home page now. You'll see a delete icon next to each post. Try clicking on one of them. You'll be redirected back to the home page and the post will be deleted.
In this tutorial, we were able to connect a Laravel website to a MongoDB server. As we saw, the integration is simple and seamless.
Even with the integration, you can use Eloquent models like you would when using other supported databases.
More can be done with jenssegers/mongodb
package. You can try adding to the website we created a search bar or advanced search with filters. Make sure to check out the documentation of the package, as well.
25