DEV Community

Cover image for How to make e commerce website with HTML, CSS and JS part 2
Modern Web
Modern Web

Posted on • Edited on

How to make e commerce website with HTML, CSS and JS part 2

Hello, hope you'll are good. Today we'll do the second part of our fullstack e-com website series. In this part, you'll make a node server to run website on localhost, then you'll learn to do form validations and storing user in firestore. In total, in this video we'll make signup page/ login page, logout feature and sellers dashboard.

If you haven't watched previous part. Watch now

To see demo or you want full coding tutorial video for better understanding. You can watch the tutorial below.

Video Tutorial

I appreciate if you can support me by subscribing my youtube channel.

Code

You can see below, our project's folder structure. We have some new files compare to what we had in the previous part.

Frame 5 (2)

Download Images, Get Source Code

So let's start coding.

NPM Init

Start with server, open the previous code folder in terminal or cmd prompt. And run npm init. This will initialize the NPM to the project. After that, install some packages by running this command.



npm i express.js nodemon firebase-admin bcrypt


Enter fullscreen mode Exit fullscreen mode
  1. express.js - is to create a server
  2. nodemon - is to run server continuously.
  3. firebase-admin - is to access firebase from backend.
  4. bcrypt - is to encrypt the user's password before storing it inside our database.

Once you are done with installation. You'll see package.json on your directory. Open the file and changes in scripts object.



"scripts": {
    "start": "nodemon server.js"
}


Enter fullscreen mode Exit fullscreen mode

This will make a start command for use to use with NPM. Now if you haven't created a server.js file. Make one. And let's make the server.

Server

Open server.js file. And start by importing the packages we just installed.



// importing packages
const express = require('express');
const admin = require('firebase-admin');
const bcrypt = require('bcrypt');
const path = require('path');


Enter fullscreen mode Exit fullscreen mode

We didn't installed the path package/library as it is a default package come with nodeJS. path allow to access the path or do path related stuffs.



// declare static path
let staticPath = path.join(__dirname, "public");


Enter fullscreen mode Exit fullscreen mode

Make public folder's path a static path. What is static path ? Static path is just a path which tells server where it has to look for the files.



//intializing express.js
const app = express();

//middlewares
app.use(express.static(staticPath));

app.listen(3000, () => {
    console.log('listening on port 3000.......');
})


Enter fullscreen mode Exit fullscreen mode

In above code, I am make a express server and listening for requests on port 3000.

Make / , /404 routes.



//routes
//home route
app.get("/", (req, res) => {
    res.sendFile(path.join(staticPath, "index.html"));
})


Enter fullscreen mode Exit fullscreen mode

Start your server now by running npm start on terminal. Open localhost:3000 on your chrome to view the page. And if the server is working, you'll see the index.html page.

For 404 route. We'll use middle ware. Make sure to add this middle ware at the very bottom of the server. Otherwise you'll get 404 page even if you are on some defined route.



// 404 route
app.get('/404', (req, res) => {
    res.sendFile(path.join(staticPath, "404.html"));
})

app.use((req, res) => {
    res.redirect('/404');
})


Enter fullscreen mode Exit fullscreen mode

You can notice, I have made a separate 404 page and redirecting user on making request to any unknown route. Well why I did that? I did that because, if I deliver the 404 page through middle ware. I'll definitely get the page, but if we go the nested routes, I'll get page without styles. See the illustration below

Frame 6

So we are almost done with our server for now, just create a /signup route to deliver signup page.



//signup route
app.get('/signup', (req, res) => {
    res.sendFile(path.join(staticPath, "signup.html"));
})


Enter fullscreen mode Exit fullscreen mode

Add all the routes before 404 route.

Sign Up Page

Open your signup.html file. Start with HTML5 template. Give a suitable title and link form.css file to it.

First make a loader for the page.



<img src="img/loader.gif" class="loader" alt="">


Enter fullscreen mode Exit fullscreen mode
form.css


