August 24, 2024

ExpressJS MVC CRUD Application + MongoDB + EJS

In the post ExpressJS MVC CRUD Application + MySQL + EJS we saw how to create an Express MVC CRUD application with MySQL as database and EJS as template engine. In this tutorial we'll see how to create Express MVC CRUD application with MongoDB as database and EJS as template engine. Bootstrap 5 is used for styling.

Note that this is not an Express REST application, it is an MVC application using HTML form which uses methods POST and GET.

Prerequisite

To create this application NodeJS, ExpressJS and MongoDB should be installed in the system.

Installing MongoDB driver for NodeJS

You can install the official MongoDB driver for NodeJS by running the following command from the root directory of your application.

npm install mongodb

Creating DB Connection

In the mongodb driver there is a MongoClient class which is used to connect to MongoDB server. DB connected is 'knpcode', there is no need to have that DB instance already created. MongoDB will create the DB with the given name if it doesn't exist.

util\dbconfig.js

const {MongoClient} = require('mongodb');

let dbconnection;
const url = 'mongodb://127.0.0.1:27017';
const client = new MongoClient(url);
const mongodbConnect = async () => {
  console.log('mongodbConnect');
  try{
    await client.connect();
    console.log('Connected successfully to server');
    dbconnection = client.db('knpcode');
  }catch(error){
    console.log(error);
    throw error;
  }
}

const getDBConnection = async() => {
  if(dbconnection){
    console.log('in if getDBConnection')
    return dbconnection;
  }else{
    console.log('in else getDBConnection')
    await mongodbConnect();
    return dbconnection;
  }
}

exports.getDBConnection = getDBConnection;

In the file there are two functions mongodbConnect and getDBConnection. Using getDBconnection function it is checked if the connection already exists then the connection is reused only if it is not yet created mongodbConnect function is called to create DB connection. In other classes where MongoDB connection is needed, getDBConnection function will be called, that's why it is exported.

Express MVC CRUD example with MongoDB

In this example User is used as Model class.

models\user.js

module.exports = class User {
  constructor(firstName, lastName, userType, startDate){
    // MongoDB adds _id that will be used as id
    //this.id = id; 
    this.firstName = firstName;
    this.lastName = lastName;
    this.userType = userType;
    this.startDate = startDate;
  }
}

