Part 3: User Roles and Management  Datastore -  MongoDB
Tags: showdev, webdev, javascript, mongodb
Published: 2020-09-15 00:50:58

## The Datastore

Part 3 of this series focuses on the datastore for the user roles and management backend. [MongoDB](https://mongodb.com) is great for storing documents. When the end-goal is moving data from a spreadsheet to a queryable database, Mongo was an easy choice for this use-case. It also supports [GeoQuerying](https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear), which I knew I would need as well.   

I also used [Mongoose](https://mongoosejs.com/) for object modeling, and [Joi](https://joi.dev/api/?v=17.2.1) as a validation library, which provides a few more out-of-the-box validations compared to the built-in Mongoose schema validations. 

> Note: I was looking for a quick and easy way to get this up and running for prototyping purposes. Worth noting is that you could also solely use Mongoose and write your own custom validation methods, which may be better from a dependency management standpoint (or omit Mongoose and use the native MongoDB Driver with a validation library like Joi).

### MongoDB Connection String
To define where the MongoDB datastore is hosted, this information is stored in a configuration file (though it can also be passed into the application through an environment variable like in any Node application). FeatherJS has fairly straightforward [configuration management](https://docs.feathersjs.com/api/configuration.html#configuration) based on the `NODE_ENV` variable. On project creation, `default.json` file is generated in the `/config` directory, which is pre-loaded with some common variables you can adjust for your project. If your `NODE_ENV` variable is set to `NODE_ENV=test`, then it will merge the `default.json` with `test.json`. The FeathersJS app then retrieves the value by accessing the key using something like `app.get('key')`;

To add a MongoDB connection, add `"mongodb": "mongodb://localhost:27017/api",` as a key-value pair to the json config file like so: 

```js
{
  "host": "localhost",
  "port": 3030,
  // ...
  "mongodb": "mongodb://localhost:27017/api"
}
```

For more information on MongoDB Connection strings, visit the [MongoDB docs](https://docs.mongodb.com/manual/reference/connection-string/). The exact format of your string may vary based on your needs.

### Connecting to FeathersJS
[FeatherJS](https://docs.feathersjs.com/) supports a handful of [popular database adapters](https://docs.feathersjs.com/guides/basics/services.html#database-adapters), including In-Memory Storage, Local Storage, popular relational databases (MySQL, Postgresql, etc.), and Elasticsearch.

The service-oriented database adapter configuration makes it easy to connect to multiple datastores to retrieve data. The CLI generator also makes it easy to scaffold new services for different databases.

For this starter, a Users service is defined to store user information. This can be done as part of the initial app creation. It conveniently asks you what you need for the user service, including the database adapter used. The generator then creates the necessary files to get you started. For creating new services, you can use `feathers generate service`.

In this application, because I'm using Mongoose, a model file is created in [/server/src/models/](https://github.com/meditatingdragon/quasar-feathersjs-user-management/tree/master/server/src/models). Within this folder, the [user.model.js](https://github.com/meditatingdragon/quasar-feathersjs-user-management/blob/master/server/src/models/users.model.js) file defines the schema that correlates to how the data will be entered into MongoDB:

```js
const schema = new mongooseClient.Schema(
    {
      email: { type: String, unique: true, lowercase: true },
      password: { type: String },
      firstname: { type: String },
      lastname: { type: String },
      company: { type: String },
      department: { type: String },
      title: { type: String },
      city: { type: String },
      permissions: { type: Array, default: ['guest'] },
      phone: { type: String },
      passwordReset: { type: String },
      passwordResetToken: { type: String },
      lastLoggedIn: { type: Date },
      team: { type: 'ObjectId', ref: 'Teams' },
      googleId: { type: String },
      isVerified: { type: Boolean },
      verifyToken: { type: String },
      verifyShortToken: { type: String },
      verifyLongToken: { type: String },
      verifyExpires: { type: Date },
      verifyChanges: { type: Object },
      resetToken: { type: String },
      resetExpires: { type: Date },
    },
    {
      timestamps: true,
    }
  );
```

As mentioned in Part 2, the [User Service](https://github.com/meditatingdragon/quasar-feathersjs-user-management/tree/master/server/src/services/users) is made of a class, hooks, and service file, which are brought together in the [User Service (user.service.js)](https://github.com/meditatingdragon/quasar-feathersjs-user-management/blob/master/server/src/services/users/users.service.js). The [User Class](https://github.com/meditatingdragon/quasar-feathersjs-user-management/blob/master/server/src/services/users/users.class.js) extends the Mongoose Database provider and provides access to `.create()`, `.update()`, `.patch()`, `.remove()` methods for MongoDB, which then get used by the service when a user action is triggered. 

User Class
```js
const { Service } = require('feathers-mongoose');
exports.Users = class Users extends Service {};
```

User Service
```js
const { Users } = require('./users.class');
const createModel = require('../../models/users.model');

module.exports = function (app) {
  const options = {
    Model: createModel(app),
    paginate: app.get('paginate')
  };

  // Initialize our service with any options it requires
  app.use('/users', new Users(options, app));

  // Get our initialized service so that we can register hooks
  const service = app.service('users');
};
```

### Model Validation
I handled the user model validation in the feather hooks ([users.hooks.js](https://github.com/meditatingdragon/quasar-feathersjs-user-management/blob/master/server/src/services/users/users.hooks.js)) layer using the Joi library. After defining the acceptable values, I used the `validate.mongoose(updateSchema, joiOptions)` hook, defining `updateSchema` as the allowed formats for the few fields a user would be permitted to pass to the backend for changes. If it didn't meet the criteria, the request would fail and return an error. 

```js
const firstname = Joi.string()
  .trim()
  .min(2)
  .max(30)
  .pattern(new RegExp('^[a-zA-Z0-9 ]{2,30}$'))

// ... omitted for brevity, view source code 
// for complete validation code

const updateSchema = Joi.object().keys({
  firstname: firstname,
  lastname: lastname,
  city: city,
  company: company,
});
```
The update schema that is checked depends on the user role, as admins have broader permissions to update additional user fields than regular users. 

### Mongoose vs MongoDB Native Driver
When I first started this project, I weighed the benefits of sticking with the original MongoDB adapter or using an object modeling library like Mongoose. After reading several analyses linked below, I ultimately decided to stick with Mongoose. Curious to hear about others' experiences with Mongoose vs. MongoDB adapters. Leave a comment and share your thoughts!

* [Mongoose vs the MongoDB Node Driver](https://medium.com/better-programming/mongoose-vs-the-mongodb-node-driver-7dc59f00a5dd)
* [Mongoose vs MongoDB Native](https://voidcanvas.com/mongoose-vs-mongodb-native/)

### Wrapping up
That wraps up this project in its current state. I may update it at a later date when I incorporate elasticsearch into this backend. Looking forward to adding powerful search capabilities to this app. I may also add more testing. There is none at this point in time.

Leave a comment, question, or suggestion! Let me know what you think.