mongo_document 1.4.5
mongo_document: ^1.4.5 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.4.5
dev_dependencies:
build_runner: ^2.4.14
freezed: ">=2.5.8 <4.0.0"
json_serializable: ^6.9.3
mongo_document: ^1.4.5
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])
]
);
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.