Table in MySQL

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `user_type` varchar(15) NOT NULL,
  `start_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb3

In the example we'll create an application where root route ('/) should display all user data along with Edit and Delete button. On clicking Edit a modal with the data for the corresponding user should open and clicking Delete button should delete that particular User data.

A link to add user is also provided, clicking that link should display a form to enter user data.

Express.js MVC example with MongoDB

Route definition

When creating an Express MVC application, convention is to keep route class light. Logic for the routes is kept in the Controller class. Route definition should refer the function in Controller.

routes\user.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/user');

//to show user data
router.get('/', userController.getUsers)
// show user form
router.get('/user', userController.showUserForm)
// Insert user and edit user
router.post('/adduser', userController.saveUser)
// delete user
router.post('/deleteuser', userController.deleteUser)

module.exports = router;

As you can see Controller is imported here and the function in the Controller are called for processing when corresponding paths are accessed.

Controller

controllers\user.js

const getDBConnection = require('../util/dbconfig').getDBConnection;
const ObjectId = require('mongodb').ObjectId;
const User = require('../models/user')

exports.getUsers = async (req, res) => {
  console.log('In getUsers user Controller');
  try{
    const dbConnection = await getDBConnection();
    const cursor = dbConnection.collection('user').find().sort({firstName:1});
    // Retreive all the results in an array
    const results = await cursor.toArray();
    res.render('user/displayUser', {
      users: results,
      pageTitle: 'Users Data',
      userType: ['Silver', 'Gold', 'Platinum']
    })
  }catch(err){
    console.log(err);
  }
}

exports.showUserForm = (req, res) => {
  console.log('In showUserForm user Controller');
  res.render('user/userform', {
    pageTitle: 'User Form',
    userType: ['Silver', 'Gold', 'Platinum']
  })
}

exports.saveUser = async (req, res) => {
  console.log('In saveUser user Controller');
  const id = req.body.id;
  const firstName = req.body.firstName;
  const lastName = req.body.lastName;
  const userType = req.body.userType;
  const startDate = req.body.startDate;
  const user = new User(firstName, lastName, userType, startDate);
  try{
    const dbConnection = await getDBConnection();
    // id present so update
    if(id != undefined){
      await dbConnection.collection('user').updateOne(
        {_id: ObjectId.createFromHexString(id)},
        {
          $set: user
        }
      );
    }else{ // no id means insert
      await dbConnection.collection('user').insertOne(user);
    }
  }catch(err){
    console.log(err);
  }
  console.log('redirecting....')
  res.redirect('/');
}

exports.deleteUser = async (req, res) => {
  console.log('In deleteUser user Controller');
  const id = req.body.deleteid;
  console.log('UserID ', id)

  try{
    const dbConnection = await getDBConnection();
    console.log('.... Waiting for connection');
    await dbConnection.collection('user').deleteOne({
      _id: ObjectId.createFromHexString(id)
    })
    res.redirect('/');
  }catch(err){
    console.log(err);
  }
}

Important points about the logic here-

  1. In the beginning there are imports, one is for getDBConnection function in DBConfig.js, ObjectId is class representation of the BSON ObjectId type this is needed to create _id in correct format for storing in MongoDB. User model is also imported to create User object using the passed user data.
  2. Note that the collection with in the DB is ‘user’.
  3. First function getUsers() is used to get all users from DB. To query all documents, collection’s find() method is used. User data is also sorted in ascending order on the basis of firstName.
  4. Note that find() returns a cursor to the documents that match the query criteria. Using Cursor’s toArray() function the results are retrieved in an array.
    This array (containing User objects) is passed to the EJS template. User type is a dropdown, options for that are also passed.
  5. showUserForm() function is used to display a form that is used to enter and save new user.
  6. saveUser() function is used for both inserting and updating data. If userID is present that means an update otherwise insert. Form data is received by accessing request.body method. body-parser module is used to parse form data sent by a user by submitting a web form.
  7. For updating collection’s updateOne() method is used where the selection criteria is based on matching IDs. Complete User object (containing modified data for the properties) is passed for updating data.
  8. For inserting collection’s insertOne() method is used where User object is passed.
  9. Once updation or insertion is done, redirection to root route ‘/’ is done which triggers the execution of getUsers() function in the controller.
  10. deleteUser() function is used to delete user from DB. User id is passed from the view in a hidden field which is accessed in the deleteUser() function to create Delete query. Collection’s deleteOne() method is used for deleting user, deletion criteria is based on matching IDs.

Views

Views are created using EJS template engine.

views\user\displayUser.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= pageTitle %></title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script>
      function editData(user){
      // Converting to object 
      const temp = JSON.parse(user);
      // setting values in modal box form - EDIT
      document.getElementById("id").value = temp._id;
      document.getElementById("firstName").value = temp.firstName;
      document.getElementById("lastName").value = temp.lastName;
      document.getElementById("startDate").value = temp.startDate;
      document.getElementById("userType").value = temp.userType;
    }

    function deleteData(id){
      document.getElementById("deleteid").value = id;
    }
    </script>
  </head>
  <body>
    <div class="container">
    <a href="/user">Add User</a>
    <h2 class="text-info d-flex justify-content-center mb-3">User Data</h2>
   
    <table  class="table table-striped table-bordered table-sm">
      <thead>
      <tr>
        <th>id</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>User Type</th>
        <th>Start Date</th>
      </tr>
      </thead>
      <% for(let user of users) { %>
      <tr> 
        <td><%= user._id%></td>
        <td><%= user.firstName%></td>
        <td><%= user.lastName%></td>
        <td><%= user.userType%></td>
        <td><%= user.startDate%></td>
        <td class="text-center"><button type="button" class="btn btn-primary me-3" data-bs-toggle="modal" data-bs-target="#editModal" onclick="editData('<%= JSON.stringify(user)%>')">
          Edit
        </button>
        <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal" onclick="deleteData('<%= user._id%>')">
          Delete
        </button>
      </td>
      </tr>
      <%}%>
    </table>
    <!-- Edit and Delete Modal-->
    <div class="modal fade" id="editModal" tabindex="-1">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="editModalLabel">Modify User</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body">
            <form action="/adduser" method="POST">
              <div class="mb-3 row">
                <label for="id" class="form-label" ></label>User ID</label>
                <input type="text"  class="form-control" name="id" id="id" readonly/>
              </div>
              <div class="mb-3 row">
                  <label for="firstName" class="form-label" ></label>Enter First Name</label>
                  <input type="text"  class="form-control" name="firstName" id="firstName" />
              </div>
              <div class="mb-3 row">
                  <label for="lastName" class="form-label">Enter Last Name</label>
                  <input type="text"  class="form-control" name="lastName" id="lastName" />
              </div>
              <div class="mb-3 row">
                  <label for="userType" class="form-label">User Type</label>
                  <select class="form-select" name="userType" id="userType">
                      <% for(let type of userType) {%>
                          <option value="<%=type%>"><%=type%></option>
                      <% } %>
                  </select>
              </div>
              <div class="mb-3 row">
                  <label for="startDate" class="form-label">Enter Start Date</label>
                  <input type="date"  class="form-control" name="startDate" id="startDate" />
              </div>

          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
            <button type="submit" class="btn btn-primary">Save changes</button>
          </div>
        </form>
        </div>
      </div>
    </div>
  </div>
  <div class="modal fade" id="deleteModal" tabindex="-1">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="deleteModalLabel">Delete User</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form action="/deleteuser" method="POST">
              <p>Do you really want to delete the user</p>
              <input type="hidden"  class="form-control" name="deleteid" id="deleteid"/>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
          <button type="submit" class="btn btn-primary">OK</button>
        </div>
      </form>
      </div>
    </div>
  </div>
</div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous">
    
  </script>
  </body>
</html>

Important points about this view-

  1. Bootstrap is used for styling, css for the same is imported from the CDN.
  2. This same view code is used for showing user data in a table, modifying existing user and deleting user.
  3. Clicking Edit or Delete button opens a Modal box. In case of edit, existing user data is pre-populated, for that JavaScript function editData() is used. In case of delete id is populated in a hidden field, for that JavaScript function deleteData() is used.

views\user\userform.ejs

This is the EJS template for showing user form, where new user data is entered that can be then saved in DB.

<!DOCTYPE html>
<html>
  <head>
    <title><%= pageTitle %></title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  </head>
  <body>
    <div class="container">
      <form action="/adduser" method="POST">
        <div class="mb-3 row">
          <label for="firstName" class="form-label" ></label>Enter First Name</label>
          <input type="text"  class="form-control" name="firstName" id="firstName" />
        </div>
        <div class="mb-3 row">
          <label for="lastName" class="form-label">Enter Last Name</label>
          <input type="text"  class="form-control" name="lastName" id="lastName" />
        </div>
        <div class="mb-3 row">
          <label for="userType" class="form-label">User Type</label>
          <select class="form-select" name="userType" id="userType">
            <% for(let type of userType) {%>
                <option value="<%=type%>"><%=type%></option>
            <% } %>
          </select>
        </div>
        <div class="mb-3 row">
          <label for="startDate" class="form-label">Enter Start Date</label>
          <input type="date"  class="form-control" name="startDate" id="startDate" />
        </div>                
        <button type="submit" class="btn btn-primary">Save</button>
      </form>
    </div>
  </body>
</html>

App.js

This JS file acts as a starting point.

const express = require('express');
const app = express();
const bodyParser = require('body-parser'); 
const port = 3000;
app.set('view engine', 'ejs');
app.set('views', 'views');

const userRoutes = require('./routes/user');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(userRoutes);

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

With all the changes done you can run the application by using the command.

node app.js

On accessing http://localhost:3000/ all the users are displayed.

Edit user data

ExpressJS MVC MySQL

Delete User

Add user

That's all for the topic ExpressJS MVC CRUD Application + MongoDB + EJS. If something is missing or you have something to share about the topic please write a comment.


You may also like

August 20, 2024

ExpressJS MVC CRUD Application + MySQL + EJS

In this tutorial we'll see how to create an Express MVC CRUD application with MySQL as database and EJS as template engine. Bootstrap 5 is used for styling.

Note that this is not an Express REST application, it is an MVC application using HTML form which uses methods POST and GET.

Express MVC CRUD example

In this example User is used as Model (though direct use of MySQL doesn't have much use of Model class, if used with Sequelize then Model class has more usage).

models\user.js

module.exports = class User {
  constructor(id, firstName, lastName, userType, startDate){
    this.is = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.userType = userType;
    this.startDate = startDate;
  }
}

Table in MySQL

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `user_type` varchar(15) NOT NULL,
  `start_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb3

