{"id":21818599,"url":"https://github.com/hash-bang/monoxide","last_synced_at":"2025-04-14T02:28:10.508Z","repository":{"id":27045868,"uuid":"30511052","full_name":"hash-bang/Monoxide","owner":"hash-bang","description":"A less poisonous way to work with Mongo","archived":false,"fork":false,"pushed_at":"2025-02-17T23:29:40.000Z","size":566,"stargazers_count":6,"open_issues_count":7,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-14T02:28:06.697Z","etag":null,"topics":[],"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/hash-bang.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}},"created_at":"2015-02-09T00:11:46.000Z","updated_at":"2025-02-17T23:29:43.000Z","dependencies_parsed_at":"2022-09-11T08:03:47.302Z","dependency_job_id":null,"html_url":"https://github.com/hash-bang/Monoxide","commit_stats":null,"previous_names":["hash-bang/mongol"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2FMonoxide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2FMonoxide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2FMonoxide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2FMonoxide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hash-bang","download_url":"https://codeload.github.com/hash-bang/Monoxide/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248810756,"owners_count":21165177,"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":[],"created_at":"2024-11-27T16:13:43.000Z","updated_at":"2025-04-14T02:28:10.485Z","avatar_url":"https://github.com/hash-bang.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Monoxide\n=========\nA less poisonous to work with Mongo.\n\nMonoxide attempts to provide nicer, chainable functionality on top of base Mongo while making some of the more C centric designs of Mongo more Node like.\n\n**NOTE: While Monoxide is now mostly stable there are still likely to be bugs. Please report these to the author if found**\n\n\n1. [API documentation](API.md)\n2. [ReST server](#rest-server)\n3. [Example setups](examples/)\n4. [Common recipes and design patterns](RECIPES.md)\n4. [TODO list](TODO.md)\n5. [Future ideas](IDEAS.md)\n6. [Common issues](COMMON-ISSUES.md)\n\n\nKey differences from Mongoose / MongoDB-Core:\n\n* **ReST server** - Provides an out-of-the-box [Express middleware](#rest-server)\n* **Syntax** - Returns a nicer syntax for [specifying schemas](#schema-setup)\n* **Query parameters as arrays** - Sort + Populate functionality can now be specified as an array rather than only as space delimited strings\n* **Plain objects** - All documents are accessible as plain JavaScript objects\n* **Virtuals** - Virtuals are handled locally (not on the database)\n* **Methods** - Methods are handled locally (again not on the database)\n* **Hooks** - Hooks (i.e. Mongoose `pre`, `post` calls) **actually work as they should**. Hooks like all the above are local and not fired at the database level\n* **OIDs / ObjectIDs** - All pointers (or `mongoose.Types.ObjectId` as Mongoose refers to them) are **strings**. Comparison is simple string comparison, there is no need to call `.toString()` on each object. The function still exists if you want an entirely plain object sans all the *glued* functions like `save()`\n* Schemas get applied on each document retrieval. Changing the schema of your project no longer leads to documents having 'the old version'. New fields added to the schema *after* document creation will be applied to older documents.\n* **ReST field surpression** - By default all fields prefixed with `_` (excepting `_id` and `__v`) are removed from ReST server output. This can be changed by adjusting the `omitFields` setting for `monoxide.express.(middleware|query|get`.\n* **Document mapping** - Each output document can be run though the `map` function to decorate it before it leaves the server - this is useful to omit complex things the client doesn't need or otherwise glue information to the document.\n* **Callback error if no matching records** - If no matching records are found in a `get()` operation and `$errNotFound` is true (the default) Monoxide will populate the error property of the callback. This is useful to automatically abandon Async chains when an expected record is not found rather than having to do manual check for record existence later.\n* **Pass private data using `$data`** - The `$data` object can be specified in any operation (query, save, delete etc.) and is ignored by Monoxide but still passed into hooks. This can be used as a method to pass data to lower-level functions such as logging operations (e.g. pass the currently logged in user to the lower level hooks)\n* **Meta polling** - Access database meta information using either the `meta()` method or the ReST adapter\n* **Version management** - Created documents correctly set the `__v` property and increment it on save\n* **DEBUG compatibility** - Setting the environment variable `DEBUG=monoxide:*` will output internal processes of Monoxide to the console\n\n\nSee the [ideas list](ideas.md) for future ideas.\n\n\nSchema Setup\n============\nMonoxide supports setting the `type` property via a string instead of using pointers to types within the `mongoose.Types` structure. Its also really easy to add methods, statics and virtuals using chainable syntax.\n\n```javascript\nvar Users = monoxide\n\t.schema('users', {\n\t\tname: String,\n\t\trole: {type: String, enum: ['user', 'admin'], default: 'user'},\n\t\tfavourite: {type: 'pointer', ref: 'widgets'},\n\t\titems: [{type: 'pointer', ref: 'widgets'}],\n\t\tmostPurchased: [{\n\t\t\tnumber: {type: 'number', default: 0},\n\t\t\titem: {type: 'pointer', ref: 'widgets'},\n\t\t}],\n\t})\n\t.static('countByType', function(type, next) { // Adds User.countByType(TYPE, callback) as a model method\n\t\tUsers.count({\n\t\t\t$collection: 'users',\n\t\t\trole: type,\n\t\t}, next);\n\t})\n\t.method('splitNames', function() { // Adds UserDocument.splitNames() as a method\n\t\treturn this.name.split(/\\s+/);\n\t})\n\t.virtual('password', function() { return 'RESTRICTED' }, function(pass) { // Adds a password handling virtual\n\t\t// Replace this with your own impressive password hashing kung-fu\n\t\tthis._password = pass;\n\t})\n\t.hook('save', function(next, doc) { // Adds a hook for when a document is saved (must fire callback to accept changes)\n\t\tconsole.log('User', doc._id, 'has been modified');\n\t\tnext();\n\t})\n```\n\nNote that the awkward `mongoose.Schema.ObjectId` type is replaced with the nicer `'pointer'` type specified as a string. All other types can be similarly specified (e.g. `\"number\"`, `\"string\"` etc.).\n\nSchemas are also automatically compiled and returned as an object from `monoxide.schema` without any need to perform additional actions on the schema before its usable. Functions that declare additional operations such as virtuals, statics, methods, hooks etc can be added and removed at any time without recompiling the object.\n\n\n\nReST Server\n===========\nThe primary interface to Monoxide is the ReST server interface for Express:\n\n```javascript\nvar express = require('express');\nvar monoxide = require('monoxide');\n\nvar app = express();\n\napp.use('/api/users/:id?', monoxide.express.middleware({\n\tcollection: 'users',\n\n\tget: true, // Allow retrieval by ID\n\tquery: true, // Allow retrieval of multiple records as a query\n\tcount: true, // Allow record counting via query\n\tcreate: false, // Alow record creation via POST / PUT\n\tsave: false, // Allow saving via POST / PATCH\n\tdelete: false, // Allow record deletion via DELETE\n\tmeta: false, // Allow retrieval of collection meta information\n\n\t// ... other options here ... //\n}));\n```\n\n\nOR you can also bring in only the specific Express middleware thats required:\n\n```javascript\nvar express = require('express');\nvar monoxide = require('monoxide');\n\nvar app = express();\n\napp.use('/api/doodads/:id?', monoxide.express.middleware('doodads'));\napp.use('/api/widgets/:id?', monoxide.express.middleware('widgets'));\n```\n\n\nYou can also secure the various methods by passing in middleware:\n\n```javascript\napp.use('/api/users/:id?', monoxide.express.middleware('users', {\n\tget: true, // Allow retrieval by ID\n\tcreate: false, // Dont allow record creation\n\tquery: false, // Dont allow querying of users (direct ID addressing is ok though)\n\tcount: false, // Disable counting\n\n\tquery: true, // Allow retrieval of multiple records as a query\n\n\tsave: function(req, res, next) {\n\t\t// User must be logged in AND be either the right user OR an admin to save user info\n\t\tif (\n\t\t\t(req.user \u0026\u0026 req.user._id) \u0026\u0026 // Logged in AND\n\t\t\t(\n\t\t\t\treq.user._id == req.user._id || // Is the same user thats being saved (saving own profile) OR\n\t\t\t\treq.user.role == 'admin' // User is an admin\n\t\t\t)\n\t\t) return next();\n\t\treturn res.status(403).send('Not logged in').end();\n\t},\n\n\tdelete: function(req, res, next) {\n\t\t// Only allow delete if the query contains 'force' as a string\n\t\tif (req.query.force \u0026\u0026 req.query.force === 'confirm') return next();\n\t\treturn res.status(403).send('Nope!').end();\n\t},\n}));\n```\n\n\n**NOTES:**\n\n* Sort keys prefixed with `-` are inverted. For example `sort=-name` means 'sort by names in reverse order'\n* Multiple query keys are automatically converted into arrays as a '$in' query type. For example `key=val1\u0026key=val2` becomes `key={$in:[val1,val2]}`. If you do not want this behaviour disable `shorthandArrays` in the ReST options.\n\n\nCherry-picking middleware\n-------------------------\nYou can also pick-and-choose the handlers to be used:\n\n```javascript\nvar express = require('express');\nvar monoxide = require('monoxide');\n\nvar app = express();\n\napp.get('/api/users', monoxide.express.query('users'));\napp.get('/api/users/count', monoxide.express.count('users'));\napp.meta('/api/users/meta', monoxide.express.meta('users'));\napp.get('/api/users/:id', monoxide.express.get('users'));\napp.post('/api/users', monoxide.express.create('users'));\napp.post('/api/users/:id', monoxide.express.save('users'));\napp.delete('/api/users/:id', monoxide.express.delete('users'));\n```\n\nIn the above the specified models are bound to their respective ReST end points (`GET /api/users` will return all users for example).\n\n\nAPI\n===\nBelow is the quick-reference API. For more detailed docs see the [API documentation](API.md) for the generated JSDoc output.\n\n\nDocument creation\n-----------------\nCreate a new document.\n\n```javascript\n\tmonoxide.create([data], [callback])\n\tmonoxide.models.MODEL.create([data], [callback])\n```\n\n```javascript\n\tmonoxide.create({\n\t\t$collection: 'widgets',\n\t\tname: 'My new widget',\n\t}, function(err, widget) { // ... // });\n\n\tmonoxide.models.widgets.create({\n\t\tname: 'My new widget',\n\t}, function(err, widget) {\n\t\t// widget = newly created document\n\t});\n```\n\nIn addition to regular document key/values the `data` object can also contain the following meta keys:\n\n| Key           | Type    | Default | Description                                                            |\n|---------------|---------|---------|------------------------------------------------------------------------|\n| `$collection` | String  | `null`  | The collection to create the document in                               |\n| `$refetch`    | Boolean | `true`  | Whether to refetch the document from the database again after creation |\n\n\nSee the [create test scripts](test/create.js) for more complex examples.\n\n\nDocument finding (single)\n-------------------------\nFind one document.\n\n```javascript\nmonoxide.get([query], [callback])\nmonoxide.models.MODEL.findOne([data], [callback])\n```\n\n\nDocument finding (multiple)\n---------------------------\nFind multiple documents.\n\n```javascript\nmonoxide.query([query], [callback])\nmonoxide.models.MODEL.find([query], [callback])\n```\n\n\nDocument counting\n-----------------\nCount documents.\n\n```javascript\nmonoxide.count([query], [callback])\nmonoxide.models.MODEL.count([query], [callback])\n```\n\n\nDocument saving (one)\n---------------------\nSave data to an existing document.\n\n```javascript\nmonoxide.save([data], [callback])\nmonoxide.models.MODEL.save([data], [callback])\n```\n\n\nDocument saving (multiple)\n--------------------------\nSave data to multiple existing documents.\n\n```javascript\nmonoxide.update([data], [callback])\nmonoxide.models.MODEL.update([data], [callback])\n```\n\n\nDocument deletion\n-----------------\nDelete documents in a collection by an optional query.\n\n\n```javascript\nmonoxide.delete([query], [callback])\nmonoxide.models.MODEL.delete([query], [callback])\ndocument.delete([callback])\n\nmonoxide.remove([query], [callback])\nmonoxide.models.MODEL.remove([query], [callback])\ndocument.remove([callback])\n```\n\n```javascript\n// Delete a specific document by its ID\nmonoxide.delete({\n\t$collection: 'widgets',\n\t$id: someID,\n});\n\n// Delete a specific document by its ID\nmonoxide.models.widgets.remove({\n\t$id: someID,\n});\n\n\n// Delete all documents in the collection where color='blue'\nmonoxide.delete({\n\t$collection: 'widgets',\n\t$multiple: true,\n\tcolor: 'blue',\n});\n\n// Delete all documents in the collection\nmonoxide.models.widgets.delete();\n```\n\n**Notes:**\n\n* Deleting by an empty query (or not specifying the query at all, e.g. `monoxide.model.delete()`) will throw an error if the `monoxide.settings.removeAll` flag is true to allow nuking an entire collection.\n* `delete` and `remove` functions are interchangable, the other is just provided for convenience\n\n\nHooks\n-----\nHooks allow watching of models. Each Hook within monoxide accepts a callback that must be triggered for execution to continue.\n\n```javascript\nmonoxide.models.MODEL.hook('create', function(next, query) { // ... // });\nmonoxide.models.MODEL.hook('postCreate', function(next, query, newDoc) { // ... // });\n\nmonoxide.models.MODEL.hook('query', function(next, query) { // ... // });\n\nmonoxide.models.MODEL.hook('save', function(next, query) { // ... // });\nmonoxide.models.MODEL.hook('postSave', function(next, query, newDoc) { // ... // });\n\nmonoxide.models.MODEL.hook('update', function(next, query) { // ... // });\n\nmonoxide.models.MODEL.hook('delete', function(next, query) { // ... // });\nmonoxide.models.MODEL.hook('postDelete', function(next, query) { // ... // });\n```\n\n\n**Notes:**\n\n* For post* hooks the `newDoc` parameter will only return the newly created document if `$refetch=true` within the create or save query. Query will always be present.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhash-bang%2Fmonoxide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhash-bang%2Fmonoxide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhash-bang%2Fmonoxide/lists"}