*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body{
    width: 100%;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: #f5f5f5;
    font-family: 'roboto', sans-serif;
}

.loader{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 100px;
}


Enter fullscreen mode Exit fullscreen mode
Output

Capture3

Now make the form.



<div class="container">
    <img src="img/dark-logo.png" class="logo" alt="">
    <div>
        <input type="text" autocomplete="off" id="name" placeholder="name">
        <input type="email" autocomplete="off" id="email" placeholder="email">
        <input type="password" autocomplete="off" id="password" placeholder="password">
        <input type="text" autocomplete="off" id="number" placeholder="number">
        <input type="checkbox" checked class="checkbox" id="terms-and-cond">
        <label for="terms-and-cond">agree to our <a href="">terms and conditions</a></label>
        <br>
        <input type="checkbox" class="checkbox" id="notification">
        <label for="notification">recieve upcoming offers and events mails</a></label>
        <button class="submit-btn">create account</button>
    </div>
    <a href="/login" class="link">already have an account? Log in here</a>
</div>


Enter fullscreen mode Exit fullscreen mode

If you notice the above code, I am using div for forms instead of form tag. Why? Because, with HTML form you can send POST request to server but can't catch the response and we want to catch the response from the server to validate the success.

Form.css


.logo{
    height: 80px;
    display: block;
    margin: 0 auto 50px;
}

input[type="text"],
input[type="password"],
input[type="email"],
textarea{
    display: block;
    width: 300px;
    height: 40px;
    padding: 20px;
    border-radius: 5px;
    background: #fff;
    border: none;
    outline: none;
    margin: 20px 0;
    text-transform: capitalize;
    color: #383838;
    font-size: 14px;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.01);
    font-family: 'roboto', sans-serif;
}

::placeholder{
    color: #383838;
}

.submit-btn{
    width: 300px;
    height: 40px;
    text-align: center;
    line-height: 40px;
    background: #383838;
    color: #fff;
    border-radius: 2px;
    text-transform: capitalize;
    border: none;
    cursor: pointer;
    display: block;
    margin: 30px 0;
}

/* checkbox styles */

.checkbox{
    -webkit-appearance: none;
    position: relative;
    width: 15px;
    height: 15px;
    border-radius: 2px;
    background: #fff;
    border: 1px solid #383838;
    cursor: pointer;
}

.checkbox:checked{
    background: #383838;
}

.checkbox::after{
    content: '';
    position: absolute;
    top: 60%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 80%;
    height: 100%;
    pointer-events: none;
    background-image: url(../img/check.png);
    background-size: contain;
    background-repeat: no-repeat;
    display: none;
}

.checkbox:checked::after{
    display: block;
}

label{
    text-transform: capitalize;
    display: inline-block;
    margin-bottom: 10px;
    font-size: 14px;
    color: #383838;
}

label a{
    color: #383838;
}

.link{
    color: #383838;
    text-transform: capitalize;
    text-align: center;
    display: block;
}


Enter fullscreen mode Exit fullscreen mode

Above is a lot of CSS, isn't it. Well if you don't know any of the above CSS properties, feel free to ask me in comments.

Output

Capture4

Now, make an alert box.



<div class="alert-box">
    <img src="img/error.png" class="alert-img" alt="">
    <p class="alert-msg">Error message</p>
</div>


Enter fullscreen mode Exit fullscreen mode


/* alert */
.alert-box{
    width: 300px;
    min-height: 150px;
    background: #fff;
    border-radius: 10px;
    box-shadow: 0 5px 100px rgba(0, 0, 0, 0.05);
    position: absolute;
    top: 60%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 20px;
    opacity: 0;
    pointer-events: none;
    transition: 1s;
}

.alert-box.show{
    opacity: 1;
    pointer-events: all;
    top: 50%;
}

.alert-img{
    display: block;
    margin: 10px auto 20px;
    height: 60px;
}

