Using MongoDB with Laravel: Beginner to Advanced
Set up MongoDB in Laravel, model documents with Eloquent, query and index collections, then move into relationships, aggregation, and production patterns.
Why MongoDB with Laravel
MongoDB stores flexible JSON-like documents instead of rigid relational rows. That fits product catalogs, activity feeds, CMS content, and analytics events where schemas change often. Laravel still gives you routing, validation, queues, and Eloquent-style models through the official mongodb/laravel-mongodb package.
Use MongoDB when documents vary by type or nest deeply. Stick with MySQL or PostgreSQL when you need strong multi-table transactions and normalized relational reporting as the default.
Beginner: install and connect
Install the package, point a database connection at MongoDB, and keep credentials in .env. You can run MongoDB locally with Docker or use MongoDB Atlas in the cloud.
- Set DB_CONNECTION=mongodb only if MongoDB is your default database
- You can keep MySQL as default and use connection = mongodb on specific models
- Verify connectivity with php artisan tinker and a simple Model::count()
composer require mongodb/laravel-mongodb
# .env
DB_CONNECTION=mongodb
MONGODB_URI=mongodb://127.0.0.1:27017
MONGODB_DATABASE=laravel_app
# config/database.php
'mongodb' => [
'driver' => 'mongodb',
'dsn' => env('MONGODB_URI', 'mongodb://127.0.0.1:27017'),
'database' => env('MONGODB_DATABASE', 'laravel_app'),
],Beginner: your first model and CRUD
MongoDB models extend MongoDB\Laravel\Eloquent\Model. Collection names are pluralized like Eloquent tables. Documents use _id by default, and fillable or guarded fields work the same way.
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
class Post extends Model
{
protected $connection = 'mongodb';
protected $collection = 'posts';
protected $fillable = [
'title',
'slug',
'body',
'tags',
'published',
];
protected $casts = [
'published' => 'boolean',
'tags' => 'array',
];
}
// Create
$post = Post::create([
'title' => 'Hello MongoDB',
'slug' => 'hello-mongodb',
'body' => 'First document from Laravel.',
'tags' => ['laravel', 'mongodb'],
'published' => true,
]);
// Read / update / delete
$post = Post::where('slug', 'hello-mongodb')->firstOrFail();
$post->update(['title' => 'Hello MongoDB + Laravel']);
$post->delete();Intermediate: query the way MongoDB thinks
Most Eloquent query methods work: where, whereIn, orderBy, limit, and pagination. MongoDB also shines with nested fields and array operators, which map cleanly to document structures.
- Store related data you always read together inside the same document when it stays small
- Use embedded arrays for tags, options, and short history lists
- Avoid unbounded arrays that grow forever inside one document
// Basic filters
Post::where('published', true)
->where('tags', 'laravel')
->orderBy('created_at', 'desc')
->paginate(20);
// Nested document fields
Order::where('customer.email', 'ada@example.com')->get();
// Array contains
Product::where('sizes', 'L')->get();
// Range queries
Event::whereBetween('occurred_at', [
now()->subDay(),
now(),
])->get();Intermediate: indexes and performance
Without indexes, MongoDB scans collections just like an unindexed SQL table. Create indexes for filters and sorts you run often, preferably through a migration so environments stay consistent.
- Index fields used in where and orderBy together when possible
- Unique indexes protect slugs, emails, and external IDs
- Check slow queries with explain() during development
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
protected $connection = 'mongodb';
public function up(): void
{
Schema::connection('mongodb')
->table('posts', function ($collection) {
$collection->index('slug', ['unique' => true]);
$collection->index(['published' => 1, 'created_at' => -1]);
$collection->index('tags');
});
}
};Intermediate: relationships
The package supports familiar relationship methods. Embed data when it belongs to one parent and is always loaded with it. Use references when documents are shared, large, or updated independently.
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\HasMany;
use MongoDB\Laravel\Relations\BelongsTo;
class User extends Model
{
protected $connection = 'mongodb';
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
protected $connection = 'mongodb';
public function author(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
$user = User::with('posts')->find($id);
$post = Post::with('author')->where('slug', $slug)->firstOrFail();Advanced: aggregation pipelines
When reports need grouping, joins, or computed fields, use aggregation instead of loading everything into PHP. You can run pipelines through the raw collection API while keeping the rest of the app on Eloquent.
use App\Models\Order;
$summary = Order::raw(function ($collection) {
return $collection->aggregate([
['$match' => ['status' => 'paid']],
['$group' => [
'_id' => '$customer_id',
'orders' => ['$sum' => 1],
'revenue' => ['$sum' => '$total'],
]],
['$sort' => ['revenue' => -1]],
['$limit' => 10],
]);
});
foreach ($summary as $row) {
// $row->_id, $row->orders, $row->revenue
}Advanced: transactions and hybrid databases
Replica-set MongoDB supports multi-document transactions. Use them when several writes must succeed or fail together. Many Laravel apps also keep auth and billing in MySQL while storing flexible content in MongoDB.
use Illuminate\Support\Facades\DB;
use App\Models\Post;
use App\Models\PostRevision;
DB::connection('mongodb')->transaction(function () {
$post = Post::create([
'title' => 'Ship carefully',
'slug' => 'ship-carefully',
'body' => 'First published version',
'published' => true,
]);
PostRevision::create([
'post_id' => $post->_id,
'body' => $post->body,
'version' => 1,
]);
});
// Hybrid: User stays on MySQL, ProfileDocument on MongoDB
class User extends Authenticatable
{
protected $connection = 'mysql';
}
class ProfileDocument extends Model
{
protected $connection = 'mongodb';
protected $collection = 'profiles';
}Advanced: validation, mass assignment, and API shape
Flexible documents still need clear rules. Validate input at the Form Request boundary, cast arrays and dates on the model, and return stable API resources so clients are not coupled to MongoDB's _id field name.
// Form Request
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:160'],
'slug' => ['required', 'alpha_dash', 'max:160'],
'body' => ['required', 'string'],
'tags' => ['array'],
'tags.*' => ['string', 'max:40'],
'published' => ['boolean'],
];
}
// API Resource
public function toArray($request): array
{
return [
'id' => (string) $this->_id,
'title' => $this->title,
'slug' => $this->slug,
'tags' => $this->tags ?? [],
'published' => (bool) $this->published,
'created_at' => optional($this->created_at)?->toISOString(),
];
}Production checklist
Treat MongoDB like any other production datastore: monitor slow operations, back up regularly, and keep indexes aligned with real queries. Start simple with CRUD and indexes, then introduce aggregation and transactions only when the problem needs them.
- Use Atlas or a replica set before relying on transactions
- Cap document growth and move hot history into separate collections
- Add indexes in migrations, never only by hand in staging
- Keep secrets in .env and never expose the MongoDB URI to the frontend
- Log failed writes and aggregation errors with enough context to replay
