Introducing Dengo: MongoDB API for Deno KV

The Best of Both Worlds: MongoDB's Familiar API with Deno's Native KV Store Today, I'm excited to introduce Dengo, a MongoDB-compatible database layer for Deno KV. Dengo brings the familiar MongoDB API to Deno's built-in key-value store, allowing developers to leverage MongoDB's powerful query capabilities while enjoying the simplicity and performance of Deno KV. Why Dengo? When building applications with Deno, especially for edge deployments, you often need a database solution that is: Lightweight - No heavy dependencies or external services Familiar - Minimal learning curve for your team Type-safe - First-class TypeScript support Serverless-ready - Works seamlessly in edge functions Deno's built-in KV store is perfect for edge deployments, but its API is quite different from traditional document databases like MongoDB. This is where Dengo comes in - it bridges this gap by providing a MongoDB-compatible API on top of Deno KV. Architecture Overview Dengo is designed with simplicity and compatibility in mind. Here's how it works: Core Components Database Class: The entry point that manages collections Collection Class: Implements MongoDB-compatible operations Query Engine: Translates MongoDB queries to KV operations Index Management: Provides efficient querying capabilities Data Storage Model Dengo stores documents in Deno KV using a simple but effective approach: // Documents are stored with collection name and document ID as the key [collectionName, documentId] -> documentData For indexes, we use a similar pattern: // Indexes use a prefix to distinguish them from documents [collectionName, "index", indexName, indexedValue] -> documentId This storage model allows for efficient lookups by ID and indexed fields while maintaining compatibility with MongoDB's document model. MongoDB Compatibility Dengo implements the core MongoDB API that most developers use daily: Supported Query Operations find() and findOne() with filtering sort(), limit(), and skip() for result manipulation projection for selecting specific fields Supported Update Operations updateOne() and updateMany() $set, $unset, $inc, $push, and other update operators upsert capability for insert-or-update semantics Supported Query Operators Comparison: $eq, $gt, $gte, $lt, $lte, $ne, $in, $nin Logical: $and, $or, $nor, $not Array: $all, $elemMatch, $size Element: $exists, $type Here's a quick example of how Dengo's API mirrors MongoDB: // MongoDB const result = await db.collection("users") .find({ age: { $gte: 21 } }) .sort({ lastName: 1 }) .limit(10); // Dengo const result = await db.collection("users") .find({ age: { $gte: 21 } }, { sort: { lastName: 1 }, limit: 10, }); Implementation Details Let's dive deeper into how Dengo implements some key MongoDB features: Query Processing When you execute a query like find({ name: "John", age: { $gt: 30 } }), Dengo: Checks if there's an index that can be used for this query If an index exists, performs an efficient range scan If no index exists, falls back to a collection scan Filters documents based on the query conditions Applies sort, limit, and skip operations Index Management Indexes are crucial for performance. Dengo supports: Single-field indexes: Optimize queries on a specific field Compound indexes: Optimize queries on multiple fields Unique indexes: Enforce uniqueness constraints Creating an index is as simple as: await collection.createIndex({ key: { email: 1 }, options: { unique: true } }); Update Operations Updates in Dengo follow MongoDB's semantics: Find documents matching the filter Apply update operators to modify the documents Write the updated documents back to the store Return metadata about the operation (matchedCount, modifiedCount, etc.) Performance Considerations While Dengo provides MongoDB compatibility, it's important to understand the performance implications: Indexed Queries: Extremely fast, similar to native KV lookups Non-Indexed Queries: Require full collection scans, which can be slow for large collections Complex Queries: May not perform as well as in MongoDB, especially for queries that would use specialized indexes in MongoDB For best performance: Create indexes for frequently queried fields Limit the size of your collections Use more specific queries to leverage indexes effectively Type Safety One of Dengo's key advantages is its first-class TypeScript support: interface User { _id: ObjectId; name: string; email: string; age: number; tags: string[]; } const users = db.collection("users"); // TypeScript will ensure you're using the correct fields and types const result = await users.findOne({ email: "john@example.com", age: { $gte: 21 }, }); // result will be typed as User | null

Mar 12, 2025 - 16:14
 0
Introducing Dengo: MongoDB API for Deno KV

The Best of Both Worlds: MongoDB's Familiar API with Deno's Native KV Store

Dengo - MongoDB API for Deno KV

Today, I'm excited to introduce Dengo, a MongoDB-compatible database layer
for Deno KV. Dengo brings the familiar MongoDB API to Deno's built-in key-value
store, allowing developers to leverage MongoDB's powerful query capabilities
while enjoying the simplicity and performance of Deno KV.

Why Dengo?

When building applications with Deno, especially for edge deployments, you often
need a database solution that is:

  1. Lightweight - No heavy dependencies or external services
  2. Familiar - Minimal learning curve for your team
  3. Type-safe - First-class TypeScript support
  4. Serverless-ready - Works seamlessly in edge functions

Deno's built-in KV store is perfect for edge deployments, but its API is quite
different from traditional document databases like MongoDB. This is where Dengo
comes in - it bridges this gap by providing a MongoDB-compatible API on top of
Deno KV.

