Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 21, 2021 08:50 am GMT

Building a User Login System with Express js and Fauna

In this article, Ill show you how to build a user login system with the node framework express.js and Fauna.

What is Fauna?

Faunais a global cloud database created to integrate with the Jamstack and modern serverless architecture. Fauna is a flexible, developer-friendly, transactional database delivered as a secure and scalable cloud API with native GraphQL.

Fauna is a NoSQL serverless database, so you dont have to worry about database provisioning, scaling, sharding, replication, or correctness.

Lets dive right into building our user login system!

Prerequisites

To take full advantage of this article, you need to have the following installed on your laptop.

  1. Node.js
  2. Have access to one package manager such as npm or yarn
  3. Access to Fauna dashboard
  4. Have a basic knowledge of Node.js, Express, and Handlebars.js or a view engine.

About the App

In this app, we will have four routes:

  • Signup Route: In this route, a new user is created using necessary credentials, e.g. email, username, and password, and then the user is logged into their account and shown their dashboard page.
  • Sign In Route: In this route, the user logs in by providing sign-up details. If successful, the user is shown their dashboard page, not if not. The user is shown the providing with necessary error message depending on what caused the sign-in to be unsuccessful.
  • Dashboard Route: In this route, after a successful sign-up or sign-in, the user is shown a customised dashboard page welcoming the user to their page.
  • Sign Out Route: This is the route to sign a user out of their account.
  • Delete Account Route: In our app, a user is allowed to delete an account created. If successful, the users account is deleted from our Faunadatabase.
  • Confirm Token Route: This route allows users to confirm their email address before successfully redirecting to the dashboard page.

Before we create our routes, we need to create our Fauna database that well use for the app following the steps below.

Step 1: Set Up Our Fauna Database

To get started with our app, we need to create our database for the app in the Fauna dashboard.

You can create a Fauna account here.

In your dashboard, click on the Create Database button, provide a name for your database, and click create.

create database

Step 2: Generating your Fauna API key

The Faunasecret key connects fauna to an application or script, unique to a database.

We need to create a Fauna API key to connect the Fauna database to our app. To do this, go to the security settings on the left side of the screen.

fauna API key

Generate key

When you click on save in the last image, it will generate a new API key for you. Copy the API key and keep the key somewhere safe as you cant have access to that key in the dashboard again

Step 3: Creating a Fauna collection

We need to create a Faunacollection that we will use within our code.

A collection is simply a grouping of documents(rows) with the same or a similar purpose. A collection acts similarly to a table in a traditional SQL database.

In our app, we will have only a collection for users. The user collection is where we will store our user data.

To create the collection, click on the database you created, click on New Collection, enter your chosen collection name then click save.

You can create as many collection names as you wish to use in your app.

create collection

Step 4: Creating a Fauna Index

Indexes are used to quickly find data without searching every document in a database collection every time a database collection is accessed. Indexes can be created using one or more fields of a database collection. To create a Faunaindex, click on the indexes section in the left part of your dashboard.

create fauna index

In our app, we will only create one index, which is the user_by_email index.

The user_by_email index is what well use to get a users data with a given email. This index needs to be unique, so the collection doesnt have duplicate emails.

Creating the project and installing dependencies

First, we need to initialise your project in npm; type the following in your terminal to do so:

npm init

This will prompt some questions asked, you can answer them appropriately, and when this is done, a package.json file is created for you.

Next, we need to install the required dependencies. Type the following in your terminal:

npm install express faunadb dotenv express-handlebars

Structuring the App

App structure

  • The routes folder is where we have our routes.js file for defining our routes.
  • The views folder is where our pages will be created and, in this case, handlebars.
  • The app.js file is where we will set up our server.
  • The configure.js file is where we will set up our apps middleware.
  • The fauna.js file is where we will connect our Faunadatabase to our app and define functions used to create-user, login-user, and some other functions we will use in our routes.
  • The sendMail.js file is where we will use nodemailer to send confirmation emails to verify a user after a user creates an account.

Building Our Application

  1. Configuring and running the server: In your app.js, write the following code to set up your server.
var express = require('express'),  config = require('./configure'),  path = require("path"),  app = express();app = config(app);app.set("port", process.env.PORT || 5000);app.set("views", path.join(__dirname, "views"));var server = app.listen(app.get("port"), function () { console.log("Server up: http://localhost:" + app.get("port"));});
  1. In your config file, which is configure.js, write the following code to configure your middleware functions.
