Stack selection, explanation, and steps to follow
Building an application API that can manage user registration and authentication inspires consideration. In this deep dive, we will step through these considerations looking at expected behaviors, technology decisions, support packages, and security requirements.
Before we do that, let’s do a sneak peek at the stack we’ve selected and what they have to offer.
Node.js
Node. js is a running environment for JavaScript. It is primarily used for non-blocking, event-driven servers.
Node will give us the ability to use JS on both the frontend and the backend, allowing for full-stack JavaScript development and has an easier learning curve for existing JS developers. There's a great ecosystem and its architecture suits lightweight, high traffic, data-intensive (but low processing/computation) applications that run across distributed devices.
Ideal applications for Node.js could include chat applications, a stockbrokers dashboard, monitoring dashboard, or a reverse proxy that is used during development instead of nGinx
Scaling considerations
JavaScript by design is a single-threaded language which means it can only process one command at a time, so its methods are considered blocking methods but it has asynchronous capabilities which allow its methods to be non-blocking.
To better explain this I liken it to a restaurant with one waiter and five tables.
A blocking system — The waiter takes down an order for a table and is unable to serve any other tables until the food has been prepared and delivered from the kitchen.
A non-blocking system — The waiter takes an order and brings it to the kitchen. They are then free to take another order and bring it to the kitchen. When the food is ready they will be called upon again to deliver the food from the kitchen to the respective table.
We could further scale with the addition of load-balancers or via clusters. Clusters allow us to take advantage of multi-core systems. So, instead of a single instance of Node.js running on a single thread, we can launch a cluster of Node.js processes to handle the load.
This is helpful, but in general heavy computations are not advised to be run on Node, They will strain the CPU and block additional requests.
Another consideration is that for relational databases, the ORM tools are not as advanced as other languages such as Rubys “ActiveRecord” etc.
Thoughts
For our Login application, there are no real architectural benefits to Node and JavaScript over a traditional server-side language, except that it will allow us to use JavaScript on both the front and back end of the app.
MongoDB
When it comes to databases, we have two obvious choices:
- A relational database that stores data in rows and columns created by an enforced schema.
- A non-relational database that stores data in collections and documents and can be schemaless.
There are advantages to both and these can depend on the data you store and how you may need to access and manipulate it. For this app, there is no huge advantage for one over the other, but I’ve decided to go with non-relational MongoDB for the following reasons.
- It’s super quick to get started. The document data model is a powerful way to store and retrieve data that allows developers to move fast. MongoDB is a schema-less NoSQL document database. It means you can store JSON documents in it, and the structure of these documents can vary as it is not enforced like SQL databases. Developers can install MongoDB and start writing code immediately on a cloud-based DB.
- JSON Storage: Yes, it is possible to store Json on SQL databases. But MongoDB and its document-based storage are ideal for it. JSON is a simple and powerful way to describe and store data as well as embed documents inside each other.
- Its works well with Node.js. Since MongoDB is a distributed database that allows ad-hoc queries, real-time updates and is a wonderful compatriot to Node.js which as we mentioned earlier is perfect is great for high traffic, data-intensive (but low processing/computation) applications that run across distributed devices
Scaling solutions
MongoDB’s horizontal, scale-out architecture can support huge volumes of both data and traffic.
MongoDB’s scale-out architecture, which distributes work across many smaller (and cheaper) computers, means that you can create an application confident that it will handle spikes in traffic as your business grows.
By comparison, most SQL databases use a scale-up architecture that is limited because it relies on creating faster and more powerful computers.
MongoDB can support large numbers of reads and writes. At the heart of these innovations is MongoDB’s approach to sharding, which allows clusters of information to be stored together as the information is spread across the cluster of computers.
MongoDB also supports database transactions that allow many changes to a database to be grouped together and either made or rejected in a batch.
Express
Express is a routing and middleware web framework that has minimal functionality of its own and is essentially a stack of middleware function calls.
These middleware functions have access to the req
 and res
 objects and the next
 param, which calls the next middleware module in the stack.
Express will be the glue for all these goodies:
-
Routing endpoints concerning the User model that we will create. TheÂ
express.Router
 is a routing system built into Express and takes care of all our routing needs -
Parsing JSON:Â TheÂ
express.json()
 method is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based onÂbody-parser
. - JSON Web Tokens:Â JWTS will sign and authentication requests to the application. We will create some custom middleware to handle user authentication, that listens to requests and responses
- Error responses: We will create custom middleware that listens to error responses on the server-side and allows us to handle them in a modular fashion.
-
express-unless:Â To conditionally skip a middleware when a condition is met, we can use theÂ
express-unless
 package. This is particularly helpful when used in conjunction with custom authentication middleware. -
bcryptjs:Â Passwords are never stored in plain text. TheÂ
bcryptjs
 npm package will encrypt our passwords and is one of the most used packages to work with passwords in JavaScript.