.alert-msg{
    color: #e24c4b;
    font-size: 20px;
    text-transform: capitalize;
    text-align: center;
    line-height: 30px;
    font-weight: 500;
}


Enter fullscreen mode Exit fullscreen mode
Output

Capture5

When alert-box has show class.

Great! we are done with signup page. Now let's make it functional. Add form.js to signup.html page.



<script src="js/form.js"></script>


Enter fullscreen mode Exit fullscreen mode

Form.js

Select all the elements we need.



const loader = document.querySelector('.loader');

// select inputs 
const submitBtn = document.querySelector('.submit-btn');
const name = document.querySelector('#name');
const email = document.querySelector('#email');
const password = document.querySelector('#password');
const number = document.querySelector('#number');
const tac = document.querySelector('#terms-and-cond');
const notification = document.querySelector('#notification');


Enter fullscreen mode Exit fullscreen mode

After done selecting all the elements. Add click event to submitBtn and inside that validate form using if else.



submitBtn.addEventListener('click', () => {
        if(name.value.length < 3){
            showAlert('name must be 3 letters long');
        } else if(!email.value.length){
            showAlert('enter your email');
        } else if(password.value.length < 8){
            showAlert('password should be 8 letters long');
        } else if(!number.value.length){
            showAlert('enter your phone number');
        } else if(!Number(number.value) || number.value.length < 10){
            showAlert('invalid number, please enter valid one');
        } else if(!tac.checked){
            showAlert('you must agree to our terms and conditions');
        } else{
            // submit form
        }
})


Enter fullscreen mode Exit fullscreen mode

In above code, how are we doing the validations. Well, I am using if else which basically means, if this is true run the following code, and if this is not that run the else code.

Let's understand the name validation.



if(name.value.length < 3){
    showAlert('name must be 3 letters long');
}


Enter fullscreen mode Exit fullscreen mode

if is checking for the condition, which is written inside the ( condition ).
name is our name element which we declared on the top the file.
value - since, name is an input field. It must have a value. Of course it can be empty. So name.value is just returning the value of the input field.
length is used to count how many letters are inside a string or how many elements are inside an array. So basically by using name.value.length we are checking for name's value length which is of course a whole number.
Once we got the length, which is a number, check for whether it is less than 3 or not.

So if the condition is true, then JS will run the code written inside the if block, which is



showAlert('name must be 3 letters long');


Enter fullscreen mode Exit fullscreen mode

That's how we are doing other fields validation also.

So we have to create showAlert(msg) function now.



// alert function
const showAlert = (msg) => {
    let alertBox = document.querySelector('.alert-box');
    let alertMsg = document.querySelector('.alert-msg');
    alertMsg.innerHTML = msg;
    alertBox.classList.add('show');
    setTimeout(() => {
        alertBox.classList.remove('show');
    }, 3000);
}


Enter fullscreen mode Exit fullscreen mode

Inside the above function, First I am just selecting the alert box related elements. After that, I am setting up the msg parameter as a innerHTML of alertMsg, which is of course the p element of alert-box. And then adding show class to alertBox. And using setTimeout to remove the show class after 3000 ms or 3 sec.

So, we are done with signup validation, so shall we submit the form now. To submit the form, make another function which will take path and data as an argument. Why make a separate function ? because we can then use the function for both signup page and login page.



// send data function
const sendData = (path, data) => {
    fetch(path, {
        method: 'post',
        headers: new Headers({'Content-Type': 'application/json'}),
        body: JSON.stringify(data)
    }).then((res) => res.json())
    .then(response => {
        processData(response);
    })
}


Enter fullscreen mode Exit fullscreen mode

So in the above code, I am using simple fetch method to make request. It is basically the fetch template. We'll make processData function later.

Send the form data to the backend now.



else{
    // submit form
    loader.style.display = 'block';
    sendData('/signup', {
        name: name.value,
        email: email.value,
        password: password.value,
        number: number.value,
        tac: tac.checked,
        notification: notification.checked,
        seller: false
    })
}


Enter fullscreen mode Exit fullscreen mode