Aim is to create an application where root route ('/) should display all user data along with Edit and Delete button. On clicking Edit a modal with the data for the corresponding user should open and clicking Delete button should delete that particular User data.

A link to add user is also provided, clicking that link should display a form to enter user data.

Express.js MVC

Route definition

When creating an Express MVC application, convention is to keep route class light. Logic for the routes is kept in the Controller class. Route definition should refer the function in Controller.

routes\user.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/user');

//to show user data
router.get('/', userController.getUsers)
// show user form
router.get('/user', userController.showUserForm)
// Insert user and edit user
router.post('/adduser', userController.saveUser)
// delete user
router.post('/deleteuser', userController.deleteUser)

module.exports = router;

As you can see Controller is imported here and the function in the Controller are called for processing when corresponding paths are accessed.

Controller

controllers\user.js

const pool = require('../util/dbconfig');
const date = require('date-and-time') 

exports.getUsers = async (req, res) => {
    console.log('In getUsers user Controller');
    const sql = "SELECT * FROM USER";
    try{
      const [result, fields] = await pool.query(sql);
      res.render('user/displayUser', {
        users: result,
        pageTitle: 'Users Data',
        date: date,
        userType: ['Silver', 'Gold', 'Platinum']
      })
      console.log(result);
    }catch(err){
      console.log(err);
    }
}

exports.showUserForm = (req, res) => {
    console.log('In showUserForm user Controller');
    res.render('user/userform', {
        pageTitle: 'User Form',
        userType: ['Silver', 'Gold', 'Platinum']
    })
}

exports.saveUser = async (req, res) => {
    console.log('In saveUser user Controller');
    const id = req.body.id;
    const firstName = req.body.firstName;
    const lastName = req.body.lastName;
    const userType = req.body.userType;
    const startDate = req.body.startDate;
    let sql = '';
    let values = [];
    // Works as update
    if(id != undefined){
      sql = `UPDATE USER SET first_name = ?, last_name = ?, user_type = ?, 
      start_date = ? WHERE ID = ?`;
      values = [firstName, lastName, userType, startDate, id];
    }else{ // Works as insert
      sql = `INSERT INTO USER (first_name, last_name, user_type, start_date) 
      values (?, ?, ?, ?)`;
      values = [firstName, lastName, userType, startDate];
    }
    try{
      const conn = await pool.getConnection();
      const [result, fields] = await conn.execute(sql, values);
      console.log(result);
      console.log(fields);
      conn.release();
    }catch(err){
      console.log(err);
    }
    res.redirect('/');
}

exports.deleteUser = async (req, res) => {
  console.log('In deleteUser user Controller');
  const id = req.body.deleteid;
  const sql = "DELETE FROM user WHERE id = ?";
  const values = [id];
  try{
    const [result, fields] = await pool.query(sql, values);
    console.log(result);
    res.redirect('/');
  }catch(err){
    console.log(err);
  }
}

Important points about the logic here-

  1. There are two imports, one is for DB configuration and another is date-time library which is used for formatting dates.
  2. First function getUsers() is used to get all users from DB. Fetched users are passed to the EJS template. User type is a dropdown, options for that are also passed.
  3. showUserForm() function is used to display a form that is used to enter and save new user.
  4. saveUser() function is used for both inserting and updating data. If userID is present that means an update otherwise insert. Form data is received by accessing request.body method. body-parser module is used to parse form data sent by a user by submitting a web form.
  5. deleteUser() function is used to delete user from DB. User id is passed from the view in a hidden field which is accessed in the deleteUser() function to create Delete query.

DB Configuration

Using mysql2 package you can also create a connection pool. For that createPool() method is used which needs few configuration values. DB pool is created in a separate file.

util\dbconfig.js

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'admin',
  database: 'knpcode', //Schema Name
  waitForConnections: true, // this is default anyway
  connectionLimit: 10, // this is default anyway
});

