Getting Started with FerretDB
MongoDB's document model and query API are excellent. Flexible schemas, nested arrays, expressive query operators, massive ecosystem. But MongoDB changed to a non-open-source license (SSPL), which creates real problems for companies that need to self-host or embed a document database without licensing risk.
FerretDB solves this. It implements the MongoDB wire protocol on top of PostgreSQL, so your existing MongoDB code (drivers, ODMs, tools like mongosh) works without changes. Under the hood, documents live in PostgreSQL: ACID transactions, mature backup tooling (pg_dump), and the entire PostgreSQL extension ecosystem. No SSPL, no license concerns.
We'll build a product catalog in one TypeScript file: inserting documents, running queries, filtering arrays, and building an aggregation pipeline. Follow along locally, or create a managed instance on Layerbase Cloud.
Contents
- Create a FerretDB Instance
- Set Up the Project
- The Product Dataset
- Connect and Insert
- Query with Filters
- Array Matching
- Aggregation Pipeline
- Regex Search
- The PostgreSQL Advantage
- When to Use FerretDB
- Wrapping Up
Create a FerretDB Instance
Local with the Layerbase CLI
The Layerbase CLI (formerly SpinDB) gets FerretDB (and its PostgreSQL backend) running with a single command. No Docker, no manual Postgres setup. (What is the Layerbase CLI?)
Install the Layerbase CLI globally:
npm i -g layerbase # npm
pnpm add -g layerbase # pnpmOr run it directly without installing:
npx layerbase create ferret1 -e ferretdb --start # npm
pnpx layerbase create ferret1 -e ferretdb --start # pnpmIf you installed globally, create and start a FerretDB instance:
lbase create ferret1 -e ferretdb --startThe Layerbase CLI downloads FerretDB and its PostgreSQL backend, configures both, and starts the server. Verify it's running:
lbase url ferret1mongodb://127.0.0.1:27017Standard MongoDB connection URL. Any MongoDB client or driver connects to it without knowing FerretDB is behind it. Leave the server running.
Layerbase Cloud
Layerbase Cloud provisions managed FerretDB instances. Pick FerretDB from the engine list and grab your connection URL from the Quick Connect panel.
Cloud instances use TLS, so the connection string looks like this:
const client = new MongoClient(
'mongodb://user:pass@cloud.layerbase.dev:PORT/db?tls=true',
)Everything else in this guide works identically whether you're running locally or on Layerbase Cloud. Just swap in your connection details.
Set Up the Project
mkdir ferretdb-products && cd ferretdb-products
pnpm init
pnpm add mongodb
pnpm add -D tsx typescriptCreate a file called catalog.ts. All the code in this post goes into that one file.
Because FerretDB speaks the MongoDB wire protocol, the standard mongodb driver works without any adapter or plugin.
The Product Dataset
Fifteen products for an e-commerce store. Nested arrays and mixed types, the kind of flexible documents that make a document database useful:
const products = [
{
id: 1,
name: 'Wireless Earbuds Pro',
category: 'Electronics',
price: 79.99,
tags: ['wireless', 'bluetooth', 'audio'],
rating: 4.5,
inStock: true,
},
{
id: 2,
name: 'Running Shoes Ultra',
category: 'Sports',
price: 129.99,
tags: ['outdoor', 'running', 'lightweight'],
rating: 4.7,
inStock: true,
},
{
id: 3,
name: 'Cotton T-Shirt Classic',
category: 'Clothing',
price: 24.99,
tags: ['casual', 'cotton', 'basics'],
rating: 4.2,
inStock: true,
},
{
id: 4,
name: 'Smart Home Hub',
category: 'Electronics',
price: 149.99,
tags: ['wireless', 'smart-home', 'premium'],
rating: 4.3,
inStock: false,
},
{
id: 5,
name: 'Yoga Mat Premium',
category: 'Sports',
price: 49.99,
tags: ['indoor', 'premium', 'exercise'],
rating: 4.8,
inStock: true,
},
{
id: 6,
name: 'Ceramic Coffee Mug Set',
category: 'Home',
price: 34.99,
tags: ['kitchen', 'ceramic', 'gift'],
rating: 4.6,
inStock: true,
},
{
id: 7,
name: 'Bluetooth Speaker Portable',
category: 'Electronics',
price: 59.99,
tags: ['wireless', 'bluetooth', 'portable', 'outdoor'],
rating: 4.4,
inStock: true,
},
{
id: 8,
name: 'Hiking Backpack 40L',
category: 'Sports',
price: 89.99,
tags: ['outdoor', 'hiking', 'waterproof'],
rating: 4.5,
inStock: true,
},
{
id: 9,
name: 'TypeScript Programming Guide',
category: 'Books',
price: 39.99,
tags: ['programming', 'typescript', 'reference'],
rating: 4.9,
inStock: true,
},
{
id: 10,
name: 'LED Desk Lamp',
category: 'Home',
price: 44.99,
tags: ['lighting', 'adjustable', 'office'],
rating: 4.3,
inStock: true,
},
{
id: 11,
name: 'Noise Cancelling Headphones',
category: 'Electronics',
price: 249.99,
tags: ['wireless', 'premium', 'noise-cancelling', 'audio'],
rating: 4.7,
inStock: false,
},
{
id: 12,
name: 'Wool Winter Jacket',
category: 'Clothing',
price: 189.99,
tags: ['winter', 'wool', 'premium'],
rating: 4.4,
inStock: true,
},
{
id: 13,
name: 'Stainless Steel Water Bottle',
category: 'Sports',
price: 29.99,
tags: ['outdoor', 'reusable', 'insulated'],
rating: 4.6,
inStock: true,
},
{
id: 14,
name: 'Smartphone Stand Adjustable',
category: 'Electronics',
price: 19.99,
tags: ['desk', 'adjustable', 'portable'],
rating: 4.1,
inStock: true,
},
{
id: 15,
name: 'Scented Candle Collection',
category: 'Home',
price: 27.99,
tags: ['relaxation', 'gift', 'aromatic'],
rating: 4.5,
inStock: true,
},
]Each product has a tags array of variable length, plus a mix of numbers, strings, and booleans. In a relational database you'd need a separate product_tags junction table. Here, it's just a field.
Connect and Insert
Connect with the standard MongoDB driver and insert the dataset:
import { MongoClient } from 'mongodb'
const client = new MongoClient('mongodb://localhost:27017')
await client.connect()
const db = client.db('store')
const collection = db.collection('products')
// Clean up from previous runs
await collection.drop().catch(() => {})
const result = await collection.insertMany(products)
console.log(`Inserted ${result.insertedCount} products`)Identical to what you'd write for MongoDB. MongoClient connects to port 27017, gets a database reference, and inserts documents. FerretDB accepts these commands over the wire protocol and translates them into PostgreSQL operations under the hood.
Query with Filters
Field matching, comparison operators, logical combinations. All work through FerretDB:
// Electronics under $100
const cheapElectronics = await collection
.find({ category: 'Electronics', price: { $lt: 100 } })
.toArray()
console.log('\nElectronics under $100:')
for (const p of cheapElectronics) {
console.log(` ${p.name} - $${p.price}`)
}
// High-rated products that are in stock
const topRated = await collection
.find({ rating: { $gte: 4.5 }, inStock: true })
.sort({ rating: -1 })
.toArray()
console.log('\nTop rated (4.5+), in stock:')
for (const p of topRated) {
console.log(` ${p.name} - ${p.rating} stars`)
}Electronics under $100:
Wireless Earbuds Pro - $79.99
Bluetooth Speaker Portable - $59.99
Smartphone Stand Adjustable - $19.99
Top rated (4.5+), in stock:
TypeScript Programming Guide - 4.9 stars
Yoga Mat Premium - 4.8 stars
Running Shoes Ultra - 4.7 stars
Ceramic Coffee Mug Set - 4.6 stars
Stainless Steel Water Bottle - 4.6 stars
Wireless Earbuds Pro - 4.5 stars
Hiking Backpack 40L - 4.5 stars
Scented Candle Collection - 4.5 stars$lt, $gte, field matching, .sort(). All work exactly as they do in MongoDB.
Array Matching
One of MongoDB's most useful features is how it handles arrays. $in matches documents where an array field contains any of the specified values:
// Products tagged "wireless" or "bluetooth"
const wirelessProducts = await collection
.find({ tags: { $in: ['wireless', 'bluetooth'] } })
.toArray()
console.log('\nWireless or Bluetooth products:')
for (const p of wirelessProducts) {
console.log(` ${p.name} - tags: ${p.tags.join(', ')}`)
}Wireless or Bluetooth products:
Wireless Earbuds Pro - tags: wireless, bluetooth, audio
Smart Home Hub - tags: wireless, smart-home, premium
Bluetooth Speaker Portable - tags: wireless, bluetooth, portable, outdoor
Noise Cancelling Headphones - tags: wireless, premium, noise-cancelling, audioNo joins, no junction tables, no subqueries. The array is a first-class citizen in the query language.
Aggregation Pipeline
The aggregation framework supports multi-stage data transformations. Group products by category, calculate average price and count:
const categoryStats = await collection
.aggregate([
{
$group: {
_id: '$category',
avgPrice: { $avg: '$price' },
count: { $sum: 1 },
},
},
{ $sort: { avgPrice: -1 } },
])
.toArray()
console.log('\nCategory statistics:')
console.log(' Category Avg Price Count')
for (const stat of categoryStats) {
const avg = (stat.avgPrice as number).toFixed(2)
console.log(` ${String(stat._id).padEnd(14)} $${avg.padStart(7)} ${stat.count}`)
}Category statistics:
Category Avg Price Count
Electronics $111.99 5
Clothing $107.49 2
Sports $74.99 4
Books $39.99 1
Home $35.99 3$group collects documents by category, $avg computes the mean price, $sum: 1 counts per group. Same aggregation pipeline syntax MongoDB developers already know.
Regex Search
Regex matching on string fields works through FerretDB too:
// Find products with "phone" in the name (case-insensitive)
const phoneProducts = await collection
.find({ name: { $regex: /phone/i } })
.toArray()
console.log('\nProducts matching "phone":')
for (const p of phoneProducts) {
console.log(` ${p.name} - $${p.price}`)
}Products matching "phone":
Noise Cancelling Headphones - $249.99
Smartphone Stand Adjustable - $19.99Run the full script:
npx tsx catalog.tsField filters, array matching, aggregation pipeline, regex search: all standard MongoDB operations. The driver has no idea it's talking to FerretDB. Every one of these documents lives in PostgreSQL.
The PostgreSQL Advantage
FerretDB isn't just "open-source MongoDB." Your data lives in PostgreSQL, and that buys you real things:
Default ACID transactions. PostgreSQL wraps every operation in a transaction by default. MongoDB added multi-document ACID in 4.0, but it requires explicit sessions and is opt-in. With FerretDB, transactional behavior is the default.
Mature backup and recovery. pg_dump, pg_basebackup, point-in-time recovery. Decades of production use across millions of deployments.
The PostgreSQL extension ecosystem. PostGIS, pg_cron, pgvector, and hundreds of others. Your document data coexists with all of it.
No SSPL licensing concerns. FerretDB is Apache 2.0. Self-host, embed, modify, redistribute, no restrictions.
Unified infrastructure. If you already run PostgreSQL, FerretDB lets you add document storage without a separate database system. One backup strategy, one set of credentials, one operational playbook.
When to Use FerretDB
FerretDB is the right fit when:
- You want MongoDB's query API but PostgreSQL's reliability. The document model and query operators are genuinely useful. You just don't want a separate database system with its own operational overhead.
- You're migrating off MongoDB's license. Existing code, drivers, and ODMs (like Mongoose) work without changes. Swap the connection string and test.
- Your team knows MongoDB but your infrastructure is PostgreSQL. No retraining, no new ops tooling. Same queries they already know.
- You need flexible schemas for prototyping. Start schemaless, then add validation or move to structured tables later. All within PostgreSQL.
- Licensing matters. Apache 2.0 vs. SSPL is a meaningful difference for companies that self-host or distribute software.
Wrapping Up
Under 80 lines of real code. Standard MongoDB driver, document insertion, filtered queries, array matching, aggregation pipeline, regex search. None of that code knows or cares that FerretDB is translating every operation into PostgreSQL.
The FerretDB documentation covers supported commands, compatibility details, and configuration.
To manage your local FerretDB instance:
lbase stop ferret1 # Stop the server
lbase start ferret1 # Start it again
lbase list # See all your database instancesThe Layerbase CLI supports 20+ engines, so you can run FerretDB alongside CockroachDB, Redis, or DuckDB without juggling separate installs. Prefer a GUI? Layerbase Desktop provides a desktop app for the same operations.
Keep reading
- Run FerretDB on Windows (No Docker, No WSL)FerretDB v2 needs a PostgreSQL extension that is not on Windows yet. We built custom binaries to run FerretDB v1 natively on Windows, no Docker or WSL.
- 7 reasons to choose FerretDB over MongoDBFerretDB gives you the MongoDB API on top of PostgreSQL, under an Apache 2.0 license. Here are seven concrete reasons teams pick it over MongoDB, and an honest list of when you should not.
- PayloadCMS Setup: PostgreSQL or FerretDBSet up PayloadCMS with managed PostgreSQL or MongoDB-compatible FerretDB on Layerbase Cloud, and why FerretDB beats MongoDB for most Payload projects.
- Migrating from MongoDB to FerretDBA practical guide to migrating from MongoDB to FerretDB, covering what works out of the box, what breaks, and how to check your codebase before switching.