This documentation shows a minimal project setup to understand how to utilize mongoose-bread
Consider the following Project structure
📂 root
┣ 📂 Controller
┃ ┗ ProductController.js
┣ 📂 Models
┃ ┗ Product.js
┣ 📂 Routes
┃ ┗ ProductRoutes.js
┗ server.js
For all options see: Options Documentation
Setup Global Plugin Options server.js
const { mongooseBread } = require("mongoose-bread")
mongooseBread.options = {
/* your global mongooseBread options */
defaultPageSize: 20, // default: 10
maxPageSize: 50, // default: 100
paramsIdKey: "productId", // default: id
bulkIdsKey: "_changeManyIds", // default: '_ids'
bulkDocsKey: "_changeManyDocs", // default: _docs
allowDiskUse: true, // default: false
customLabels: { // optional - set custom keys for results
docs: "documents", // default: 'docs'
meta: "myPaginationData", // default: 'pagination'
limit: "maxResultSize", // default: 'limit'
page: "page", // default: 'page'
pagingCounter: "currentPageFirstDocumentStartIndex", // default: 'pagingCounter'
hasNextPage: "nextPageAvailable", // default: 'hasNextPage'
hasPrevPage: "previousPageAvailable", // default: 'hasPrevPage'
nextPage: "nextPageNumber", // default: 'nextPage'
prevPage: "previousPageNumber", // default: 'prevPage'
totalDocs: "documentsTotal", // default: 'totalDocs'
totalPages: "pagesTotal", // default: 'totalPages'
acknowledged: "success", // default: 'acknowledged'
modifiedCount: "modifiedDocumentsAmount", // default: 'modifiedCount'
deletedCount: "deletedDocumentsAmount", // default: 'deletedCount'
createdCount: "createdDocumentsAmount", // default: 'createdCount'
readCount: "readDocumentsAmount", // default: 'readCount'
},
}
The customLabels properties set the keys of returned values accordingly
For all options see: Options Documentation
Setup Options when adding mongoose-bread plugin to a schema Models/Product.js
This overrides the Global Options for productSchema
const mongoose = require("mongoose")
const mongooseBread = require("mongoose-bread")
const productSchema = new mongoose.Schema(
{
name: String,
price: Number,
currency: String,
},
{ timestamps: true }
)
productSchema.plugin(mongooseBread, {
/* your mongooseBread options */
defaultPageSize: 10,
maxPageSize: 100,
searchableFields: ["name", "currency"],
blacklistedFields: ['__v', 'deleted', 'createdAt', 'updatedAt'],
softDelete: true,
})
module.exports = mongoose.model("Product", productSchema)
Use plugin methods in Controller Controller/ProductController.js
const Product = require("../Models/Product")
const breadHelper = Product.breadHelper()
const { catchAsync } = require("../my/utils/library")
const browseProducts = catchAsync(async (req, res, next) => {
const options = breadHelper.createBrowseOptions(req)
const result = await Product.browse(options)
res.status(200).json({
message: req.query.search ? "search" : "browse",
result,
})
})
module.exports = { browseProducts }
For examples of all available methods and their respective output see: Return Values Documentation
This is a simple function wrapper to pass all Errors to the global Error Handler in server.js
.
This avoids to use try catch blocks in all Controller methods.
function catchAsync(fn) {
return (req, res, next) => { fn(req, res, next).catch(next) }
}
The following example applies for Schemas with softDelete enabled
Setup Routes Routes/ProductRoutes.js
// imports
router.use( /* your auth middleware */ )
router.use( /* your permission middleware */ )
router.get( '/bin/:id', /* middleware */, ProductController.readDeleted)
router.patch( '/bin/:id', /* middleware */, ProductController.rehabilitate)
router.delete( '/bin/:id', /* middleware */, ProductController.destroy)
router.get( '/bin', /* middleware */, ProductController.browseDeleted)
router.patch( '/bin', /* middleware */, ProductController.rehabilitate)
router.delete( '/bin', /* middleware */, ProductController.destroy)
router.get( '/:id', /* middleware */, ProductController.read)
router.patch( '/:id', /* middleware */, ProductController.edit)
router.delete( '/:id', /* middleware */, ProductController.softDelete)
router.get( '/', /* middleware */, ProductController.browseProducts)
router.patch( '/', /* middleware */, ProductController.edit)
router.delete( '/', /* middleware */, ProductController.softDelete)
router.post( '/', /* middleware */, ProductController.add)
module.exports = router
Register Routes server.js
// imports
const app = express()
app.use(bodyParser.json()) // recommended package "body-parser"
app.use("/api/v1/products", ProductRoutes)
If something goes wrong, i.e. An invalid ObjectId is passed to the breadHelper or the requested Query does not yield proper results, then mongoose-bread will throw a MongooseBreadError
This makes detection of the Error type in the global Error Handler quite convient
Handle MongooseBreadError server.js
// app setup
app.use((error, req, res, next) => {
if (error instanceof MongooseBreadError) {
// logger info
const { details, issuer } = error
console.log( details, issuer )
// error details provided by mongooseBreadError
return res.status( error.statusCode ).json({
message: error.message,
status: error.statusCode,
result: error.result,
})
}
// generic error response
return res.status(500).json({
message: 'Something went wrong',
status: 500,
result: null
})
})
Example MongooseBreadError if searchableFields is an empty Array and request.query.search
is set
{
message: 'Search is not availabe for this resource',
details: 'To enable search provide an "searchableFields" Array to the plugin registration options',
issuer: 'MongooseBreadHelper parseSearchFilter',
statusCode: 404,
result: {}
}
By default this plugin allows a custom query to be passed via request.query as JSON string.
api.example.com/products?query="{"protection":"disable"}"
This can be harmfull if used by users without admin privileges
To disable this functionality remove the request.query.query
key
- via middleware (recommended)
Routes/ProductRoutes.js
const sanitizeBreadRequestMiddleware = (req, res, next) => {
delete req.query.sort
delete req.query.query
delete req.query.projection
//...
next()
}
router.get(
"/",
sanitizeBreadRequestMiddleware,
ProductController.browse
)
- or in the Controller before helper call
Controller/ProductController.js
const Product = require("../Models/Product")
module.exports = {
browse: catchAsync(async (req, res, next) => {
delete req.query.sort
delete req.query.query
delete req.query.projection
//...
const options = Product.breadHelper().createBrowseOptions(req)
const result = await Product.browse(options)
res.status(200).json({
message: req.query.search ? "search" : "browse",
result,
})
}),
}
For all options see: Options Documentation