This is after form validations

Make signup route inside server.js to handle form submission.

Sign Up - POST

Before making the route add this line at the top. This will enable form sharing. Otherwise you'll not able to receive form data.



app.use(express.json());


Enter fullscreen mode Exit fullscreen mode


app.post('/signup', (req, res) => {
    let { name, email, password, number, tac, notification } = req.body;

    // form validations
    if(name.length < 3){
        return res.json({'alert': 'name must be 3 letters long'});
    } else if(!email.length){
        return res.json({'alert': 'enter your email'});
    } else if(password.length < 8){
        return res.json({'alert': 'password should be 8 letters long'});
    } else if(!number.length){
        return res.json({'alert': 'enter your phone number'});
    } else if(!Number(number) || number.length < 10){
        return res.json({'alert': 'invalid number, please enter valid one'});
    } else if(!tac){
        return res.json({'alert': 'you must agree to our terms and conditions'});
    }       
})


Enter fullscreen mode Exit fullscreen mode

Here, first I am extracting the data from the request. So as we are sending form data from the front end. You can see I am using the same name in backend also.



let { name, email, password, number, tac, notification } = req.body;


Enter fullscreen mode Exit fullscreen mode

And after that, I am performing form validation, of course we have done it in front end, but it is good to have validation on back end also, because front end can be easily by pass.



if(name.length < 3){
    return res.json({'alert': 'name must be 3 letters long'});
} else if .....


Enter fullscreen mode Exit fullscreen mode

Notice I am not using value here, because the name here here is not input, its a string which we got from the front end. And in response I am sending JSON data. Which look like this.



JSON = {
   'key': 'value'
}


Enter fullscreen mode Exit fullscreen mode

It is similar to JS objects, but it is used to transfer data across the web.

Great. Now handle the JSON data, in front end.



const processData = (data) => {
    loader.style.display = null;
    if(data.alert){
        showAlert(data.alert);
    }
}


Enter fullscreen mode Exit fullscreen mode

Hide the loader first, Of course. After that check whether the received data contains alert key or not. If it contain, just use showAlert function to alert the user. Isn't it simple.

Ok so now let's store the user in database or firestore.

Storing user in firestore

Before writing more code, make sure you make firebase project and download the secret key file from the dashboard. You can refer this to download the key.

Once you the got key file. Move that into your project folder outside public folder.

Then init the firebase inside server.js.



// firebase admin setup
let serviceAccount = require("path of key file");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

let db = admin.firestore();


Enter fullscreen mode Exit fullscreen mode

After initializing the firebase. Inside signup POST route. Store the user in database after validations.



// store user in db
db.collection('users').doc(email).get()
.then(user => {
    if(user.exists){
        return res.json({'alert': 'email already exists'});
    } else{
        // encrypt the password before storing it.
        bcrypt.genSalt(10, (err, salt) => {
            bcrypt.hash(password, salt, (err, hash) => {
                req.body.password = hash;
                db.collection('users').doc(email).set(req.body)
                .then(data => {
                    res.json({
                        name: req.body.name,
                        email: req.body.email,
                        seller: req.body.seller,
                    })
                })
            })
        })
    }
})


Enter fullscreen mode Exit fullscreen mode

In firebase we have collections, which store same group of data. In this case we have users collection in our firstore. db.collection is used to access the collection. And once you are in collection, you can get the document by calling doc(docname) and after you found the doc, you can get it by calling get() method. And after you got the doc you can access it using then. That is this whole line mean.



db.collection('users').doc(email).get()
.then(...)


Enter fullscreen mode Exit fullscreen mode

The above code we are running to check, the email is already exists in our database or not. If it is we are send an alert. And if not, storing the user in the database.



if(user.exists){
    return res.json({'alert': 'email already exists'});
} else{
    // encrypt the password before storing it.
    bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(password, salt, (err, hash) => {
            req.body.password = hash;
            db.collection('users').doc(email).set(req.body)
            .then(data => {
                res.json({
                    name: req.body.name,
                    email: req.body.email,
                    seller: req.body.seller,
                })
            })
        })
    })
}