module.exports = pool;

Views

Views are created using EJS template engine.

views\user\displayUser.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= pageTitle %></title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script>
      function editData(user){
      // Converting to object 
      const temp = JSON.parse(user);
      // setting values in modal box form - EDIT
      document.getElementById("id").value = temp.id;
      document.getElementById("firstName").value = temp.first_name;
      document.getElementById("lastName").value = temp.last_name;
      document.getElementById("startDate").value = temp.start_date;
      document.getElementById("userType").value = temp.user_type;
    }

    function deleteData(id){
      document.getElementById("deleteid").value = id;
    }
    </script>
  </head>
  <body>
    <div class="container">
    <a href="/user">Add User</a>
    <h2 class="text-info d-flex justify-content-center mb-3">User Data</h2>
   
    <table  class="table table-striped table-bordered table-sm">
      <thead>
      <tr>
        <th>id</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>User Type</th>
        <th>Start Date</th>
      </tr>
      </thead>
      <% for(let user of users) { %>
      <tr> 
        <td><%= user.id%></td>
        <td><%= user.first_name%></td>
        <td><%= user.last_name%></td>
        <td><%= user.user_type%></td>
        <td><%= date.format(user.start_date,'YYYY-MM-DD');%></td>
        <!-- Changing user.start_date to a formatted date -->
        <% user.start_date = date.format(user.start_date,'YYYY-MM-DD');%>
        <td class="text-center"><button type="button" class="btn btn-primary me-3" data-bs-toggle="modal" data-bs-target="#editModal" onclick="editData('<%= JSON.stringify(user)%>')">
          Edit
        </button>
        <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal" onclick="deleteData('<%= user.id%>')">
          Delete
        </button>
      </td>
      </tr>
      <%}%>
    </table>
    <!-- Edit and Delete Modal-->
    <div class="modal fade" id="editModal" tabindex="-1">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="editModalLabel">Modify User</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body">
            <form action="/adduser" method="POST">
              <div class="mb-3 row">
                <label for="id" class="form-label" ></label>User ID</label>
                <input type="text"  class="form-control" name="id" id="id" readonly/>
              </div>
              <div class="mb-3 row">
                  <label for="firstName" class="form-label" ></label>Enter First Name</label>
                  <input type="text"  class="form-control" name="firstName" id="firstName" />
              </div>
              <div class="mb-3 row">
                  <label for="lastName" class="form-label">Enter Last Name</label>
                  <input type="text"  class="form-control" name="lastName" id="lastName" />
              </div>
              <div class="mb-3 row">
                  <label for="userType" class="form-label">User Type</label>
                  <select class="form-select" name="userType" id="userType">
                      <% for(let type of userType) {%>
                          <option value="<%=type%>"><%=type%></option>
                      <% } %>
                  </select>
              </div>
              <div class="mb-3 row">
                  <label for="startDate" class="form-label">Enter Start Date</label>
                  <input type="date"  class="form-control" name="startDate" id="startDate" />
              </div>

          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
            <button type="submit" class="btn btn-primary">Save changes</button>
          </div>
        </form>
        </div>
      </div>
    </div>
  </div>
  <div class="modal fade" id="deleteModal" tabindex="-1">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="deleteModalLabel">Delete User</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form action="/deleteuser" method="POST">
              <p>Do you really want to delete the user</p>
              <input type="hidden"  class="form-control" name="deleteid" id="deleteid"/>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
          <button type="submit" class="btn btn-primary">OK</button>
        </div>
      </form>
      </div>
    </div>
  </div>
