This is the second part of the "How to write an Authentication API using MongoDB and Express" series. 🌈
You can check out the First Part here
In this part , I'm going to cover the simplest approach to connecting to MongoDB using the Mongoose ODM.
Mongoose provides different lifecycle methods as well as simple ways to write out elaborate Schemas that interface with your MongoDB collections. We'll make use of this functionality today and define our User Schema
using Mongoose.
Setting up a basic server using Express
Prerequisites 🌻
- NodeJS installed on your system.
-
npm
installed on your system. - Any Code Editor (Visual Studio Code, Sublime)
- Postman for testing out our API. (or Insomnia , VSC REST CLIENT)
- Terminal
Initializing a Repository 🌈
cd
into your preferred directory and run npm init -y
.
The -y flag says yes to all the options when npm prompts you to fill out the package.json file.
npm is a package manager that allows us to add , use and manage libraries , frameworks and additional packages in our Project.
Run npm i express mongoose
to install express and mongoose. This will add express and mongoose to your project. You can check if they're installed by inspecting your package.json file.
Let's also install two additional devDependencies. devDependencies are only used for development purposes and are not shipped in production. To install packages as devDependencies, we use the --save-dev
flag.
npm i nodemon morgan --save-dev
.
We'll use nodemon to reload our server everytime we make changes to our server.js
file. we'll use Morgan to monitor HTTP requests made to our server.
Go to your package.json
file and add "start": "nodemon server.js",
under scripts.
Your package.json
should look like this at the end.
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1",
"mongoose": "^5.9.25",
},
"devDependencies" : {
"nodemon": "^2.0.4".
"morgan": "^1.10.0"
}
}
Setting up an Express server
Let's set up a simple Express server. In your project directory , create a server.js
file and add the following code.
const express = require('express');
const app = express();
const morgan = require('morgan');
app.use(morgan('tiny'));
app.listen(5000, () => {
console.log("Server started on PORT : ", port);
})
In the code above , we bring in the dependencies we require by using the require(<package_name>)
syntax and then assign it to a variable.
we invoke the express() function and store it's return value in app.
const app = express()
We also require the morgan
package. Since morgan
is a middleware , we use it using the app.use(<middleware>)
method. We also pass 'tiny'
parameter. There are different values you can pass as parameters to the morgan function. You can read more about morgan here.
Go back to your terminal and run npm start
to startup your server and you should see the following message.
> npm start
Server started on PORT : 5000
Creating a MongoDB Cluster 🌈
Let's head to https://www.mongodb.com and set up a cluster for personal use.
Create an account and sign in to it. On signing in , you should see a button on the right that says Create a New Cluster
In the next screen , choose any Cloud provider and choose a Region with a free-tier that's closest to you.
Once you're satisfied with your options , proceed to create a cluster. It will take 3-5 minutes to create a cluster and soon , you'll see your newly created cluster on the home screen.
Now , before we can connect to our cluster , we need to make a few more adjustments.
Go to the Database Access tag and click on Add New Database User
Select the method of authentication as Password and fill out a username and password for the DB user.
Make sure to jot down the password and username somewhere for later , when we need to connect to our Database.
Set the user's privileges to Read and Write to Database and click on the Add User Button. You can now see that the user has been added.
Finally, click on the Network Access tab in the left pane and click on the Add IP Address Button.
On the pane , that pops up , click on the Add Current IP Address Button and then click on the confirm button.
With this , we're done with setting up our MongoDB Cluster.
Let's copy our connection string next.
Click on the connect button
Let's connect to MongoDB using their native drivers.
Copy the connection string.
Note ⚠️
I have opted to use the connection string for NodeJS version 2.2.12
or later as opposed to the latest connection string. While not all people would run into this problem , I had troubles connecting with the latest Node connection string when using Mongoose.
Connecting using Mongoose.connect 🌈
Before connecting to our Database , lets install an additional dependency to our project. Stop your server using ctrl + C
and run npm i dotenv
.
Dotenv is a package that allows us to store sensitive information such as usernames and passwords in a .env
file as environment variables.
In your project , create a .env
file. To define an environment variable , simple type in the name of the variable , an equal-to sign and then the value.
VARIABLE_NAME = your_password
Now we can use the environment variable anywhere in our project by simply requiring the dotenv
package.
require('dotenv').config();
To use an environment variable ,
const port = PROCESS.env.VARIABLE_NAME;
Let's setup our environment variables.
In our .env
file , add the connection string , password , username and let's also add a name for our database.
In our connection string , erase the beginning string username:<password>@
.
We will pass the username and password separately as parameters when we're connecting to our database.
Define the following env variables and fill in the values as required.
DB_URI = <your_connection_string>
DB_NAME = <any_db_name>
DB_USER = <your_username>
DB_PASSWORD = <your_password>
Note that the username and password are not the username and password of your MongoDB account. They're the username and password you set , when you created the DB user.
We'll now define our initDB.js
file. We'll set up a separate file so you can add this file to any future backend projects that require connecting to mongoDB. Separating different aspects of your code also improves readability.
Create an initDB.js
file and add the following code to it.
const { connect, connection } = require('mongoose');
const { config } = require('dotenv');
/*const {___} = require(<package>) is called Destructuring.
This makes our code a lot more cleaner.
*/
/*We'll use module.exports since we want to import this file in our server.js*/
module.exports = () => {
config(); //invoking the dotenv config here
const uri = process.env.DB_URI;
connect(uri, {
dbName: process.env.DB_NAME,
user: process.env.DB_USER,
pass: process.env.DB_PASS,
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
})
.then(() => {
console.log('Connection estabislished with MongoDB');
})
.catch(error => console.error(error.message));
}
In the code above we've used the connect
method that Mongoose provides us.
You can read more about mongoose here.
We pass two parameters to this method.
connect('<connection_string>', { options });
dbName: process.env.DB_NAME,
user: process.env.DB_USER,
pass: process.env.DB_PASS,
We can pass our dbName
user
and pass
as options rather than adding them to our connection string.
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
The options above are passed to avoid any Deprecation warnings
. Think of them as nothing more than boilerplate code for now.
Since the connect method returns a promise (Read more about promises here), we can use .then()
method to handle our promise and the .catch()
method to handle any errors we may encounter.
In our .then()
method block , we use a callback function to console.log()
that we've connected to MongoDB.
In our .catch()
method block , we'll fire another callback function that gives us the error
parameter. We'll log this into our console.
console.error(error.message)
We're almost done here. Let's require the initDB.js
file in server.js
.
//Initialize DB
require('./initDB')();
Run npm start
in your terminal and you should see ,
> npm start
Server started on PORT : 5000
Connection established with MongoDB
Adding Mongoose Lifecycle methods
Mongoose provides us with the connection
method to interface with the MongoDB connection at different phases
- Connected
- Disconnected
- Error
Let's log different messages to the console based on what part of the cycle , our mongoDB connection is at.
Add the following code to initDB.js after the catch block.
connection.on('connected', () => {
console.log('Mongoose connected to DB Cluster');
})
connection.on('error', (error) => {
console.error(error.message);
})
connection.on('disconnected', () => {
console.log('Mongoose Disconnected');
})
On restarting the Express server , we can now see
> npm start
Server started on PORT : 5000
Connection established with MongoDB
Mongoose connected to DB Cluster
However , when you stop the server , you'll not see the disconnected message. This happens because we're abruptly stopping the express server at the same time.
Add the following code
process.on('SIGINT', () => {
connection.close(() => {
console.log('Mongoose connection closed on Application Timeout');
process.exit(0);
})
Now you'll see the Mongoose connection closed on Application Timeout message when you stop the server.
process.on('SIGINT' , () => {..})
The above code is used to interface with the server at exactly the point when it is shut down.
With this you have now successfully connected to MongoDB from your express server using mongoose!
Defining a User Schema 📚
In your project directory , create a folder called models. We'll define any and all models for our Collection in this folder.
Let's also create a file called Users.model.js
.
To this file add the following code
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
email: {
type: String,
unique: true,
lowercase: true,
required: true
},
username: {
type: String,
unique: true,
min: 6,
max: 15
},
password: {
type: String,
required: true,
min: 6,
max: 1024
},
role: {
type: String,
enum: ['user', 'admin'],
required: true,
default: 'user'
},
date: {
type: Date,
default: Date.now()
}
// orders: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Order' }]
})
module.exports = mongoose.model('User', UserSchema);
In the code above , we require('mongoose')
and initialise the Schema method which is part of the Mongoose package to our own Schema constant.
const Schema = mongoose.Schema
Now with the help of Schema
, we can define the structure of what our User's data should look like.
The object defined inside the Schema is self-explanatory.
You can read about the different options you can pass in as part of the object here.
You can see that I have commented out one particular line of code in the definition. Although it is not important to discuss it now , we will be using it to define a foreign key to a different collection.
Finally we'll use the mongoose.model('<name_of_model>',model_Schema)
to "package/model" our mongoose schema and export it using module.exports
.
With this , we've learnt how to ,
- Connect to MongoDB
- Define a Schema
That's all for part 2 folks! 🌻
In the next part , we'll set up our API's Routes and set up Validation Schemas to validate data that is posted to our server. 👨🏻💻
Top comments (3)
Very nice explanation! 🙂
Very Helpful!
Thanks