Login and Registration Systems are the basic components and as a developer, you have to implement such systems for the client projects, open-source projects, or a college project.
In this tutorial, we are going to learn how to build a simple and working login and registration system. We are going to use the following technologies.
- Node – To build a Service
- MongoDB – To store users
- Redis – to handle session
- Bootstrap and jQuery – for a simple front-end
You need the following software installed in your system before proceeding ahead.
- Node v11.15.0 or higher/li>
- MongoDB v4.0.3 or higher
- Redis v3.0.7 or higher
Once you have installed it, we can proceed ahead.
What we are going to build?
We are going to build a simple user login and registration system using Node and MongoDB. Check out the final result.
Sign up page
Login page
User profile page
I recommend you to clone the repository and refer the code along with the tutorial.
Let’s begin.
MongoDB and Redis
After installing the MongoDB and Redis, you need to run the database server programs.
To run the MongoDB server program, run the following command.
To run the Redis server program, run the following command.
Let’s proceed ahead and create our service using Node.
Building User Login and Registration System using Node
Create a new Node project using the following command.
Install required dependencies using the following command.
Create a new file and name it config.json. We will store the application configuration in this file.
"mongodbURL": "mongodb://localhost:27017/userDemo",
"redisHost": "localhost",
"redisPort": 6379,
"sessionSecret": "some-secret-hash",
"port": 3000,
"env": "development"
}
Next, we will create the entry file i.e app.js.
const session = require("express-session");
const redis = require("redis");
const redisStore = require("connect-redis")(session);
const nconf = require("nconf");
const bodyParser = require("body-parser");
const chalk = require("chalk");
const path = require("path");
const app = express();
// load config file
nconf
.argv()
.env()
.file({
file: __dirname + "/config.json",
});
// connect to redis session store
const redisSessionStore = redis.createClient(
nconf.get("redisPort"),
nconf.get("redisHost"),
{
db: 0,
}
);
redisSessionStore.on("connect", () => {
console.log(
`${chalk.green("✓")} Connected to ${chalk.green("Redis")} Session Store`
);
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// session store
app.use(
session({
secret: nconf.get("sessionSecret"),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week
},
store: new redisStore({ client: redisSessionStore }),
resave: false,
saveUninitialized: false,
})
);
// set static path
app.set("views", path.join(__dirname, "/views"));
app.engine("html", require("ejs").renderFile);
// routes
app.use("/", require("./routes/static"));
app.use("/users", require("./routes/users"));
// start the app
app.listen(nconf.get("port") || 3000);
console.log("App Started...");
First, we are loading our config.json file. We are going to use the configuration variable throughout the application.
Next, we are making a connection to Redis, and later passing the connection to express session.
In a production environment, you should always use an external session store such as Redis.
Let’s go and create routers of our application, create a new folder and name it routes. Create two new files in it and name it static.js and users.js.
Here is the code of each router file.
const app = express();
const router = express.Router();
// static routes
router.get("/", (req, res) => {
if (req.session.user) {
return res.redirect("/home");
}
res.render("index.html");
});
router.get("/home", function (req, res) {
if (req.session.user) {
return res.render("home.html", { name: req.session.user.name });
}
res.redirect("/");
});
module.exports = router;
const router = express.Router();
const joi = require("@hapi/joi");
const models = require("../models/users");
router.post("/login", async (req, res) => {
try {
const schema = joi.object().keys({
email: joi.string().email().required(),
password: joi.string().min(6).max(20).required(),
});
const result = schema.validate(req.body);
if (result.error) {
throw result.error.details[0].message;
}
let checkUserLogin = await models.verifyUser(result.value);
if (checkUserLogin.error) {
throw checkUserLogin.message;
}
// set session for the logged in user
req.session.user = {
name: checkUserLogin.data.name,
email: checkUserLogin.data.email,
};
res.json(checkUserLogin);
} catch (e) {
res.json({ error: true, message: e });
}
});
router.post("/signup", async (req, res) => {
try {
const schema = joi.object().keys({
name: joi.string().min(3).max(45).required(),
email: joi.string().email().required(),
password: joi.string().min(6).max(20).required(),
});
const result = schema.validate(req.body);
if (result.error) {
throw result.error.details[0].message;
}
let addUserResponse = await models.addUser(result.value);
res.json(addUserResponse);
} catch (e) {
res.json({ error: true, message: e });
}
});
router.get("/logout", (req, res) => {
if (req.session.user) {
req.session.destroy();
}
res.redirect("/");
});
module.exports = router;
We are using the joi module to validate the incoming data from the front-end. If the incoming data is properly validated, we are then calling our database function.
Let’s create our database functions. Create a new folder and name it models. Create a new file and name it connection.js.
const nconf = require("nconf");
const chalk = require("chalk");
mongoose.connect(nconf.get("mongodbURL"), {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// validate MongoDB connection
const db = mongoose.connection;
// events
db.on("error", () => {
console.log("MongoDB connection error");
process.exit(0);
});
db.once("open", function (callback) {
console.log(
`${chalk.green("✓")} Connected to ${chalk.green("MongoDB")} Store`
);
});
module.exports = {
mongoConnection: db,
};
In the code shown above, we are creating a connection to the MongoDB database specified in the configuration file. After the successful connection, we are exporting the connection variable to be used for querying.
Create a new file and name it users.js in the models directory.
const { mongoConnection } = require("./connection");
/**
* @addUser
*/
function addUser(userData) {
return new Promise(async (resolve, reject) => {
try {
// check if user does not exists
let checkUserData = await checkIfUserExists({ email: userData.email });
if (checkUserData.data && checkUserData.data.length > 0) {
// user already exists, send response
return resolve({
error: true,
message: "User already exists with this credentials. Please login",
data: [],
});
}
// generate password hash
let passwordHash = await bcrypt.hash(userData.password, 15);
userData.password = passwordHash;
// add new user
mongoConnection
.collection("users")
.insertOne(userData, async (err, results) => {
if (err) {
console.log(err);
throw new Error(err);
}
//return data
resolve({
error: false,
data: results.ops[0],
});
});
} catch (e) {
reject(e);
}
});
}
/**
* @verifyUser
* @param {*} userData
*/
function verifyUser(userData) {
return new Promise(async (resolve, reject) => {
try {
let userDatafromDb = await checkIfUserExists({ email: userData.email });
if (userDatafromDb.data && userDatafromDb.data.length > 0) {
// user already exists, verify the password
let passwordVerification = await bcrypt.compare(
userData.password,
userDatafromDb.data[0].password
);
if (!passwordVerification) {
// password mismatch
return resolve({
error: true,
message: "Invalid email or password",
data: [],
});
}
// password verified
return resolve({ error: false, data: userDatafromDb.data[0] });
} else {
return resolve({
error: true,
message:
"There is no user exists with this credentials. Please create a new account.",
data: [],
});
}
} catch (e) {
console.log(e);
reject(e);
}
});
}
/**
* @checkIfUserExists
*/
function checkIfUserExists(userData) {
return new Promise((resolve, reject) => {
try {
// check if user exists
mongoConnection
.collection("users")
.find({ email: userData.email })
.toArray((err, results) => {
if (err) {
console.log(err);
throw new Error(err);
}
resolve({ error: false, data: results });
});
} catch (e) {
reject(e);
}
});
}
module.exports = {
addUser: addUser,
verifyUser: verifyUser,
};
In the code shown above, we are adding a new user to our system and verifying it using the email and password combination.
We are using the bcrypt module to generate a password hash and store it in the database and later verify it during the login process.
Next, we need to create a simple user interface to validate our code. I have created a very simple user interface. I am not going to show the code here as it’s very long HTML code, if you want, you can view the code here.
Running our program
Run the program using the following command.
Navigate your browser to localhost:3000 to view the application.
Create a new user.
Login using the credentials.
Check the entry in the database.
User profile page.
What’s next?
We have to build a simple user login and registration system using Node, MongoDB, and Redis. In a production scenario, we may need to develop the user verification feature, forgot password feature, account deletion feature, etc. These features can be built on top of the codebase we have developed together.
Further Study
Building Email Verification System Using Node and Redis
Node js Tutorial for Beginners Step by Step With Examples
Caching a MongoDB Database with Redis