mongo_document 1.0.1 copy "mongo_document: ^1.0.1" to clipboard
mongo_document: ^1.0.1 copied to clipboard

The MongoDb Document Code Generator

mongo_document #

pub package build status license

📦 mongo_document #

A simple annotation that lets you perform CRUD on MongoDB using native Dart types.

⚠️ Work in Progress: This package is still under active development. Many features are experimental or not yet implemented. Expect breaking changes and missing functionality.

Motivation #

While mongo_dart provides low-level MongoDB access, it requires you to manually manage collections, types, field names, and query logic. This can easily lead to mismatches, especially when your Dart model fields use different names from the database schema (e.g., postAuthor vs. post_author).

@MongoDocument bridges this gap by generating type-safe CRUD operations and query builders based on your annotated freezed classes. This saves you from writing raw queries or worrying about field mapping errors. Behind the scenes, your Dart native types are translated into formats compatible with mongo_dart.


🚀 Features #

  • Zero‑boilerplate CRUD.save(), .delete(), .insertMany() on your model instance
  • Rich Query API — type‑safe DSL: .findOne(), .findMany(skip:limit:), .deleteOne(), .deleteMany(), .updateOne(), .updateMany(), .count()
  • Nested joins — automatic $lookup + $unwind when querying referenced @MongoDocument fields
  • Array supportQList<T> with .contains(), .inList(), .elemMatch() for List/Set fields
  • Map supportQMap<V> with sub‑key queries e.g p.misc['key'].eq(value)
  • Field renaming — honors @JsonSerializable(fieldRename: …)
  • Timestamps — auto‑manages _id, createdAt, updatedAt

🛠️ Getting Started #

1. Add dependencies #

In your pubspec.yaml:

dependencies:
  json_annotation: ^4.9.0
  mongo_document: ^0.0.1

dev_dependencies:
  build_runner: ^2.4.14
  freezed: ">=2.5.8 <4.0.0"
  json_serializable: ^6.9.3
  mongo_document_generator: ^0.0.1
dart pub get

2. Initialize the MongoDB connection #

Call once before using any generated APIs (e.g. in main()):

import 'package:mongo_document/mongo_connection.dart';

Future<void> main() async {
  await MongoConnection.init('mongodb://localhost:27017/mydb');
  // Now generated .save(), .findOne(), etc. will work
}

3. Define & annotate your models #

Use freezed + @MongoDocument:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:mongo_document/mongo_document.dart';

part 'post.freezed.dart';
part 'post.g.dart';
part 'post.mongo_document.dart';

@freezed
@MongoDocument(collection: 'posts')
abstract class Post with _$Post {
  @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
  const factory Post({
    @ObjectIdConverter() @JsonKey(name: '_id') ObjectId? id,
    User? author,
    String? body,
    @Default(<String>[]) List<String> tags,
    @DateTimeConverter() DateTime? createdAt,
    @DateTimeConverter() DateTime? updatedAt,
  }) = _Post;

  factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
}

4. Generate the code #

dart run build_runner build --delete-conflicting-outputs

This produces post.mongo_document.dart, adding:

  • Post.save(), Post.delete(), Posts.insertMany(), Posts.findOne(), Posts.findMany(), Posts.deleteOne(), Posts.deleteMany(), Posts.updateOne(), Posts.updateMany(), Posts.count()
  • QPost with QueryField, QMap, QList getters
  • Automatic nested‐join logic and skip/limit handling

💡 Usage Examples #

Create / Insert #

// single insert
final newPost = Post(author: user, tags: ['news'], body: 'Hello');
await newPost.save();

// Update a single post
var post = await Posts.findOne((p) => p.body.contains("Hello World"));
post = post?.copyWith(body: 'new post body');
await post?.save();

// bulk insertMany
final p1 = Post(author: user, tags: ['news'], body: 'Hello');
final p2 = Post(author: user, tags: ['tech'], body: 'World');
final inserted = await Posts.insertMany([p1, p2]);
print(inserted.map((p) => p.id));

findOne #

// find a single post whose tags include 'general' and body equals 'hello world'
final post = await Posts.findOne(
  (p) => p.tags.contains('general') & p.body.eq('hello world')
);

findMany #

// load first 10 posts with author populated
final posts = await Posts.findMany(
  (p) => p.author.firstName.startsWith('A'),
  skip: 0,
  limit: 10,
);

Array element match #

// find posts with any tag starting with 'gen'
final genPosts = await Posts.findMany(
  (p) => p.tags.elemMatch((t) => t.startsWith('gen'))
);

// find posts with tags contain 'awesome' in the tag list
final awesomePosts = await Posts.findMany(
(p) => p.tags.contains('awesome')
);

Map key query #

// find posts where analytics['views'] > 100
final hot = await Posts.findMany(
  (p) => p.analytics['views'].gt(100)
);

count #

final countAll = await Posts.count((p) => p.body.ne(null));

⚙️ Troubleshooting #

If you encounter the warning @JsonSerializable can only be used on classes: add the following to your analysis_options.yaml to suppress it

analyzer:
  errors:
    invalid_annotation_target: ignore