mongo_document 1.3.2
mongo_document: ^1.3.2 copied to clipboard
A code generator for MongoDB-backed Dart models using @MongoDocument annotations - build type-safe, maintainable backend layers.
Table of Contents #
Overview #
mongo_document bridges Dart freezed
models and MongoDB via mongo_dart
, generating zero‑boilerplate, type‑safe CRUD and query builders that respect your Dart-native naming conventions (e.g. camelCase) while serializing to and from your DB schema (e.g. snake_case).
⚠️ Work in Progress: Experimental features may change. Your feedback and contributions are welcome.
Motivation #
When your Dart models use camelCase, but your database schema uses a different naming style (e.g. snake_case or any other convention), manual mapping between the two becomes tedious and error-prone. mongo_document removes that friction—letting you CRUD directly from your Dart model definitions, regardless of how you choose to name fields in MongoDB.
Features #
- Zero‑Boilerplate CRUD & Queries:
.save()
,.delete()
,.findOne()
,.findMany()
,.findOneByNamed()
,.findManyByNamed()
,.findById()
- Batch Operations:
.saveMany(List<T> documents)
for bulk inserts;.updateOne(predicate, namedArgumentsOfUpdates)
for targeted updates - Type-Safe DSL & Named Filters: Lambda-based predicates (
p => p.field.eq(...)
) or named-argument filters matching your model - Automatic Field Mapping: Honors
@JsonSerializable(fieldRename)
—camelCase in Dart, snake_case in MongoDB—and respects explicit@JsonKey(name)
overrides - Nested References & Projections: Generates
*Projections
helper classes for each nested@MongoDocument
type - Joins, Arrays & Maps: Built-in
$lookup
for references;QList
andQMap
support array/map operations - Timestamps & IDs: Auto-manage
_id
,created_at
, andupdated_at
Getting Started #
Prerequisites #
- Dart SDK ≥ 3.0
- A running MongoDB instance (local or remote)
- MongoDB server version ≥ 3.6
Installation #
Add to pubspec.yaml
:
dependencies:
freezed_annotation: ">=2.4.4 <4.0.0"
json_annotation: ^4.9.0
mongo_document_annotation: ^1.3.2
dev_dependencies:
build_runner: ^2.4.14
freezed: ">=2.5.8 <4.0.0"
json_serializable: ^6.9.3
mongo_document: ^1.3.2
Then:
dart pub get
Initialization #
In your application entrypoint (e.g. main()
), configure the MongoDB connection once:
import 'package:mongo_document_annotation/mongo_document_annotation.dart';
Future<void> main() async {
await MongoConnection.initialize('mongodb://localhost:27017/mydb');
// Now you can use generated .save(), .findOne(), etc.
// Handle graceful shutdown
ProcessSignal.sigint.watch().listen((_) async {
print('SIGINT received. Shutting down gracefully...');
await MongoDbConnection.shutdown();
exit(0);
});
ProcessSignal.sigterm.watch().listen((_) async {
print('SIGTERM received. Shutting down gracefully...');
await MongoDbConnection.shutdown();
exit(0);
});
}
Usage #
Defining Models #
⚠️ Requirement: Every @MongoDocument
class must include an ObjectId
field in its primary constructor annotated with @ObjectIdConverter()
and @JsonKey(name: '_id')
. This ensures a valid MongoDB _id
is always present.
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:mongo_document_annotation/mongo_document_annotation.dart';
part 'post.freezed.dart';
part 'post.g.dart';
part 'post.mongo_document.dart';
@MongoDocument(collection: 'posts')
@freezed
abstract class Post with _$Post {
@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
const factory Post({
@ObjectIdConverter() @JsonKey(name: '_id') ObjectId? id,
User? author,
String? body,
@JsonKey(name:'post_note') String? postNote,
@Default(<String>[]) List<String> tags,
@DateTimeConverter() DateTime? createdAt,
@DateTimeConverter() DateTime? updatedAt,
}) = _Post;
factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
}
Generating Code #
Run build_runner:
dart run build_runner build --delete-conflicting-outputs
This generates:
- Instance methods:
.save()
,.delete()
, - Static APIs:
Posts.saveMany()
,Posts.findOne()
,Posts.findMany()
,Posts.findById()
,Posts.findOneByNamed()
,Posts.findManyByNamed()
,Posts.updateOne(...)
- Query builder
QPost
with typed fields
CRUD Examples #
// Create & Save
final post = await Post(body: 'Hello world', tags: ['intro']).save();
// Batch Save
await Posts.saveMany([
Post(body: 'Batch A'),
Post(body: 'Batch B')
]);
// Update via copyWith and finally save()
await post?.copyWith(body: 'Updated').save();
// Targeted updateOne
await Posts.updateOne(
(p) => p.body.eq('Hello world'),
body: 'Updated via updateOne'
);
Advanced Queries & Projections #
All query methods—including .findOne()
, .findMany()
, .findOneByNamed()
and .findManyByNamed()
—support an optional projections
parameter. Projection helper classes are generated for each nested @MongoDocument
type in your model (e.g. for a User? author
field you get PostAuthorProjections
). Use these with the corresponding *Fields
enums to include or exclude fields.
// Named-argument single query with exclusions
final result = await Posts.findOneByNamed(
body: 'Secret Post',
projections: [
PostAuthorProjections(exclusions: [PostAuthorFields.password])
]
);
// DSL query with inclusions
Post? postWithAuthorNames = await Posts.findOne(
(p) => p.body.eq('Hello'),
projections: [
PostAuthorProjections(inclusions: [PostAuthorFields.firstName, PostAuthorFields.lastName])
]
);
Named-Argument Queries & Projections #
// Named-argument many query
List<Post> intros = await Posts.findManyByNamed(body: 'Welcome', tags: ['intro']);
// Targeted deleteManyByNamed
await Posts.deleteManyByNamed(body:'Hello World');
Nested-Document Queries & Projections #
When querying via a nested @MongoDocument
, you must include projection helpers for both the root and the nested documents to ensure fields are returned. Otherwise, queries against nested fields will not return any data even if matching documents exist.
// Querying only nested field without projection - returns null
final postNull = await Posts.findOne((p) => p.author.id(authorId));
// Project all root Post fields
final postAllFields = await Posts.findOne(
(p) => p.author.id(authorId),
projections: [
PostProjections()
]
);
// Project both Post and nested author fields
final postWithAuthor = await Posts.findOne(
(p) => p.author.id(authorId),
projections: [
PostProjections(),
PostAuthorProjections()
]
);
Both PostProjections
and PostAuthorProjections
also support inclusions
and exclusions
parameters to customize which fields to include or omit in the result.
Configuration & Conventions #
- Converters:
@ObjectIdConverter()
,@DateTimeConverter()
- Collection name from
@MongoDocument(collection: ...)
Troubleshooting #
Add to analysis_options.yaml
:
analyzer:
errors:
invalid_annotation_target: ignore
Contributing #
See CONTRIBUTING.md for guidelines.
License #
This project is licensed under the MIT License. See LICENSE for details.