var createError = require('http-errors'); routes = require('./routes/routes') express = require('express'), session = require('express-session'), path = require('path'), cookieParser = require('cookie-parser'), logger = require('morgan'), dotenv = require('dotenv').config(),  flash = require('connect-flash'), exphbs = require('express-handlebars'), relativeTime = require('dayjs/plugin/relativeTime'), dayjs = require('dayjs');module.exports = function (app) { dayjs.extend(relativeTime); app.engine('.hbs', exphbs.create({  defaultlayout: 'main',  layoutsDir: path.join(__dirname, './views/layouts'),  partialsDir: path.join(__dirname, './views/partials'),  helpers: { timeago: () => dayjs(new Date().toString()).fromNow()},  extname: '.hbs', }).engine); app.set('view engine', 'hbs'); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(flash()); app.use(session({  secret: process.env.SECRET,  resave: true,  saveUninitialized: true,  maxAge: 600 })) app.use(function(req,res,next){  app.locals.isLoggedIn = req.session.user ? true : false  next();}) app.use(routes) app.use('/public/', express.static(path.join(__dirname, './public'))); // catch 404 and forward to error handler app.use(function(req, res, next) {  next(createError(404)); }); // error handler app.use(function(err, req, res) {  // set locals, only providing error in development  res.locals.message = err.message;  res.locals.error = req.app.get('env') === 'development' ? err : {};  // render the error page  res.status(err.status || 500);  res.render('error');  });  return app;};
  1. Create a .env file in your route folder and fill it with the following:
NODE_LOGIN_FAUNA_KEY=your generated fauna API keySECRET=your app secret keyEMAIL=your emailPASSWORD=your email password

The email you input here is what you will use to send confirmation emails to new users, so ensure its a valid and functional one.

Creating our Fauna helper functions

To create a user, log in a user, update a user verification status that we will use to know if a user is verified or not, and delete a user in Fauna. Fauna has provided helper functions to help with that. Paste the following in your code to help with that:

var dotenv = require('dotenv').config(),  faunadb = require('faunadb'),  bcrypt = require('bcrypt'),  q = faunadb.query;let Client = new faunadb.Client({ secret: process.env.NODE_LOGIN_FAUNA_KEY });exports.createUser = async (email, username, password) => { password = bcrypt.hashSync(password, bcrypt.genSaltSync(10)) // generates a hash for the password let data try {  data= await Client.query(     q.Create(    q.Collection('Users'),    {     data: {email, username, password, isVerified: false}    }   )  )  if (data.username === 'BadRequest') return // if there's an error in the data creation it should return null } catch (error) {  console.log(error)  return  } const user = data.data user.id = data.ref.value.id // attaches the ref id as the user id in the client, it will be easy to fetch and you can guarantee that it's unique return user}exports.getUserByEmail = async (email) => { try {  const user = await Client.query(   q.Get(    q.Match(     q.Index('user_by_email'),     email    )   )  )  return user.data } catch {  return // return null if there is any error. }}exports.loginUser = async (email, password) => {try { let userData = await Client.query(  q.Get(    q.Match(q.Index('user_by_email'), email.trim())  ) ) userData.data.id = userData.ref.value.id if (bcrypt.compareSync(password, userData.data.password)) return userData.data else return} catch (error) { return}}exports.updateUser = (userId) => { const user = Client.query(  q.Update(   q.Ref(q.Collection('Users'), userId),   {    data: {     isVerified: true    }   }  ) ) .then((result) => result.data) .catch((err) => console.log(err.message))}exports.deleteUser = (userId) => { const user = Client.query(  q.Delete(   q.Ref(q.Collection('Users'), userId)  ) ) .then((result) => console.log(result)) .catch((err) => console.log(err.message))}

Above, we created five Faunahelper functions which are:

  1. createUser: It takes in an email, username and password, generates a hash for the password using bcrypt, saves the users information to false and set isVerified to false until the user confirms the account, then the isVerified will be set to true
  2. getUserByEmail: It retrieves a user by email using the index we created earlier.
  3. loginUser: It logs a user in using the email and password.
  4. updateUser: It updates a users information which in this case, updates a users verified status.
  5. deleteUser: Deletes a user from the Faunadatabase.

Defining Routes

To define all possible routes we discussed earlier for the app, create a routes.js file in the routes folder type the following:

var express = require('express'),  hbs = require('express-handlebars'),  router = express.Router(),  auth = require('../fauna'),  {sendMail} = require('../sendMail'),  dotenv = require('dotenv').config(),  jwt = require('jsonwebtoken');router.get('/', (req, res) => { return res.render('index');});// Sign Up Routes router.get('/signup/', (req, res) => { return res.render('auth/signup')})router.post('/signup/', async (req, res) => { try {  const {username, email, password, confirm_password} = req.body  if (password !== confirm_password) {   return res.render('auth/signup', {    error: 'Passwords do not match'   })  }  const user = await auth.createUser(email, username, password)  let token = jwt.sign(user, process.env.SECRET, {expiresIn: 600})  if (user) {   req.session.user = user   // Send verification mail for confirmation of account using Nodemailer   sendMail(email, `Hi ${username}!,
To verify your account, please click on the link below and signin again.
http://
${req.headers.host}/confirm/${token}`, 'Verify your account') req.session.save((err) => {console.log(err)}) return res.redirect('/dashboard/') } } catch (error){ return res.render('auth/signup', { error: error.message }) } return res.render('auth/signup', { error: 'Username or Email is chosen' })})// Sign In Routesrouter.get('/signin/', function(req, res) { return res.render('auth/signin');});router.post('/signin/', async (req, res) => { try { const {email, password} = req.body const user = await auth.loginUser(email, password) if (user) { req.session.user = user req.session.save((err) => console.log(err)) return res.redirect('/dashboard/') } } catch (error){ return res.render('auth/signin', { error: 'Invalid Email or Password' }) } return res.render('auth/signin', { error: 'Invalid Email or Password' })});// Dashboard Routesrouter.get('/dashboard/', async (req, res) => { try { if (req.session.user) { const user = req.session.user return res.render('dashboard', {user}) } } catch (error){ return res.render('dashboard', { error: error.message }) } return res.redirect('/')});// Sign Out Routesrouter.get('/signout/', (req, res) => { req.session.destroy((err) => console.log(err)) return res.redirect('/signin/')})// Delete Account Routerouter.delete('/delete-account/', async (req, res) => { if (req.session.user) { auth.deleteUser(req.session.user.id) req.session.destroy(); return res.status(200).json({success: 'Data Deleted Successfully' }) } else { return res.status(400).json({error: 'Not Successfully Deleted'}) }})// confirm token and update user verification statusrouter.get('/confirm/:token', (req, res) => { const token = req.params.token jwt.verify(token, process.env.SECRET, (err, decoded) => { try { if (err) { return res.render('auth/signup', { error: 'Invalid Token' }) } user = auth.updateUser(decoded.id, {isVerified: true}) if (user) { req.session.user = user return res.redirect('/dashboard') } } catch (error) { return res.render('auth/signup', { error: 'Invalid Token' }) } })})module.exports = router;

In the dashboard route, we added user session after sign-in for easy logging in for a period of time provided the user hasnt signed out yet.

In the signout route, the user session is deleted, and the user is redirected back to the home page.

In the delete route, the user is deleted from our Faunadatabase with the deleteUser function we created in our fauna.js file.

In the confirm route, we generated a unique token using jsonwebtoken, sending an email using nodemailer with a redirect link containing the unique token with the link redirecting to the dashboard page and confirming the user email address. Then the users isVerified status will be set to true.

Sending Mails

Ive been mentioning sending mails, but for the mail to actually be sent, we need a helper function to send a mail after a user has created an account. We would create a sendMail.js file. Type the following below:

var config = require('./configure'),  express = require('express'),  router = express.Router(),  nodemailer = require('nodemailer');exports.sendMail = async (to, html, subject) => {  var transporter = nodemailer.createTransport({  service: 'gmail',  port:465,  auth: {    user: process.env.EMAIL,    pass: process.env.PASSWORD  }});var mailOptions = { from: process.env.EMAIL, to: to, subject: subject || 'Confirmation of Account', html: html};transporter.sendMail(mailOptions, function(error, info){  if (error) {    console.log(error);    return {error: error.message}  } else {    console.log('Email sent: ' + info.response);    return {success: info.response}    }  });  transporter.close()}

Testing Our App

Like I said earlier, our front end is built with handlebars. You can choose any view engine you want to use. Lets test the routes weve built:

  • SignUp Route

We signup with our credentials (email, username and password), It redirects to the dashboard page with a welcome message but saying the user should check his/her email for verification instructions.

Signup

Lets confirm if the user has been created in the database

confirm user

Well then confirm if our email has been sent.

confirm email

Ps: For you to enable nodemailer to send mails using your provided email, you have to configure your Gmail settings to allow less secure apps and enable Recaptcha.

  • Signin Route

Well click the link sent to the mail and check if it redirects to the sign-in page.

confirm sent mail

Well sign in again and see the new welcome message of a verified user.

welcome message

  • Signout Route

Well click the sign out button and sign out of the account.

  • Delete Route

We sign in again and test the delete account feature. The user will be completely deleted from the Faunadatabase.

Lastly, Well now confirm from our database if the user has been deleted.

check database

As we can see above, the only user we created has been deleted.

Conclusion

This article has built a web application that logs users in and logs users out using two exciting technologies, Fauna and Expressjs. The source code for this project is available on Github. If you enjoyed this article, please share it with your friends who will need it. You can reach me on Twitterif you have any questions.

Written in connection with the Write with Fauna Program.


Original Link: https://dev.to/sodiq123/building-a-user-login-system-with-express-js-and-fauna-4p32

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To