Mongoose models with the mighty Adonis.js
Have you met Adonis? No? And his ORM Lucid?
Ohh, so you don’t want SQL? What about mongodb? Mongoose?
Now it’s possible with adonis-mongoose-model 🙂.
⚠️ Note: this post is from Dec 2017.
Adonis is great ✨
If you’ve used Laravel in the past, then you shouldn’t worry about the learning curve; you are already on the top. This frameworks is basically inspired on Laravel’s Architecture, so it’s awesome. But if you were planning on using a No-SQL ODM, it’s not supported by default.
Many of Nodejs apps use MongoDB. Maybe out of “hype driven development”, or out of architectural necessity, but it that case, the most common ODM is Mongoose, so I’ve integrated both mongoose and Adonis.
The basic problem is that the adonis-auth uses Schemas and Serializers for analyzing tokens and sessions, but usually stored on the SQL DB with Lucid Models, so this package comes with a custom mongoose serializer, and a base Model a Token that adapts to the adonis schema, explained below.
Mongoose classic model creation
With mongoose API, you created models like this:
'use strict'
const mongoose = use('Mongoose')
const { Schema } = mongoose
/**
* FooBar's db Schema
*/
const FooBarSchema = new Schema({
})
/**
* FooBar's instance and static methods
* @class
*/
class FooBar {
}
FooBarSchema.loadClass(FooBar)
module.exports = mongoose.model('FooBar', FooBarSchema)
Having a Schema separate, and adding custom static and instance methods on a ES6 class, and then adding them into the schema.
The indexes were added on the schema, along with middleware and more.
Introducing Adonis-mongoose-model
After the install,
adonis install adonis-mongoose-model --yarn
and performing the install instructions, you should be able to
adonis make:mongoose
That’ll create a model using the basic structure, that inherits a bunch of behind the scenes behavior from the BaseModel.
'use strict'
const BaseModel = use('Model')
/**
* @class Foo
*/
class Foo extends BaseModel {
static boot () {
// Hooks:
// this.addHook('preSave', () => {})
// Indexes:
// this.index({}, {background: true})
}
/**
* Foo's schema
*/
static get schema () {
return {
}
}
}
module.exports = Foo.buildModel('Foo')
As you can see, now the build process is quite clean and readable. The model is a class, it has a static property that defines the schema (meaning you can create schema inheritance using super.schema to get the parent’s schema), it includes the base timestamps (created_at and updated_at) and strong hook and index properties.
It’s all nice and pretty, but how do I customize my schema? 🤔
Everything is heavily customizable, but if you need the schema, inside the boot function you can access this._schema
to get the mongoose schema before it’s used in the model.
Hooks, Indexes and Timestamps
Hooks are a Adonis thing. You even get a command for making them, and are like mongoose middleware. The new API that connects mongoose models with hooks is quite simple:
static boot () {
this.addHook('preSave', 'UserHook.notifyUpdate')
this.index({ email: 1 }, { background: true })
}
The first parameter of addHook is a string on camelCase that separates the mongoose time with the action. The time are pre | post, and the actions are init | save | remove | validate; and, for example, combining them using camelCase leaves something like postRemove (read about mongoose middleware).
The second parameter is the callback, that can be an anonymous function, or a hook’s method (read about hooks).
Indexes can be defined inside the boot method, so they are assigned when the model is created, or even outside the model, but before the buildModel method is triggered.
Calling them outside the boot method will stack them into a queue that will run when the schema is build, before the boot method, so it’s the same either way.
(Read more about mongoose indexes)
Timestamps: created_at and updated_at are defaults of every model you create. And also when you update a model, the updated_at value is updated with the current time. To remove this behavior, add this property on your class:
/**
* Disable timestamps for the Model
*
* @readonly
* @static
*/
static get timestamps () {
return false
}
When creating the schema, that false property will determine if the timestamps properties are appended or not.
Token Model
Adonis-auth needs a token, so we provide a TokenMongoose. You must create your own model (adonis make:mongoose Token) and extend the TokenMongoose, and then customize whatever you like
'use strict'
const TokenMongoose = use('AdonisMongoose/Src/Token')
/**
* Token's instance and static methods
* @class
*/
class Token extends TokenMongoose {
/**
* You can modify the amount of days that the token will be valid
*/
static expires () {
return 5
}
/**
* You can modify the default schema
*/
static get schema () {
// Edit your schema here
return super.schema
}
/**
* Customize populated properties for the user
*/
static getUserFields (type) {
return '_id email'
}
}
module.exports = Token.buildModel('Token')
You can check the full source of the Token Model here: src/Model/TokenMongoose.js
Keep reading? 📚
The instructios.md has more information on how the model creating life cycle works, and adding custom Schema options and more, check it out.
Now you’re up
Now it’s your turn to try Adonis with MongoDB like a boss! Always remember to submit if you encounter any issues, and fork it if you’d like to help with new ideas / bugfixing / optimizations!
Thanks for reading