Enter fullscreen mode Exit fullscreen mode

bycrypt is the encrypt package, you can read its documentation if you want. But to hash the password, you can just code it. genSalt is how much salting you want to perform on a text. And hash is to covert the text into hash. And after that again, everything is same till doc(), but this time we don;t have to get() we have to set() which is pretty much self explanatory. And at the last, in response, I am sending users name, email and seller status to front end.

Now let's store it in front end.



const processData = (data) => {
    loader.style.display = null;
    if(data.alert){
        showAlert(data.alert);
    } else if(data.name){
        // create authToken
        data.authToken = generateToken(data.email);
        sessionStorage.user = JSON.stringify(data);
        location.replace('/');
    }
}


Enter fullscreen mode Exit fullscreen mode

Use session storage to store the user data inside session. But we can;t simply use users email to validated its authenticity, we need something at least which we can validate. For that generate an auth token for the user. This will be not advance but yeah I thought to make it one.

First add token.js file to signup.html.



<script src="js/token.js"></script>


Enter fullscreen mode Exit fullscreen mode

after that create generateToken function.

Token.js


let char = `123abcde.fmnopqlABCDE@FJKLMNOPQRSTUVWXYZ456789stuvwxyz0!#$%&ijkrgh'*+-/=?^_${'`'}{|}~`;

