DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How to Use MongoDB With Laravel
Shahed Nasser
Shahed Nasser

Posted on • Originally published at blog.shahednasser.com on

How to Use MongoDB With Laravel

This article was originally posted on my personal blog.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then, change to the directory of the website you just created:

cd laravel-mongodb
Enter fullscreen mode Exit fullscreen mode

Try to start the server:

php artisan serve
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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=
Enter fullscreen mode Exit fullscreen mode

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', '')
    ],
],
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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'];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then, we need to install the required NPM dependencies and compile the Breeze assets:

npm install && npm run dev
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Inside the controller, add the following method:

public function home() {
    return view('home');
}
Enter fullscreen mode Exit fullscreen mode

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 = '/';
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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()]);
}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

Then, add the createForm method in PostController:

public function createForm() {
    return view('post_form');
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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');
  }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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]);
  }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Now, add a new route in routes/web.php:

Route::post('/posts/delete', [PostController::class, 'delete'])->middleware(['auth'])->name('post.delete');
Enter fullscreen mode Exit fullscreen mode

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');
  }
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
shifas05 profile image
shifas05

Thank you! helpful

πŸ€” Did you know?

Β 
πŸŽ™ DEV hosts some podcasts that you can find on our Podcasts page.