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

No comments:

Post a Comment