Architecture Overview

Dengo is designed with simplicity and compatibility in mind. Here's how it
works:

Core Components

  1. Database Class: The entry point that manages collections
  2. Collection Class: Implements MongoDB-compatible operations
  3. Query Engine: Translates MongoDB queries to KV operations
  4. Index Management: Provides efficient querying capabilities

Data Storage Model

Dengo stores documents in Deno KV using a simple but effective approach:

// Documents are stored with collection name and document ID as the key
[collectionName, documentId] -> documentData

For indexes, we use a similar pattern:

// Indexes use a prefix to distinguish them from documents
[collectionName, "index", indexName, indexedValue] -> documentId

This storage model allows for efficient lookups by ID and indexed fields while
maintaining compatibility with MongoDB's document model.

MongoDB Compatibility

Dengo implements the core MongoDB API that most developers use daily:

Supported Query Operations

  • find() and findOne() with filtering
  • sort(), limit(), and skip() for result manipulation
  • projection for selecting specific fields

Supported Update Operations

  • updateOne() and updateMany()
  • $set, $unset, $inc, $push, and other update operators
  • upsert capability for insert-or-update semantics

Supported Query Operators

  • Comparison: $eq, $gt, $gte, $lt, $lte, $ne, $in, $nin
  • Logical: $and, $or, $nor, $not
  • Array: $all, $elemMatch, $size
  • Element: $exists, $type

Here's a quick example of how Dengo's API mirrors MongoDB:

// MongoDB
const result = await db.collection("users")
  .find({ age: { $gte: 21 } })
  .sort({ lastName: 1 })
  .limit(10);

// Dengo
const result = await db.collection("users")
  .find({ age: { $gte: 21 } }, {
    sort: { lastName: 1 },
    limit: 10,
  });

Implementation Details

Let's dive deeper into how Dengo implements some key MongoDB features:

Query Processing

When you execute a query like find({ name: "John", age: { $gt: 30 } }), Dengo:

  1. Checks if there's an index that can be used for this query
  2. If an index exists, performs an efficient range scan
  3. If no index exists, falls back to a collection scan
  4. Filters documents based on the query conditions
  5. Applies sort, limit, and skip operations

Index Management

Indexes are crucial for performance. Dengo supports:

  • Single-field indexes: Optimize queries on a specific field
  • Compound indexes: Optimize queries on multiple fields
  • Unique indexes: Enforce uniqueness constraints

Creating an index is as simple as:

await collection.createIndex({ key: { email: 1 }, options: { unique: true } });

Update Operations

Updates in Dengo follow MongoDB's semantics:

  1. Find documents matching the filter
  2. Apply update operators to modify the documents
  3. Write the updated documents back to the store
  4. Return metadata about the operation (matchedCount, modifiedCount, etc.)

Performance Considerations

While Dengo provides MongoDB compatibility, it's important to understand the
performance implications:

  1. Indexed Queries: Extremely fast, similar to native KV lookups
  2. Non-Indexed Queries: Require full collection scans, which can be slow for large collections
  3. Complex Queries: May not perform as well as in MongoDB, especially for queries that would use specialized indexes in MongoDB

For best performance:

  • Create indexes for frequently queried fields
  • Limit the size of your collections
  • Use more specific queries to leverage indexes effectively

Type Safety

One of Dengo's key advantages is its first-class TypeScript support:

interface User {
  _id: ObjectId;
  name: string;
  email: string;
  age: number;
  tags: string[];
}

const users = db.collection<User>("users");

// TypeScript will ensure you're using the correct fields and types
const result = await users.findOne({
  email: "john@example.com",
  age: { $gte: 21 },
});

// result will be typed as User | null

Limitations and Future Work

While Dengo aims to provide a MongoDB-compatible experience, there are some
limitations:

  1. No Aggregation Framework: Complex data transformations need to be done in application code
  2. Limited Transaction Support: Only atomic operations on single documents are fully supported
  3. No Change Streams: Real-time updates are not currently supported

Future versions of Dengo will address these limitations as Deno KV evolves.

Getting Started

Using Dengo is straightforward:

import { Database } from "dengo";

// Open a Deno KV database
const kv = await Deno.openKv();

// Create a Dengo database instance
const db = new Database(kv);

// Get a collection
const todos = db.collection("todos");

// Insert a document
await todos.insertOne({
  title: "Learn Dengo",
  completed: false,
  createdAt: new Date(),
});

// Query documents
const incompleteTodos = await todos.find({ completed: false });

Conclusion

Dengo brings the best of both worlds to Deno developers - MongoDB's familiar and
powerful API with Deno's native KV store. It's perfect for:

  • Edge functions and serverless applications
  • Projects migrating from MongoDB to Deno
  • Developers who want a document database without external dependencies

We're excited to see what you build with Dengo! Check out our
GitHub repository for more examples and
documentation.

This post is part of the Dengo launch series. Stay tuned for more posts about
performance optimization, migration strategies, and real-world use cases.