An Interest In:
Web News this Week
- March 21, 2024
- March 20, 2024
- March 19, 2024
- March 18, 2024
- March 17, 2024
- March 16, 2024
- March 15, 2024
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.
- Node.js
- Have access to one package manager such as npm or yarn
- Access to Fauna dashboard
- 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.
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.
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.
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.
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
- 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
- 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"));});
- 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;};
- 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:
- 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
- getUserByEmail: It retrieves a user by email using the index we created earlier.
- loginUser: It logs a user in using the email and password.
- updateUser: It updates a users information which in this case, updates a users verified status.
- 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.
Lets confirm if the user has been created in the database
Well then confirm if our email has been sent.
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.
Well sign in again and see the new welcome message of a verified user.
- 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.
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
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To