Laravel is a popular PHP framework that has been around for a long time now. With Laravel, you can easily build web apps, APIs, and more.

MongoDB is a popular NoSQL database that has also been popular for a while now. With MongoDB, you can build databases that are collections of JSON-like documents.

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.

Prerequisites

Required

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.

Optional

The following are not required, but are optional to be able to follow with the article:

  • Node.js
  • Mingo, a software that allows you to easily manage and view the MongoDB databases on a server.

Create Laravel Website

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.

Install Integration Library

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

Configure the Connection

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.

Change the Models

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.

Add Authentication

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.

Install Breeze

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.

Testing Authentication

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.

Changing Default Route

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.

Implement CRUD Operations

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.

Create the Migration

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.

Create the Model

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.

Show Posts

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 and Update a Post

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.

Delete Post

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.

Conclusion

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.