</div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous">
    
  </script>
  </body>
</html>

Important points about this view-

  1. Bootstrap is used for styling.
  2. This same view code is used for showing user data in a table, modifying existing user and deleting user.
  3. Clicking Edit or Delete button opens a Modal box. In case of edit existing user data is pre-populated, for that JavaScript function editData() is used. In case of delete id is populated in a hidden field.

views\user\userform.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= pageTitle %></title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  </head>
  <body>
    <div class="container">
      <form action="/adduser" method="POST">
        <div class="mb-3 row">
          <label for="firstName" class="form-label" ></label>Enter First Name</label>
          <input type="text" class="form-control" name="firstName" id="firstName" />
        </div>
        <div class="mb-3 row">
          <label for="lastName" class="form-label">Enter Last Name</label>
          <input type="text"  class="form-control" name="lastName" id="lastName" />
        </div>
        <div class="mb-3 row">
          <label for="userType" class="form-label">User Type</label>
          <select class="form-select" name="userType" id="userType">
            <% for(let type of userType) {%>
              <option value="<%=type%>"><%=type%></option>
            <% } %>
          </select>
        </div>
        <div class="mb-3 row">
          <label for="startDate" class="form-label">Enter Start Date</label>
          <input type="date" class="form-control" name="startDate" id="startDate" />
        </div>
        <button type="submit" class="btn btn-primary">Save</button>
      </form>
    </div>
  </body>
</html>

App.js

This JS file acts as a starting point.

const express = require('express');
const app = express();
const bodyParser = require('body-parser'); 
const port = 3000;
app.set('view engine', 'ejs');
app.set('views', 'views');

const userRoutes = require('./routes/user');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(userRoutes);

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

With all the changes done you can run the application by using the command.

node app.js

On accessing http://localhost:3000/ all the users are displayed.

Edit user data

ExpressJS MVC MySQL

Delete User

Add user

That's all for the topic ExpressJS MVC CRUD Application + MySQL + EJS. If something is missing or you have something to share about the topic please write a comment.


You may also like

July 17, 2024

ExpressJS: EJS Partials

In EJS template engine there is no support for inheritance like you have in Pug through block and extends keywords. So, you can't create layouts using blocks in EJS as you can in Pug template inheritance. But the functionality to include smaller files to create a larger file is very important in order to promote code reuse, making maintenance of code easier as you don't need to change code in every page. In EJS you can have the similar functionality by including smaller files to create a bigger file.

In EJS template terminology these are known as EJS partials which are reusable components that can be combined to create a larger view.

Syntax for include in EJS

You can include partial files by using the following command.

<%- include( PARTIAL_FILE ) %> 

Here PARTIAL_FILE is the file path relative to the template where partial file is included.

Note the use of <%- %> tags which allows you to output unescaped HTML content. When you use this tag <%= %>, if you have any HTML it will be rendered as string (HTML code itself) not as HTML display.

