What I found by building my own NodeJS boilerplate.
May 03, 2017 • 3 minutes to read
Mongoose Tips
I’ve played with Mongoose a lot in the past week. I’ve built a NodeJS API boilerplate for help me kickstart some REST API project. I setup the regular auth using PassportJS with the local and JWT strategies. By doing this I found some useful tricks with Mongoose. Some tips I never really see somewhere ‘maybe I didn’t search lot 😃’ and I want to share you what I found.
toJSON()
Example, you want to make authentication with your app and you don’t want to send the password to the front-end. It’s normal cause this is a big security issue if you did. So want you can do it’s create a function who take your user and return a new object.
1function getUser(user) {2 return {3 _id: user._id,4 username: user.username,5 };6}
This strategy work but I think the one I’m gonna show gonna be better.
1UserSchema.methods = {2 /**3 * Authenticate the user4 *5 * @public6 * @param {String} password - provided by the user7 * @returns {Boolean} isMatch - password match8 */9 authenticateUser(password) {10 return compareSync(password, this.password);11 },12 /**13 * Hash the user password14 *15 * @private16 * @param {String} password - user password choose17 * @returns {String} password - hash password18 */19 _hashPassword(password) {20 return hashSync(password);21 },2223 /**24 * Generate a jwt token for authentication25 *26 * @public27 * @returns {String} token - JWT token28 */29 createToken() {30 return jwt.sign(31 {32 _id: this._id,33 },34 constants.JWT_SECRET,35 );36 },3738 /**39 * Parse the user object in data we wanted to send when is auth40 *41 * @public42 * @returns {Object} User - ready for auth43 */44 toAuthJSON() {45 return {46 _id: this._id,47 token: `JWT ${this.createToken()}`,48 };49 },5051 /**52 * Parse the user object in data we wanted to send53 *54 * @public55 * @returns {Object} User - ready for populate56 */57 toJSON() {58 return {59 _id: this._id,60 username: this.username,61 };62 },63};
So here we have a lot of stuff to check 😃. Methods in mongoose are finally what they said. They are methods available on your user object. An example here we have js±authenticateUser(password)
who is use for authenticate the user find with email if the password is the right one. Same go for the js±_hashPassword(password)
who just simply hash the password before saving the user in the DB. js±createToken()
like the name say create the JWT token and can be user right inside the response
1res.status(200).json({ user, token: user.createToken() })
But the one I want you to see it's the js±toJSON()
. This on is use when finally you on your user. So if you check back
1res.status(200).json({ user, token: user.createToken() })
you can see I send the user. Because we have the js±toJSON()
on make it working just like this. We don't send timestamps, email, password etc. We just send js±_id
and js±username
nothing more. But ok why do the js±toAuthJSON()
? Because now I can reformat the response to be
1res.status(200).send(user.toAuthJSON())
so I just send an Object with _id
and token
. Hope this part make sense 😃.
The reason why have to methods for JSON below 😃.
Statics
After that in Mongoose, you have access to something call Statics in your schema. This is the same thing like in class. Statics are method who can be used without initiate this one. So you can use it right with the model himself.
Example
1PostSchema.statics = {2 /**3 * Create a post4 *5 * @public6 * @param {Object} args - Object contains title and text7 * @param {String} authorId - the author id8 * @returns {Post} Post Object - new post create9 */10 createPost(args, authorId) {11 return this.create({12 ...args,13 author: authorId,14 });15 },1617 list({ skip = 0, limit = 10 }) {18 return this.find()19 .sort({ createdAt: -1 })20 .skip(skip)21 .limit(limit)22 .populate('author');23 },24};
Here I have 2 statics methods to my post. This method finally is just for abstract some of your code. For me, that make my life a bit easier and make the controller cleaner. The js±createPost(args, authorId)
it's for just clean up a bit the code. I can use it by doing
1Post.createPost({ title: 'Hello' }, '123')
I just remove some code and make it a bit easier when it came to maybe change DB. I can keep the same controller but just change my js±Post
services.
After this one we have
1list({ skip = 0, limit = 10 })
This one it's just for make kind of pagination easier. You can see I use the ES6 feature Default Parameters who let me add default parameters if these values are js±undefined
. Again I can use it like that
1Post.list({ skip: 5, limit: 20 });
This is again for me just sugars and makes my code easier to follow.
Again toJSON() 😃
In the last example in the list we have js±.populate('author');
. Because of the js±toJSON()
by default the user gonna have only js±_id
and js±username
no need to add select value etc :). That's why I have js±toAuthJSON()
who is called on login and js±toJSON()
for this kind of thing.
Packages
Some packages I didn't know in the NodeJS ecosystem and need to be used 😄.
Joi
I really like this one for help me make validation in my controller. So easy to use too.
1export const validation = {2 create: {3 body: {4 title: Joi.string().min(3).required(),5 text: Joi.string().required(),6 },7 },8 update: {9 body: {10 title: Joi.string().min(3),11 text: Joi.string(),12 },13 },14};
After this, in your routes file, you do with the help of express-validation
1routes.post(2 '/',3 authJwt,4 validate(PostController.validation.create),5 PostController.create,6);7routes.patch(8 '/:id',9 authJwt,10 validate(PostController.validation.update),11 PostController.updatePost,12);
Helmet
Helmet's a library who help you secure your Express app. Easy to install just need to add it as a middleware js±app.use(helmet())
. This is for getting the standard. You can check on their GitHub to see another way.
Cors
Cors's a middleware who enable for you the Cross-Origin request. Can be added for getting everything working just by doing js±app.use(cors())
but it's a good thing to whitelist your front-end only etc. Take again a look at the docs before use it.
Http-Status
Http-Status just make your life easier to add status to your endpoint.
1export async function getList(req, res, next) {2 try {3 return res4 .status(HTTPStatus.OK)5 .json(await Post.list({ skip: req.query.skip, limit: req.query.limit }));6 } catch (err) {7 err.status = HTTPStatus.BAD_REQUEST;8 return next(err);9 }10}
Other useful packages
Prettier
Prettier help you to reformat your code and make it look better in no time. I start to use it about 1 month ago and now use it on every project I do. Easy to install this packages gonna save you time and gonna make your code look much better. PS if you use it with eslint and have a lot of red error maybe add eslint-config-prettier
to your project and add it to your extends in .eslintrc
. This gonna remove eslint issue with syntax looking and prettier gonna manage it.
Example
1{2 "extends": [3 "equimper",4 "prettier"5 ]6}
Lint-Staged
Lint-Staged gonna run your linter on your commit. Why have it ? Because maybe you use eslint and prettier and forgot all time to run the scripts. So your code looks bad etc. By adding this tools your commit gonna be linting before that let your commit. Can be really useful for a project with a lot of people.
For adding it I just add this in my packages.json
1{2 "pre-commit": "lint-staged",3 "lint-staged": {4 "*.js": [5 "eslint",6 "yarn prettier",7 "git add"8 ]9 },10 "scripts": {11 "lint": "eslint src --color",12 "prettier": "node ./scripts/prettier.js write",13 "lint-staged": "lint-staged",14 }15}
End word
Hope this little article was a little gold mine of packages and tips for you. That was a really good experience working on this simple boilerplate. Plz take a look at it and let me know what you think of it.