const generateToken = (key) => {
    let token = '';
    for(let i = 0; i < key.length; i++){
        let index = char.indexOf(key[i]) || char.length / 2;
        let randomIndex = Math.floor(Math.random() * index);
        token += char[randomIndex] + char[index - randomIndex];
    }
    return token;
}
```
This above code, it will simply generate a text of whose sets of 2 letters index number add to give the original text index from the char string. It is simple but complex also. It okay, to copy it if you want.

Now we also want a function to validate the token.
```JS
const compareToken = (token, key) => {
    let string = '';
    for(let i = 0; i < token.length; i=i+2){
        let index1 = char.indexOf(token[i]);
        let index2 = char.indexOf(token[i+1]);
        string += char[index1 + index2];
    }
    if(string === key){
        return true;
    }
    return false;
}
```
Great! we are almost done with the page. Till now we have successfully stored the used in session, so let's validate it.

######form.js
```Js
// redirect to home page if user logged in
window.onload = () => {
    if(sessionStorage.user){
        user = JSON.parse(sessionStorage.user);
        if(compareToken(user.authToken, user.email)){
            location.replace('/');
        }
    }
}
```
we are adding load event to window, which is checking is user in session or not. If it is in session, we are validating the auth token. And it its legit. I am redirecting user to home page. As he/she really don't need sign up.

Great! Our sign up page is done. Since the blog is being too much lengthy. I think that enough for today. But yes, in the second part, I have made login page and seller's dashboard. Which I made in the tutorial. So if you want to make all the features, of course you want. [Watch the tutorial](https://www.youtube.com/watch?v=yYSfOe0QBOk)

I hope you understood each and everything. If you have doubt or I missed something let me know in the comments.

#Articles you may find Useful

1. [Best CSS Effect](https://dev.to/kunaal438/css-the-best-css-effects-of-all-time-most-underrated-web-ux-2chj)
2. [Infinte CSS loader](https://dev.to/kunaal438/quick-css-make-infinity-loading-animation-for-your-next-website-187k)
3. [Disney+ Clone](https://dev.to/kunaal438/how-to-create-disney-plus-clone-for-beginner-in-2021-html-css-js-m3p)
4. [Youtube API - Youtube Clone](https://dev.to/kunaal438/create-working-youtube-clone-with-search-box-youtube-api-2a6e)
5. [TMDB - Netflix Clone](https://dev.to/kunaal438/how-to-create-netflix-clone-netflix-clone-with-hmtl-css-js-989)

I really appreciate if you can subscribe my youtube channel. I create awesome web contents.

<a href="https://www.youtube.com/c/modern_web?sub_confirmation=1"><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jeitpawvax07cx7r9yoa.png"></a>

[Source Code](https://www.patreon.com/posts/56549627), [Donate me on Paypal](https://paypal.me/modernwebchannel)

Your donation really motivates me to do more amazing tutorials like this. Support me on [patreon](https://www.patreon.com/modernweb), [Buy me a coffee](https://ko-fi.com/modernweb), [Donate me on paypal](https://paypal.me/modernwebchannel)

Thanks For reading. 
Enter fullscreen mode Exit fullscreen mode

Top comments (22)

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Take it from somebody whos job it is to login an register and design user flows, I would recommend that you don't try to handle your own security or store users etc, get an IDP, even a free one like keycloak and use that. Your wallet / mind will thank you.

Collapse
 
fridaycandours profile image
Friday candour

What is idp

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀
Collapse
 
mcwolfmm profile image
mcwolfmm

something I did not like generating the token. and this is a critical moment.

if you plan to implement a real project with such an architecture, it is appropriate to consider working with JWT instead of using the current implementation.

Collapse
 
themodernweb profile image
Modern Web

Yeah I agree.I was just experimenting with token validation. Of course JWT is far or is the best solution for authentication☺️

Collapse
 
mnparekh83 profile image
mnparekh83

Hello - After following your 1st video tutorial, the 2nd part gives me error in npm start -
Image description

Kindly let me know.

Collapse
 
themodernweb profile image
Modern Web

Hey, make sure in your package.json file you have "start" : "nodemon server.js" then it should run.

Collapse
 
rumi1111 profile image
Rumi1111

Hello!

The Node.js thing does not work. Is there a way to implement it in Windows at all?

Or, more directly: I need a webshop with mysqli database. I am not familiar with what you use here. Is there a way to use your code directly with a "normal" database?

Thank you!

Collapse
 
rumi1111 profile image
Rumi1111

To be more concrete:

You write at the start of this part "Start with server, open the previous code folder in terminal or cmd prompt."

I don't understand that at all. What is "the previous code folder"? How can I open a whole folder in Terminal? How should I "start with server"? What server? Etc.

It seems that there is quite some important information missing.

Collapse
 
rumi1111 profile image
Rumi1111

It works now, at least locally. I still don't know how I get this online, but I will persist.
I hope I get it working! It's great that you offer this, thank you!

Collapse
 
themodernweb profile image
Modern Web

All codes are same, I wrote the blog in exact same order as the video. The only thing is, video it was line by line, and because of blog, I making it element by element, all the codes are same and you can follow any one.

Collapse
 
mcwolfmm profile image
mcwolfmm

and a little more criticism :) nowhere do you handle callback errors. this is a very bad practice.
be sure to check for an error first and only then continue processing the task. also, you can add another class of messages (besides alert) for example error to return in case of error

Collapse
 
themodernweb profile image
Modern Web

Thanks to pointing out the mistakes I'll make sure to handle error first.☺️

Collapse
 
craftogrammer profile image
Rahul

Keep it up :)

Collapse
 
vashuai profile image
VashuAI

// send data function
const sendData = (path, data) => {
fetch(path, {
method: 'post',
headers: new Headers({'Content-Type': 'application/json'}),
body: JSON.stringify(data)
}).then((res) => res.json()) //error in this line
.then(response => {
processData(response);
})

error-Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at form.js:62:26

Collapse
 
arnob15490 profile image
arnob15490

getting this error on the login page while the email and password is correct.
form.js:82 Uncaught (in promise) ReferenceError: generateToken is not defined
at processData (form.js:82:5)
at form.js:72:9

Collapse
 
ribosomatic profile image
Jesus Liñan

Excelente! Muy buen post, eres muy hábil en el desarrollo web. Thanks for share your job.

Some comments have been hidden by the post's author - find out more