Express.js - EJS partials example

In this example we'll create an ExpressJS app where pages will have a navigation menu and footer. Since this menu and footer is common for all the pages so we can create partial files with navigation menu and footer code which can then be included in other pages which are going to be home.ejs and user.ejs.

There are also stylesheets main.css (for styling menu and footer) and user.css (for styling table which shows user data) where user.css is specific to user.ejs template.

public\css\main.css

.header{
    width: 100%;
    height: 3rem;
    text-align: center;
    background-color: #6d70a8;
    padding: 1 2rem;
    margin-bottom: 1rem;
}

.nav{
    height: 100%;
    display: flex;
    align-items: center;
}

.nav-menu{
    list-style: none;
    display: flex;
}

.nav-menu-item{
    margin: 0 2rem;
    padding: 0;
}

.nav-menu-item a{
    text-decoration: none;
    color: white;
}

.footer { 
    text-align: center;
 
    position: absolute; 
    width: 100%;
    bottom: 0; 
    left: 0; 
    background-color: #6d70a8;
    color: white;
}

public\css\user.css

table, td, th {
  border: 1px solid;
}
table {
  width: 80%;
  border-collapse: collapse;
}

Within views folder let's create a sub-folder named partials to save partial files.

views\partials\navigation.ejs

<header class="header">
  <nav class="nav">
    <ul class="nav-menu">
      <li class="nav-menu-item">
        <a href="/">Home</a></li>
      <li class="nav-menu-item">
        <a href="/user">User</a>
      </li>
    </ul>
  </nav>
</header>

views\partials\footer.ejs

<footer class="footer">
  <p>&copy; 2024 Company Name. All rights reserved.</p>
</footer>

views\home.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/css/main.css">
  </head>
  <body>
    <%- include('partials/navigation.ejs') %>
    <h1>EJS Demo</h1>
    <p>Welcome <strong><%= userName %></strong></p>
    <%- include('partials/footer.ejs') %>
  </body>
</html>

Since navigation and footer are needed so those files are included at the appropriate location within the body.

views\user.ejs

This template is used to show user data by iterating users array. In this file also navigation and footer are needed so those files are included.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= pageTitle%></title>
    <link rel="stylesheet" href="/css/user.css">
    <link rel="stylesheet" href="/css/main.css">
  </head>
  <body> 
    <%- include('partials/navigation.ejs') %>
    <h2>Using for-of to loop array</h2>
    <table>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Gender</th>
      </tr>
      <% for(let user of users) {%>
      <tr> 
        <td><%= user.name%></td>
        <td><%= user.age%></td>
        <td><%= user.gender%></td>
      </tr>
      <%}%>
    </table>
    <%- include('partials/footer.ejs') %>
  </body>
</html>

app.js

const express = require('express');
const app = express();
const port = 3000;
const path = require('path');

// To serve static files like CSS
app.use(express.static(path.join(__dirname, 'public')));

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// Hardcoded user data
const users = [{name:'Reene', age: 28, gender:'F'}, 
    {name:'Badal', age: 34, gender:'M'},
    {name:'Vidyut', age: 25, gender:'M'},
    {name:'Dhriti', age: 29, gender:'F'}
]

// for home page
app.get('/', (req, res) => {
    res.render('home', {pageTitle:'HomePage', userName:'TestUser'});
})