Mongoose
Mongoose is an object document modeling (ODM) layer that sits on top of Node’s MongoDB driver. If you’re coming from SQL, it’s similar to an object-relational mapping (ORM) for a relational database.
While it’s not required to use Mongoose with Mongo it has a lot of neat features to help during development. The ones we will concern ourselves with include the following:
1. Schemas
We mentioned earlier how Mongo is schemaless which is great for fast development although not so great for validating objects and maintaining a structure.
Mongoose schema will define the structure of the document, its default values, and its validators. It helps maintain structure, gives a clear idea of what is going into the database, reduces preventable bugs, and allows for cleaner code.
2. Models
Mongoose models provide an interface to the database for creating, querying, updating, deleting records, etc.
It also creates Model abstraction which makes it easier to work with, so it looks like you are working with just objects rather than pure data.
Schemas have a few configurable options which can be passed to the constructor or to the set method. We will use this to create JSON objects.
Package Installs
Wonderful. Now to set up our Mongo database. There are different ways to do this of course, but I believe by far the quickest is to avail the free shared DB available on the cloud.
Note: You should be able to follow these steps to create the Mongo database.
Once downloaded, run npm init
 in your terminal. This will prompt you to enter an entry point which we will set to server.js
:
$ npm install express mongoose express-unless bcryptjs dotenv jsonwebtoken — save
The Build
We are going to build a secure REST API that will handle common requirements for a User model:
// User DataUser {
Id: (auto generated UUID)
Username: String
Email: String
Password: String
Date: Date
}
The endpoints I will use to create and securely fetch this user are:
-
POST
 on the endpointÂ/users/register
 (create a new user) -
POST
 on the endpointÂ/users/login
 (check for user and authenticate) -
GET
 on the endpointÂ/users/:id
 (get a specific user)
The next thing I like to do is roughly design my directory structure:
Controllers
- Userscontroller.js
Modals
- Usersmodal.js
Services
- Userservice.js
Helpers
- Errorhandler.js
- jwt.js
server.js
.env
server.js
We are using Express middleware capabilities to create a modular system where we will do the following:
- Listen to requests and responses and route them to the appropriate controller, in this case to our user controller
- Authenticate requests, which will use our JWT module, unless they come from specific routes that do not require authentication.
- Parse JSON data
- Listen for errors in responses
We will also make our connection to mongoose when the app is initiated.
You will be able to use the Mongo URL that can be created by following the documentation.
Here we will also begin seeing some of the advantages of mongoose
.
It has two very useful built-in methods that act as event handlers for when an error occurs or on successful connection to the DB.
Express will help us again by listening to the localhost we decide upon. The following code will help us do this:
jwt.js
To handle the creation and authorization of JSON Web Tokens (JWTS), we will create some custom middleware and helper methods.
We can use these to check for the authorization token sent over by the client in the request header and create and sign tokens to send over to the client-side.
We have used the dotenv
 package to safely store secret passwords in a .env
 file, which is used to build and verify the token. Make sure to not commit this file to production.
errorHandler.js
A nice way I’ve seen of modulizing errors is by creating custom middleware that listens for error responses and manages what is returned to the client-side.
The error handling middleware modules positioning in the call stack is important since it will need to be called after the route middleware we applied.
UserController.js
Our user controller listens for requests with the help of Express Router, and it then determines what we do with these requests and their subsequent data.
When a user registers, the first thing we do is utilize bcryptjs
 to encrypt the password (it is wise to do this as soon as possible). It allows us to create salt. Salts create unique passwords even in the instance of two users choosing the same passwords and then hash the password.
Upon completion, we are ready to send it to our services module, which contains the methods needed to update our MongoDB database with the hashed password we created.
UserModals.js
Before we look at our service module it's first a good idea to look deeper at the usermodal.js
 file.
It’s here that we utilize Mongoose to create a schema for our User object. We are going to use this for type checking and validating data.
Mongoose Schemas have a few configurable options which can be passed to the constructor or to the set method, we can use this to create a toJSON
 the method that turns our MongoDB document into JSON that can be sent to our client-side.
Here we are creating and setting an id property and removing _id
, __v
, and the password hash which we do not need to send back to the client.
We then create our mongoose model and export it. This model will have access to all mongoose's helpful query methods to retrieve our data from MongoDB.Â
UserServices.js
Before we look at our service module, it's a good idea to look deeper at our usermodal.js.
It’s here that we utilize Mongoose to create a schema for our User
 object. We are going to use this for type checking and validating data.
Schemas have a few configurable options which can be passed to the constructor or to the set method and this will be helpful because we can use this to create a toJSON
 method that abstracts our MongoDB document into JSON that we are happy to send back to our client-side, by creating and setting an id property and removing _id
, __v
 and the password hash which we do not need to send back to the client.
We will then create our Mongoose model and export it. This model will have access to all the helpful query methods to retrieve our data from MongoDB.
I hope you have found this useful and thank you for reading.
GitHub Repo:Â https://github.com/gavmac/authentication-api
Leave a comment