MongoDB + Laravel = Love — When to use NoSQL

Alex Renoki
FAUN — Developer Community 🐾
7 min readJan 17, 2019

--

Photo by Manuel Geissinger from Pexels

This post has an audio version thanks to Miguel Piedrafita’s Blogcast app.

In case you have followed me for quite a while, I have published this specific article: Setup MongoDB for Laravel on ploi.io. In case you need to deploy your application to production without “hassle”, ploi.io might seem a great choice. This time, I’ll introduce you to MongoDB on Laravel.

NoSQL (MongoDB) seems quite unpopular on Laravel, not because no one cares, but because not many people are going to use Mongo over SQL since SQL is already embedded into Laravel and provides a great experience for newcomers to understand how Laravel works with databases.

In case you don’t know what are the ups’n’downs of NoSQL over SQL, head back to the previously mentioned article on which not only I’m explaining how to deploy MongoDB on a cloud server instance, but that’s all about running NoSQL with Laravel. This time, I’ll just break through each up’n’down shortly while giving examples and how things work, how to use it locally, and how they affect the production environment.

Starting from ground zero — you have to be quite familiar with Laravel Eloquent ORM to adventure yourself further. If you are not quite familiar, it’s always time to learn! Be eager to learn things all the time!

Prerequisites & running it locally

There’s nothing much to do — you just need to have MongoDB running on your machine. I strongly encourage people to run a Valet or Homestead on their local machines because it’s easier. When it comes to MongoDB, I only worked with Homestead. So if you are one of the lucky winners that can work with Homestead, head on your Homestead.yaml file and add mongodb: true to that. This is my pretty-basic configuration for Homestead:

ip: 192.168.10.13
memory: 4096
cpus: 2
mongodb: true
mariadb: true
provider: virtualbox
authorize: ~/.ssh/id_rsa.pub
keys:
- ~/.ssh/id_rsa
folders:
-
map: 'C:\Laravel\homestead'
to: /home/vagrant/code
sites:
-
map: homestead.test
to: /home/vagrant/code/public
php: "7.2"
schedule: true
databases:
- homestead
name: homestead
hostname: homestead

I also encourage you to use the vendor/bin/homestead make command instead of copying the configuration I stated as an example because the path to your folder should be auto-generated by that command. What I stated in this example was the fact that I have a mongodb: true there.

All you have to do further is to re-provision the machine (or run it with --provision in case you never up-ed your machine).

Congrats! You got your MongoDB server running inside the VM. It can be accessed using localhost as host and homestead and secret as user and password in your .env.

Eloquent ORM for NoSQL

Jens Segers’ MongoDB package is pretty popular. It might be more packages that handle NoSQL, but when it’s based on the community — we must trust it more. I have also run it in production for quite a while, and so far, I never encountered problems with it.

The setup is pretty straightforward if you read the documentation — it will help you install it through Composer, set up a database driver. We’ll focus just on the important things.

This package offers a Moloquent model, as the documentation states. It is an Eloquent model but made for Mongo. If you worked with ORM for a while, you would be familiar with this kind of look:

class Post extends Model
{
//
}

In this case, we will no longer extend from Model, the basic model which Laravel gives us.

use Jenssegers\Mongodb\Eloquent\Model;class Post extends Model
{
//
}

SQL works with tables, NoSQL works with collections. Instead of the $table variable, we’ll have a $collection variable. Also, it’s pretty important to note that the primary key cannot be set through $primaryKey and the $incrementing is unavailable. Additionally, you might want to specify that the model belongs to the mongodb connection you have created earlier:

use Jenssegers\Mongodb\Eloquent\Model;class Post extends Model
{
protected $connection = 'mongodb';
}

You must be at peace with the fact that MongoDB auto-assigns primary keys to documents (the equivalent of rows from SQL). So, to access the primary key of a document, you just have to use the same attribute name as in the basic model:

echo 'The post ID is: '. $post->_id;
echo 'The post ID is '. $post->id; // they both work

Using find() uses the primary key field to retrieve the result:

$post = Post::find('517c43667db388101e00000f');

Almost no schemas to define

In NoSQL, we don’t have schemas to define. We can go as crazy as we want. But we can take advantage of the migrations to define indexes, unique fields, and other mongo-specific fields:

Schema::create('posts', function ($collection) {
$collection->index('slug');
$collection->unique('slug');
});