// for user page
app.get('/user', (req, res) => {
    res.render('user', {users: users, pageTitle: 'User Page'});
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

On running this file- node app.js and then accessing the URL- http://localhost:3000/

EJS Partials

Clicking on user menu option.

Express.js EJS partials

That's all for the topic ExpressJS: EJS Partials. If something is missing or you have something to share about the topic please write a comment.


You may also like

ExpressJS: EJS Template Iteration Examples

In the post ExpressJS: EJS Template Engine Integration With Examples we have seen how you can use EJS template with Express.js app. In this post we'll see how to iterate over an array or object in EJS template.

Iteration in EJS

EJS template uses JavaScript code with in the scriptlet tags <% %>. Which means you can use the same loops which are used in JavaScript to iterate an array or object in EJS.

Loops that can be used are-

  1. Normal for loop
  2. While loop
  3. for-of to iterate array
  4. forEach to loop array
  5. for-in to iterate an object

Express.js - EJS iteration example

Let's assume we have an array of User objects which we want to iterate and display in our view named 'user.ejs' which is created as a EJS template.

app.js

const express = require('express');

const app = express();

const port = 3000;
const path = require('path');

// To serve static files like CSS
app.use(express.static(path.join(__dirname, 'public')));

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// Hardcoded user data
const users = [{name:'Reene', age: 28, gender:'F'}, 
    {name:'Badal', age: 34, gender:'M'},
    {name:'Vidyut', age: 25, gender:'M'},
    {name:'Dhriti', age: 29, gender:'F'}
]

app.get('/user', (req, res) => {
    res.render('user', {users: users, pageTitle: 'User Page'});
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

As you can see in the code there is an array named users that contains user objects. There is also a mapping '/user' which renders the user.ejs template and values for variables users and pageTitle in the view are also passed.

public\css\user.css

CSS used for styling table.

table, td, th {
  border: 1px solid;
}
table {
  width: 80%;
  border-collapse: collapse;
}

views\user.ejs

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= pageTitle%></title>
    <link rel="stylesheet" href="/css/user.css">
  </head>
  <body> 
    <h2>Using while loop</h2>
    <table>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Gender</th>
      </tr>
      <% 
      let i = 0;
      while(i < users.length) {%>
      <tr> 
        <td><%= users[i].name%></td>
        <td><%= users[i].age%></td>
        <td><%= users[i].gender%></td>
      </tr>
      <% i++;
      }%>
    </table>
    <h2>Using normal for loop</h2>
    <table>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Gender</th>
      </tr>
      <% for(let i= 0; i < users.length; i++) {%>
      <tr> 
        <td><%= users[i].name%></td>
        <td><%= users[i].age%></td>
        <td><%= users[i].gender%></td>
      </tr>
      <%}%>
    </table>
    <h2>Using for-of to loop array</h2>
    <table>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Gender</th>
      </tr>
      <% for(let user of users) {%>
      <tr> 
        <td><%= user.name%></td>
        <td><%= user.age%></td>
        <td><%= user.gender%></td>
      </tr>
      <%}%>
    </table>
    <h2>Using forEach to loop array</h2>
    <table>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Gender</th>
      </tr>
      <% users.forEach(user => {%>
      <tr> 
        <td><%= user.name%></td>
        <td><%= user.age%></td>
        <td><%= user.gender%></td>
      </tr>
      <%});%>
    </table>
  </body>
</html>

In the code all of the mentioned loops are used to show iteration of an array in EJS template. For each loop, JavaScript code is enclosed with in the scriptlet tags.

So, the loop and starting curly bracket is in a scriptlet tag

<% for(let user of users) {%>

Then the closing curly bracket is in a scriptlet tag

<%}%>

When you run the file- node app.js and then access the URL- http://localhost:3000/user

EJS Template Iteration Example

EJS object iteration ExpreeeJS example

If you want to iterate an object and extract the (key, value) pairs then you can use for-in loop.

app.js

const express = require('express');

const app = express();

const port = 3000;
const path = require('path');

// To serve static files like CSS
app.use(express.static(path.join(__dirname, 'public')));

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// Hardcoded user data
const user = {name:'Reene', age: 28, gender:'F'};

app.get('/user', (req, res) => {
    res.render('user', {user: user, pageTitle: 'User Page'});
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

views\user.ejs

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= pageTitle%></title>
    <link rel="stylesheet" href="/css/user.css">
  </head>
  <body> 
    <h2>Using for-in loop</h2>
    <ul>
      <% for(let key in user) {%>
        <li><%=key.charAt(0).toUpperCase() + key.substr(1).toLowerCase() + ': ' + user[key]%></li>
      <%}%>
    </ul>
  </body>
</html>

When you run the file- node app.js and access the URL- http://localhost:3000/user

you'll get the result as-

Using for-in loop

    Name: Reene
    Age: 28
    Gender: F

That's all for the topic ExpressJS: EJS Template Iteration Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

July 16, 2024

ExpressJS: EJS Template Engine Integration With Examples

The post ExpressJS Template Engines Introduction gives an introduction to template engines that can be used with Express.js. In this article we'll see how to use EJS template engine.

Installing EJS

You can install EJS by using the following command.

npm install ejs

Settings in Express.js for using EJS

You need to configure EJS as the view engine in Express.js app.

app.set('views', './views')

This setting sets the views directory in the application root directory as the location where template files are saved. Template engine will search this directory to locate the template that has to be transformed. By default template engine searches in 'views' directory so you can omit this configuration if you are saving template files in 'views' directory.

app.set('view engine', 'ejs')

This setting sets EJS as the template engine.

Writing EJS template

You can use express-generator to create project structure which gives a views directory and with in that folder you can create files with .ejs extension.

EJS templates are very similar to HTML only difference is the use of template tags <% %> to produce dynamic content.

views\home.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= pageTitle %></title>
  </head>
  <body>
    <h1>EJS Demo</h1>
    <p>Welcome <strong><%= userName %></strong></p>
  </body>
</html>

As you can see template tags are used at two places with in the template.

<title><%= pageTitle %></title>

<p>Welcome <strong><%= userName %></strong></p>

app.js

const express = require('express');

const app = express();

const port = 3000;
const path = require('path');

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.get('/', (req, res) => {
    res.render('home', {pageTitle:'HomePage', userName:'TestUser'});
})

Some important points to note here-

  1. Properties are set for template engine using app.set() method.
  2. In res.render() method template name is passed as the first parameter and as a second parameter an object is passed which has values set for the variables in the template with variable name as key.

This is the generated HTML that you can view by going to page source.

<!DOCTYPE html>
<html>
  <head>
    <title>HomePage</title>
  </head>
  <body>
    <h1>EJS Demo</h1>
    <p>Welcome <strong>TestUser</strong></p>
  </body>
</html>

That's all for the topic ExpressJS: EJS Template Engine Integration With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

July 5, 2024

ExpressJS: Pug Case Statement

In the post ExpressJS: Pug Conditionals we have seen how we can write conditions in Pug template using if else. One more way to write condition is using case statement in Pug template.

The case statement is a shorthand for JavaScript's switch statement. It takes the following form-

case variable
  when value1
    // Code block
  when value2
    // Code block
  ..
  ..
  default
    // Code block for default

Variable may be of type number or string.

Express.js - Pug case example

In the example pug template, we iterate over an array of date objects which has properties date and country. By using case with date.country we format the date in different formats based on the country.

app.js

const express = require('express');

const app = express();

const port = 3000;
const path = require('path');
app.locals.date = require('date-and-time');

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

const dates = [{date:'2003-08-19', country: 'USA'}, 
    {date:'2012-11-23', country: 'UK'},
    {date:'2023-02-11', country: 'India'},
    {date:'2023-09-17', country: 'Germany'},
    {date:'2023-09-17', country: 'Hungary'}
]

app.get('/country', (req, res) => {
    res.render('country', {dates: dates, pageTitle: 'Country Page'});
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

Important points to note here-

  1. Code uses date-and-time JS library which is just a collection of functions for manipulating date and time.
  2. Install date-and-time library using the command-
    npm i date-and-time
  3. Since we need to use date-and-time functions in the template so it is imported using app.locals. The app.locals object has properties that are local variables within the application, and will be available in templates rendered with res.render

views\country.pug

doctype html
html(lang="en")
  head
    meta(charset="UTF-8")
    meta(name="viewport", content="width=device-width, initial-scale=1.0")
    title #{pageTitle}
  body
    h3 Case Statement Demo 
      ul
        each d in dates
          case d.country
            when "India"
            when "UK"
              li= date.format(new Date(d.date),'DD/MM/YYYY') + ' ('+d.country + ')'
            when "USA"
              li= date.format(new Date(d.date),'MM-DD-YYYY') + ' ('+d.country + ')'
            when "Germany"
              li= date.format(new Date(d.date),'YYYY-MM-DD') + ' ('+d.country + ')'
            default 
              li= date.format(new Date(d.date),'YYYY/MM/DD') + ' ('+d.country + ')'

In the code, iteration is done over the dates array and case statement is used with d.country. Based on the value of the country different formats are passed with date.format() which is a function from date-and-time JS library used in the template.

Now if you run the app.js and access the URL- http://localhost:3000/country

Pug Case Statement

That's all for the topic ExpressJS: Pug Case Statement. If something is missing or you have something to share about the topic please write a comment.


You may also like

How to Add 404 Error Page in Express.js

This tutorial shows how to add a 404 (page not found) error page when using ExpressJS routing. If you try to access any path that is not matched to any route then 404 error page should be displayed.

Adding 404 error page

If you have created your project structure using express-generator then you will have separate route folder for keeping route files where you can use express.Router() to create route definitions.

Let's create a separate error HTML too and send that in the case there is no matching route.

views\error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Error Page</title>
</head>
<body>
    <h3>Page not found</h3>
</body>
</html>

In the app.js file after adding the routes as middleware you can add a separate Express middleware for sending response with status as 404 (not found) and file as the created error.html file.

app.js

const express = require('express');
const app = express();

const port = 3000;
const path = require('path');

const usersRouter = require('./routes/users');
const productRouter = require('./routes/products');
// adding routes
app.use(usersRouter);
app.use(productRouter);
// for error page, add after other routes
app.use((req, res, next) => {
    res.status(404).sendFile(path.join(__dirname, 'views', 'error.html'));
})
app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

With this if you run app.js and try to access any route which doesn't exist you should get error page as response with status code 404.

404 Error Page in Express.js

That's all for the topic How to Add 404 Error Page in Express.js. If something is missing or you have something to share about the topic please write a comment.


You may also like