{"id":22934623,"url":"https://github.com/anasqiblawi/localgoose","last_synced_at":"2026-04-11T12:41:28.331Z","repository":{"id":264350675,"uuid":"893122479","full_name":"AnasQiblawi/localgoose","owner":"AnasQiblawi","description":"A lightweight, file-based ODM Database for Node.js, inspired by Mongoose","archived":false,"fork":false,"pushed_at":"2024-11-30T20:14:10.000Z","size":118,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-30T21:20:14.213Z","etag":null,"topics":["database","document-database","embedded-database","file-based","file-system","json-server","json-storage","lightweight-database","local-db","local-storage","localstorage","mongodb","mongoose","nodejs","nosql","odm"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AnasQiblawi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-23T15:40:32.000Z","updated_at":"2024-11-30T20:14:13.000Z","dependencies_parsed_at":"2024-11-23T16:32:41.605Z","dependency_job_id":"22ecfcf5-55d3-42c8-b97e-c90538949b0c","html_url":"https://github.com/AnasQiblawi/localgoose","commit_stats":null,"previous_names":["anasqiblawi/localgoose"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AnasQiblawi%2Flocalgoose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AnasQiblawi%2Flocalgoose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AnasQiblawi%2Flocalgoose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AnasQiblawi%2Flocalgoose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AnasQiblawi","download_url":"https://codeload.github.com/AnasQiblawi/localgoose/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229699861,"owners_count":18109852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","document-database","embedded-database","file-based","file-system","json-server","json-storage","lightweight-database","local-db","local-storage","localstorage","mongodb","mongoose","nodejs","nosql","odm"],"created_at":"2024-12-14T11:44:29.309Z","updated_at":"2026-04-11T12:41:23.306Z","avatar_url":"https://github.com/AnasQiblawi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Localgoose\n\nA lightweight, file-based ODM (Object-Document Mapper) for Node.js, inspired by Mongoose but designed for local JSON storage. Perfect for prototypes, small applications, and scenarios where a full MongoDB setup isn't needed.\n\n## Features\n\n- 🚀 Mongoose-like API for familiar development experience\n- 📁 JSON file-based storage\n- 🔄 Schema validation and type casting\n- 🎯 Rich query API with chainable methods\n- 📊 Aggregation pipeline support\n- 🔌 Virtual properties and middleware hooks\n- 🏃‍♂️ Zero external dependencies (except BSON for ObjectIds)\n- 🔗 Support for related models and references\n- 📝 Comprehensive CRUD operations\n- 🔍 Advanced querying and filtering\n- 🔎 Full-text search capabilities\n- 📑 Compound indexing support\n- 🔄 Schema inheritance and discrimination\n- 🎨 Custom type casting and validation\n- 🗄️ Backup and restore functionality\n- 🧩 Custom types and schema inheritance\n- 🛠️ Middleware hooks for documents, queries, and aggregations\n- 🌐 Geospatial queries and indexing\n- 📅 Date operators and bitwise operators\n\n## Installation\n\n```bash\nnpm install localgoose\n```\n\n## Quick Start\n\n```javascript\nconst { localgoose } = require('localgoose');\n\n// Connect to a local directory for storage\nconst db = localgoose.connect('./mydb');\n\n// Define schemas for related models\nconst userSchema = new localgoose.Schema({\n  username: { type: String, required: true },\n  email: { type: String, required: true },\n  age: { type: Number, required: true },\n  tags: { type: Array, default: [] }\n});\n\nconst postSchema = new localgoose.Schema({\n  title: { type: String, required: true },\n  content: { type: String, required: true },\n  author: { type: localgoose.Schema.Types.ObjectId, ref: 'User' },\n  likes: { type: Number, default: 0 }\n});\n\n// Create models\nconst User = db.model('User', userSchema);\nconst Post = db.model('Post', postSchema);\n\n// Create a user\nconst user = await User.create({\n  username: 'john',\n  email: 'john@example.com',\n  age: 25,\n  tags: ['developer']\n});\n\n// Create a post with reference to user\nconst post = await Post.create({\n  title: 'Getting Started',\n  content: 'Hello World!',\n  author: user._id\n});\n\n// Query with population\nconst posts = await Post.find()\n  .populate('author')\n  .sort('-likes')\n  .exec();\n\n// Use aggregation pipeline\nconst stats = await Post.aggregate()\n  .match({ author: user._id })\n  .group({\n    _id: null,\n    totalPosts: { $sum: 1 },\n    avgLikes: { $avg: '$likes' }\n  })\n  .exec();\n```\n\n## API Reference\n\n### Connection\n\n```javascript\n// Connect to database\nconst db = await localgoose.connect('./mydb');\n\n// Create separate connection\nconst connection = await localgoose.createConnection('./mydb');\n```\n\n### Schema Definition\n\n```javascript\nconst schema = new localgoose.Schema({\n  // Basic types\n  string: { type: String, required: true },\n  number: { type: Number, default: 0 },\n  boolean: { type: Boolean },\n  date: { type: Date, default: Date.now },\n  objectId: { type: localgoose.Schema.Types.ObjectId, ref: 'OtherModel' },\n  buffer: localgoose.Schema.Types.Buffer,\n  uuid: localgoose.Schema.Types.UUID,\n  bigInt: localgoose.Schema.Types.BigInt,\n  mixed: localgoose.Schema.Types.Mixed,\n  map: localgoose.Schema.Types.Map,\n  \n  // Arrays and Objects\n  array: { type: Array, default: [] },\n  object: {\n    type: Object,\n    default: {\n      key: 'value'\n    }\n  }\n});\n\n// Virtual properties\nschema.virtual('fullName').get(function() {\n  return `${this.firstName} ${this.lastName}`;\n});\n\n// Instance methods\nschema.method('getInfo', function() {\n  return `${this.username} (${this.age})`;\n});\n\n// Static methods\nschema.static('findByEmail', function(email) {\n  return this.findOne({ email });\n});\n\n// Middleware\nschema.pre('save', function() {\n  this.updatedAt = new Date();\n});\n\nschema.post('save', function() {\n  console.log('Document saved:', this._id);\n});\n\n// Indexes\nschema.index({ email: 1 }, { unique: true });\nschema.index({ title: 'text', content: 'text' });\n```\n\n### Model Operations\n\n#### Create\n```javascript\n// Create a single document\nconst doc = await Model.create({\n  field: 'value'\n});\n\n// Create multiple documents\nconst docs = await Model.create([\n  { field: 'value1' },\n  { field: 'value2' }\n]);\n\n// Insert many documents\nconst docs = await Model.insertMany([\n  { field: 'value1' },\n  { field: 'value2' }\n], {\n  ordered: true, // Optional: documents are inserted in order\n  lean: true     // Optional: returns plain objects instead of documents\n});\n```\n\n#### Read\n```javascript\n// Find all documents\nconst docs = await Model.find();\n\n// Find with specific conditions\nconst docs = await Model.find({\n  field: 'value',\n  number: { $gt: 10 }\n});\n\n// Find one document\nconst doc = await Model.findOne({\n  field: 'value'\n});\n\n// Find by ID\nconst doc = await Model.findById(id);\n\n// Count documents\nconst count = await Model.countDocuments({\n  field: 'value'\n});\n\n// Estimated count (faster but not exact)\nconst count = await Model.estimatedDocumentCount();\n\n// Check if document exists\nconst exists = await Model.exists({ field: 'value' });\n\n// Get distinct values\nconst values = await Model.distinct('field', { type: 'specific' });\n\n// Find with population\nconst doc = await Model.findOne({ field: 'value' })\n  .populate('reference')\n  .exec();\n```\n\n#### Update\n```javascript\n// Update one document\nconst result = await Model.updateOne(\n  { field: 'value' },            // filter\n  { $set: { newField: 'new' }},  // update\n  { upsert: true }               // options\n);\n\n// Update many documents\nconst result = await Model.updateMany(\n  { field: 'value' },\n  { $set: { newField: 'new' }}\n);\n\n// Find one and update\nconst doc = await Model.findOneAndUpdate(\n  { field: 'value' },\n  { $set: { newField: 'new' }},\n  { \n    new: true,      // return updated document\n    upsert: true    // create if not exists\n  }\n);\n\n// Find by ID and update\nconst doc = await Model.findByIdAndUpdate(\n  id,\n  { $set: { field: 'new' }},\n  { new: true }\n);\n\n// Replace one document\nconst result = await Model.replaceOne(\n  { field: 'value' },\n  { newDocument: true }\n);\n\n// Increment a field\nconst result = await Model.increment(\n  { field: 'value' },  // filter\n  'counter',           // field to increment\n  5                    // increment amount (default: 1)\n);\n```\n\n#### Delete\n```javascript\n// Delete one document\nconst result = await Model.deleteOne({\n  field: 'value'\n});\n\n// Delete many documents\nconst result = await Model.deleteMany({\n  field: 'value'\n});\n\n// Find one and delete\nconst doc = await Model.findOneAndDelete({\n  field: 'value'\n});\n\n// Find by ID and delete\nconst doc = await Model.findByIdAndDelete(id);\n\n// Find by ID and remove (alias for findByIdAndDelete)\nconst doc = await Model.findByIdAndRemove(id);\n```\n\n#### Bulk Operations\n```javascript\n// Bulk write operations\nconst result = await Model.bulkWrite([\n  {\n    insertOne: {\n      document: { field: 'value' }\n    }\n  },\n  {\n    updateOne: {\n      filter: { field: 'value' },\n      update: { $set: { field: 'new' }}\n    }\n  },\n  {\n    deleteOne: {\n      filter: { field: 'value' }\n    }\n  }\n], {\n  ordered: true // Optional: operations are executed in order\n});\n\n// Bulk save documents\nconst result = await Model.bulkSave([\n  new Model({ field: 'value1' }),\n  new Model({ field: 'value2' })\n], {\n  ordered: true\n});\n```\n\n### Query API\n\n```javascript\n// Chainable query methods\nconst docs = await Model.find()\n  .where('field').equals('value')\n  .where('number').gt(10).lt(20)\n  .where('tags').in(['tag1', 'tag2'])\n  .select('field1 field2')\n  .sort('-field')\n  .skip(10)\n  .limit(5)\n  .populate('reference')\n  .exec();\n\n// Advanced queries with geospatial support\nconst docs = await Model.find()\n  .where('location')\n  .near({\n    center: [longitude, latitude],\n    maxDistance: 5000\n  })\n  .exec();\n\n// Text search\nconst docs = await Model.find()\n  .where('$text')\n  .equals({ $search: 'keyword' })\n  .exec();\n```\n\n### Aggregation Pipeline\n\n```javascript\nconst results = await Model.aggregate()\n  .match({ field: 'value' })\n  .group({\n    _id: '$groupField',\n    total: { $sum: 1 },\n    avg: { $avg: '$numField' }\n  })\n  .sort({ total: -1 })\n  .limit(5)\n  .exec();\n```\n\n## Backup and Restore\n\n```javascript\n// Create backup\nconst backupPath = await Model.backup();\n\n// Restore from backup\nawait Model.restore(backupPath);\n\n// List backups\nconst backups = await Model.listBackups();\n\n// Clean up old backups\nawait Model.cleanupBackups();\n```\n\n### Supported Update Operators\n\n#### Field Update Operators\n- `$set`: Sets the value of a field\n- `$unset`: Removes the specified field from a document\n- `$rename`: Renames a field\n- `$setOnInsert`: Sets the value of a field if an update results in an insert\n\n#### Increment/Decrement Operators\n- `$inc`: Increments the value of a field by the specified amount\n- `$mul`: Multiplies the value of a field by the specified amount\n- `$min`: Updates the field only if the specified value is less than the existing value\n- `$max`: Updates the field only if the specified value is greater than the existing value\n\n#### Array Update Operators\n- `$push`: Adds an item to an array\n- `$pull`: Removes all array elements that match a specified query\n- `$addToSet`: Adds elements to an array only if they do not already exist\n- `$pop`: Removes the first or last item from an array\n- `$pullAll`: Removes all matching values from an array\n\n#### Bitwise Operators\n- `$bit`: Performs bitwise AND, OR, and XOR updates of integer values\n\n#### Date Operators\n- `$currentDate`: Sets the value of a field to the current date\n\n### Supported Query Operators\n\n- `equals`: Exact match\n- `gt`: Greater than\n- `gte`: Greater than or equal\n- `lt`: Less than\n- `lte`: Less than or equal\n- `in`: Match any value in array\n- `nin`: Not match any value in array\n- `regex`: Regular expression match\n- `exists`: Check for existence of a field\n- `size`: Match the size of an array\n- `mod`: Match documents where the value of a field modulo some divisor is equal to a specified remainder\n- `near`: Find documents near a specified point\n- `maxDistance`: Limit the results to documents within a specified distance from the point\n- `within`: Find documents within a specified shape\n- `box`: Find documents within a rectangular box\n- `center`: Find documents within a specified circle\n- `centerSphere`: Find documents within a specified spherical circle\n- `polygon`: Find documents within a specified polygon\n- `geoIntersects`: Find documents that intersect a specified geometry\n- `nearSphere`: Find documents near a specified point using spherical geometry\n- `text`: Full-text search\n- `or`: Logical OR\n- `nor`: Logical NOR\n- `and`: Logical AND\n- `elemMatch`: Match documents that contain an array field with at least one element that matches all the specified query criteria\n\n### Supported Aggregation Operators\n\n- `$match`: Filter documents\n- `$group`: Group documents by expression\n- `$sort`: Sort documents\n- `$limit`: Limit number of documents\n- `$skip`: Skip number of documents\n- `$unwind`: Deconstruct array field\n- `$lookup`: Perform left outer join\n- `$project`: Reshape documents\n- `$addFields`: Add new fields\n- `$facet`: Process multiple aggregation pipelines\n- `$bucket`: Categorize documents into buckets\n- `$sortByCount`: Group and count documents\n- `$densify`: Fill gaps in time-series data\n- `$graphLookup`: Perform recursive search on a collection\n- `$unionWith`: Combine documents from another collection\n- `$count`: Count the number of documents\n- `$out`: Write the result to a collection\n- `$merge`: Merge the result with a collection\n- `$replaceRoot`: Replace the input document with the specified document\n- `$set`: Add new fields or update existing fields in documents\n- `$unset`: Remove specified fields from documents\n\n### Supported Group Accumulators\n\n- `$sum`: Calculate sum\n- `$avg`: Calculate average\n- `$min`: Get minimum value\n- `$max`: Get maximum value\n- `$push`: Accumulate values into array\n- `$first`: Get first value\n- `$last`: Get last value\n- `$addToSet`: Add unique values to array\n- `$stdDevPop`: Calculate population standard deviation\n- `$stdDevSamp`: Calculate sample standard deviation\n- `$mergeObjects`: Merge objects into a single object\n\n## Advanced Features\n\n### Schema Validation\n\n```javascript\nconst schema = new localgoose.Schema({\n  email: {\n    type: String,\n    required: true,\n    validate: {\n      validator: function(v) {\n        return /\\S+@\\S+\\.\\S+/.test(v);\n      },\n      message: props =\u003e `${props.value} is not a valid email!`\n    }\n  },\n  age: {\n    type: Number,\n    min: [18, 'Must be at least 18'],\n    max: [120, 'Must be no more than 120']\n  },\n  password: {\n    type: String,\n    minlength: 8,\n    match: /^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])/\n  }\n});\n```\n\n### Middleware Hooks\n\n```javascript\n// Document middleware\nschema.pre('save', async function() {\n  if (this.isModified('password')) {\n    this.password = await hash(this.password);\n  }\n});\n\n// Query middleware\nschema.pre('find', function() {\n  this.where({ isActive: true });\n});\n\n// Aggregation middleware\nschema.pre('aggregate', function() {\n  this.pipeline().unshift({ $match: { isDeleted: false } });\n});\n```\n\n### Virtual Population\n\n```javascript\nschema.virtual('posts', {\n  ref: 'Post',\n  localField: '_id',\n  foreignField: 'author',\n  justOne: false,\n  options: { sort: { createdAt: -1 } }\n});\n```\n\n### Schema Inheritance\n\n```javascript\nconst baseSchema = new localgoose.Schema({\n  name: String,\n  createdAt: Date\n});\n\nconst userSchema = new localgoose.Schema({\n  email: String,\n  password: String\n});\n\nuserSchema.add(baseSchema);\n```\n\n### Custom Types\n\n```javascript\nclass Point {\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n  }\n}\n\nconst pointSchema = new localgoose.Schema({\n  location: {\n    type: Point,\n    validate: {\n      validator: v =\u003e v instanceof Point,\n      message: 'Invalid point'\n    }\n  }\n});\n```\n\n## File Structure\n\nEach model's data is stored in a separate JSON file:\n\n```\nmydb/\n  ├── User.json\n  ├── Post.json\n  └── Comment.json\n```\n\n## Error Handling\n\nLocalgoose provides detailed error messages for:\n- Schema validation failures\n- Required field violations\n- Type casting errors\n- Query execution errors\n- Reference population errors\n\n## Best Practices\n\n1. **Schema Design**\n   - Define schemas with proper types and validation\n   - Use references for related data\n   - Implement virtual properties for computed fields\n   - Add middleware for common operations\n\n2. **Querying**\n   - Use proper query operators\n   - Limit result sets for better performance\n   - Use projection to select only needed fields\n   - Populate references only when needed\n\n3. **File Management**\n   - Regularly backup your JSON files\n   - Monitor file sizes\n   - Implement proper error handling\n   - Use atomic operations when possible\n\n4. **Performance Optimization**\n   - Use indexes for frequently queried fields\n   - Implement pagination for large datasets\n   - Cache frequently accessed data\n   - Use lean queries when possible\n\n5. **Data Integrity**\n   - Implement proper validation\n   - Use transactions when needed\n   - Handle errors gracefully\n   - Keep backups up to date\n\n## Limitations\n\n- Not suitable for large datasets (\u003e10MB per collection)\n- No support for transactions\n- Limited query performance compared to real databases\n- Basic relationship support through references\n- No real-time updates or change streams\n- No distributed operations\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n## License\n\nMIT\n\n## Author\n\n[Anas Qiblawi](https://github.com/AnasQiblawi)\n\n## Acknowledgments\n\nInspired by Mongoose, the elegant MongoDB ODM for Node.js.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanasqiblawi%2Flocalgoose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanasqiblawi%2Flocalgoose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanasqiblawi%2Flocalgoose/lists"}