The migration process is the same. To be able to run the migrations, make sure that the default driver set (the DB_CONNECTION env variable) is set to mongodb.

php artisan migrate

In case you wanna run both SQL and NoSQL in the same project, I can give you some hints:

  • move your SQL migrations to a folder inside the migrations folder. I’ll call it mysql.
  • all models that work with other than the default database driver should extend the right model

Running migrations should be done both ways, either it’s local or in production.

For the default driver (i.e. when DB_CONNECTION is set to mongodb):

php artisan migrate

For the other driver (i.e. for MySQL):

php artisan migrate --database=mysql --path=database/migrations/mysql/

Authenticating users with MongoDB

A big problem that I have encountered with MongoDB was when it was about the User. The User model extends an Authenticatable model. MongoDB does not support the default model that Laravel offers, so we’ll have to use the one that comes with the package:

use Jenssegers\Mongodb\Auth\User as Authenticatable;class User extends Authenticatable
{
//
}

In case you want to use Laravel Passport with MongoDB, make sure you use designmynight/laravel-mongodb-passport. It will provide you another Authenticatable class that will do the job for you!

NoSQL = NoJoins

NoSQL is good for some reasons over SQL: flexibility, speed, scalability, and integrity. One down from the ups’n’downs story is that we don’t have joins. We cannot use a third collection to join data between two other collections. However, the package has some cards hidden under the sleeve.

For example, a belongsToMany relationship would look like this:

class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User');
}
}

Either you’re trying to access $user->roles or $role->users, you will need a third table called role_user that stores data.

In this case, MongoDB does something nasty: it allows you many-to-many, but it does not need a third table — it simply will store ids in each other’s model. Make sure that if you want to set custom foreign keys, you set the second parameter of the belongsToMany function to null.

class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role', null, 'users_ids', 'roles_ids');
}
}
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User', null, 'roles_ids', 'users_ids');
}
}
$role = Role::named('admin')->first();$user->attach($role);
$role->attach($user);

After doing this, you’ll end up with something like this:

The User document looks like this:

{
"_id": "user_id_here",
"username": "dummy",
...,
"roles_ids": [
"role_id_here"
]
}

The Role document looks like this:

{
"_id": "role_id_here",
"name": "administrator",
...,
"users_ids": [
"user_id_here"
]
}

At the same time, the rest of the other relationships, excepting morphToManyworks the same way as they do in SQL.

Schema is your playground

So, as long as NoSQL is schemaless, we can define it while we work with data, right? In MongoDB, we’re able to drop fields whenever we want.

This is our user document:

{
"_id": "...",
"name": "John",
"password": "...",
"old_field1": "value1",
"old_field2": "value2",
}

So, if we want to drop some fields, we can do it using the drop method:

$john = User::where('name', 'John')->first();$john->drop('old_field1');// or$john->drop(['old_field1', 'old_field2']); // works with more

In the end, it will simply remove the field from our document:

{
"_id": "...",
"name": "John",
"password": "..."
}

Pushing it into production

So, for quite a while, I’ve been running MongoDB in production. The truth is, that with the basic usage of the ORM with a small-configuration MongoDB like I have described in Setup MongoDB for Laravel on ploi.io, it just works without problems. Even for high-traffic apps, MongoDB is fast and reliable. On average, it consumes around 4 GB of RAM with around 600 million document reads, writes, and deletes on an entire month, with an average of 600 writes and reads, combined, per second. What I don’t recommend is using it for caching — whenever you wanna cache, just remember to use memory over disk — it provides less friction and more speed of accessing and writing.

💸 Sponsorship

Hi, I’m Alex, the founder of Renoki Co.. I’m thankful for taking your time to read this article, and I hope that it helped you. Developing and maintaining packages and delivering good articles about Laravel, Kubernetes and AWS take a lot of time, but I believe it’s a time well spent.

If you support more helpful articles, or you are using one or more Renoki Co. open-source packages in your production apps, in presentation demos, hobby projects, school projects or so, sponsor our work with Github Sponsors. 📦

👋 Join FAUN today and receive similar stories each week in your inbox! Get your weekly dose of the must-read tech stories, news, and tutorials.

Follow us on Twitter 🐦 and Facebook 👥 and Instagram 📷 and join our Facebook and Linkedin Groups 💬

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author! ⬇

--

--

Minimalist Laravel developer. Full stacking with AWS and Vue.js. Sometimes I deploy to Kubernetes. 🚢