The Datastore
Part 3 of this series focuses on the datastore for the user roles and management backend. MongoDB 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, which I knew I would need as well.
I also used Mongoose for object modeling, and Joi 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 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:
{
"host": "localhost",
"port": 3030,
// ...
"mongodb": "mongodb://localhost:27017/api"
}
For more information on MongoDB Connection strings, visit the MongoDB docs. The exact format of your string may vary based on your needs.
Connecting to FeathersJS
FeatherJS supports a handful of popular 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/. Within this folder, the user.model.js file defines the schema that correlates to how the data will be entered into MongoDB:
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 is made of a class, hooks, and service file, which are brought together in the User Service (user.service.js). The User Class 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
const { Service } = require('feathers-mongoose');
exports.Users = class Users extends Service {};
User Service
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) 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.
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!
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.
Top comments (0)