in

Simple CRUD App with Express.js, SQLite & MVC pattern


DISCLAIMER: This is not “the best way” or “should do” blog post, instead I will just try to share my experience and what I find to work out.

The goal is to create an application that will implement CRUD operations in Node.js using the Express.js framework and SQLite database, using the MVC design pattern. It will be a simple To Do List Application in which the user can Create, Read, Update and Delete a task.

The application is available on the following link To Do Application, and the complete code is available on the GitHub repository.



Content:

  1. Getting started
  2. Template engine
  3. Routes
  4. Database
  5. MVC architectural pattern
  6. More setup
  7. Create task
  8. Delete and update task
  9. Data validation
  10. Conclusion



1. Getting started

The first step is to create a new directory and initialize the project using a terminal.

~/To-Do-Application$ npm init -y
Enter fullscreen mode

Exit fullscreen mode

After that, it is necessary to install the project dependencies.

~/To-Do-Application$ npm install express sqlite3 ejs express-validator
Enter fullscreen mode

Exit fullscreen mode

express is the most popular Node.js framework and it enables fast and easy web application development.

sqlite3 is simply a module that allows interaction with the SQLite database.

ejs is one of the many templating engines to choose from, essentially allows the generation of HTML markup using javascript.

express-validator is a module that gives the ability to validate entries so that for example the end-user could not enter an empty task or exceed the allowed number of characters.

The main entry point of the application is the app.js file and it is located in the root directory.



app.js

// Load modules
const express = require('express');

// Create express application
const app = express();

// Listen on port 8080 for connections
app.listen(8080, () => {
  console.log('Server started and listening at http://localhost:8080');
});

// Respond when a GET request is made to the index page
app.get('/', (request, response) => {
  response.send('To Do Application');
});
Enter fullscreen mode

Exit fullscreen mode

require() function loads the Express module and puts it in a variable with the same name.

Express function express() puts a new Express application inside the app variable.

.listen () method simply listening for incoming requests on a defined port 8080.

Express method app.get() handle GET requests and when they match with the defined route / the method calls the callback function which takes a request and a response object as arguments. Then express .send () method returns the string 'To Do Application'.

To start the application, it is necessary to run the following command in the terminal.

~/To-Do-Application$ node app
Enter fullscreen mode

Exit fullscreen mode

If everything is ok the following text will appear.

Server started and listening at: http://localhost:8080
Enter fullscreen mode

Exit fullscreen mode

While in the browser at http://localhost:8080 the result will be the string “To Do Application”.

When Node.js applications are developed and some changes are made to the code, to see their effect, it is necessary to restart the server, which is very annoying.

It is much easier to install a tool Nodemon that will monitor any changes in the code and restart the server automatically.

~/To-Do-Application$ npm install -g nodemon 
~/To-Do-Application$ nodemon app
Enter fullscreen mode

Exit fullscreen mode



2. Template engine

The template engine is used to create HTML templates using HTML syntax but also allows the injection of dynamic data and logic.

The first step is to define the engine and the directory where the templates will be located in the app.js file.

// Load modules
...
const path = require('path');
...
...
// Set view engine and views directory
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
Enter fullscreen mode

Exit fullscreen mode

The .set () method is used to configure application behavior by assign setting names to value.

It is also necessary to create directory views in the root directory.

The path is a core Node.js module and does not need to be installed. In this case .join() method is used to connect Node.js environment variable __dirname with directory views.

It is defined from where the template files will be rendered and which template engine will be used.

For now, three files will be defined in the views directory:

  • index.ejs
  • header.ejs
  • footer.ejs

The visual look of the application will be created with the help of the Bootstrap 5 framework, and explaining this framework goes beyond the scope of this article.



index.ejs

<%- include('header') %> 

<div class="container my-5">
  <h1>Index page</h1>
</div>

<%- include('footer') %> 
Enter fullscreen mode

Exit fullscreen mode

<%- is just EJS tag for injection unescaped value into the template and include is command for including other ejs files, in this case, header.ejs and footer.ejs.



header.ejs

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>To Do Application</title>
  <!-- Bootstrap CSS -->
  <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>
Enter fullscreen mode

Exit fullscreen mode



footer.ejs

  <!-- Bootstrap Bundle with Popper -->
  <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>
Enter fullscreen mode

Exit fullscreen mode

And this is how the index.html file will be displayed to the end user (header.ejs + index.ejs + footer.ejs).



index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>To Do Application</title>
  <!-- Bootstrap CSS -->
  <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 my-5">
    <h1>Index page</h1>
  </div>
  <!-- Bootstrap Bundle with Popper -->
  <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>
Enter fullscreen mode

Exit fullscreen mode



3. Routes

Routes determine how the application will respond to different end-user requests.

One / route is already defined in the application, and using the .get () method, if the end-user requests it, the application will execute the handler function and show the string “To Do Application”.

For the app.js file to remain clean and readable, the routes will be defined in a separate taskRoutes.js file, which will be located in the routes directory.



taskRoutes.js

// Load modules
const express = require('express');

//  Create route handler
const router = express.Router();

// Respond when a GET request is made to the index page
router.get('/', (request, response) => {
  response.render('index');
});

// Export router
module.exports = router;
Enter fullscreen mode

Exit fullscreen mode

The express . Router () method is used to create a new instance of the router object router that will handle incoming requests. And instead calling .send() method on response object, method .render() will render index.ejs file.

The last line just exporting the router so that it could be used in the app.js file.



app.js

// Load modules
...
const taskRoutes = require('./routes/taskRoutes');
...
...
// Application routes
app.use(taskRoutes);
Enter fullscreen mode

Exit fullscreen mode



4. Database

The first step is to install the SQLite database and in Linux operating system it is very simple.

~/To-Do-Application$ sudo apt install sqlite3
Enter fullscreen mode

Exit fullscreen mode

Now create a new directory database where the database will be located, navigate within it, and create the SQLite database.

~/To-Do-Application/database$ sqlite3 To-Do-Application-Database
Enter fullscreen mode

Exit fullscreen mode

When the database is created, it is necessary to create a table and fill it with data.

sqlite> CREATE TABLE Tasks(
   ...> Task TEXT NOT NULL,
   ...> Status TEXT NOT NULL,
   ...> Task_Id INTEGER PRIMARY KEY AUTOINCREMENT);
sqlite> INSERT INTO Tasks VALUES ("Task One", "Completed", "1"), ("Task Two", "Canceled", "2"), ("Task Three", "In progress", "3");
sqlite> .quit;
Enter fullscreen mode

Exit fullscreen mode

Sometimes it’s easier to create and design a database visually, and DB Browser for SQLite is a great software for that purpose.

The database connection to the application will be made in the database.js file.



database.js

// Load modules
const sqlite3 = require('sqlite3').verbose();
const path = require('path');

// Connect with SQLite database
const db_path = path.join(__dirname, '../database', 'To-Do-Application-Database');
const appDatabase = new sqlite3.Database(db_path, sqlite3.OPEN_READWRITE, err => {
  if (err) {
    console.error(err.message);
  }
  console.log('Successful connected to the database');
});

// Export database object
module.exports = { appDatabase };
Enter fullscreen mode

Exit fullscreen mode

There is no more need to explain loading modules or exporting modules.

In the variable db_path is simply defined path to the database in the database directory.

In this case, console.log(db_path) will give the next output.

/home/my-computer-name/To-Do-Application/database/To-Do-Application-Database

Inside Database() method as arguments are entered database information such as the path to the database, opening mode, a callback function, and result is the database object stored in appDatabase variable. The callback function just checks for a connection error and, depending on the results, prints the appropriate message in the console.

This is the current status of the project tree.



5. MVC architectural pattern

This is a very trivial and simple application, and therefore the organization of the code itself is not a problem, but to understand how to organize the code, the MVC pattern will be applied.

MVC stands for Model-View-Controller and represents one of the most popular approaches to application architecture.

Model is the part of the application that works with data.

View represents everything the end-user will see.

Controller is responsible for the logic of the application, it requests data from the Model and delivers it in View, to render the appropriate page to the-end user.

It is necessary to create the following: directory models and inside it the file taskModels.js, directory controllers and inside it the file taskControllers.js.



taskRoutes.js

// Load modules
const express = require('express');
const taskController = require('../controllers/taskControllers');

//  Create route handler
const router = express.Router();

// GET Index Page
router.get('/', taskController.task_index);

// Export router
module.exports = router;
Enter fullscreen mode

Exit fullscreen mode



taskControllers.js

// Load modules
const taskModel = require('../models/taskModels');

// Index page controller
function task_index (request, response) {
  taskModel.getTasks((queryResult) => {
    console.log(queryResult);
    response.render('index', { tasks: queryResult });
  });
};

// Export controllers
module.exports = {
  task_index
};
Enter fullscreen mode

Exit fullscreen mode



taskModels.js

// Load modules
const database = require('../database/database');

// Get all tasks from database
const getTasks = (callback) => {
  const sql = `SELECT * FROM Tasks`;
  database.appDatabase.all(sql, [], (error, rows) => {
    if (error) {
      console.error(error.message);
    }
    callback(rows);
  });
};

// Export models
module.exports = {
  getTasks
};
Enter fullscreen mode

Exit fullscreen mode

What exactly is going on here?

In taskRoutes.js .get() method on router object as second argument call task_index function from taskController.js.

task_index function as before has two parameters request and response but this time also calls function getTasks from taskModel.js.

getTasks function has a specified arrow function (with parameter queryResult) as an argument.

After appDatabase object method .all() get result, that result will be stored in queryResult (an error or rows from database) and with help of .render() method available for use in index.ejs.



index.ejs

<%- include('header') %> 

<div class="container">
  <h2 class="mt-5 mb-3">To Do List</h2>
  <table class="table">
    <thead>
      <tr>
        <th scope="col">Task</th>
        <th scope="col">Status</th>
        <th class="d-print-none">
          Actions
        </th>
      </tr>
    </thead>
    <tbody>
      <% for (const task of tasks) { %>
      <tr>
        <td><%= task.Task %></td>
        <td class="status"><%= task.Status %></td>
        <td class="d-print-none">
          <a class="btn btn-sm btn-warning">Update</a>
          <a class="btn btn-sm btn-danger">Delete</a>
        </td>
      </tr>
      <% } %>
    </tbody>
  </table>
</div>

<%- include('footer') %> 
Enter fullscreen mode

Exit fullscreen mode

tasks: queryResult this part enables usage of queryResult inside the index.ejs with variable name tasks.

ejs tags <% %> are for control-flow (no output) and <%= %> output value into the template.

This code simply loops through each row and prints the values in a table.

To Do List

And this is the current state of the project tree.

Project tree



6. More setup

To serve static files such as CSS files and JavaScript files, the express middleware function will be used.



app.js

...
// Serve static files
app.use(express.static(path.join(__dirname, 'public')));
...
Enter fullscreen mode

Exit fullscreen mode

Now it is necessary to create a public directory and inside it a css directory with a custom.css file and a js directory with a main.js file.

Access to these files is now possible via url /css/custom.css or /js/main.js.

The custom.css file is used to overriding some Bootstrap 5 styles, and the main.js file is used to color tasks depending on their status. They are not shown in this blog post, but they can be easily accessed via the GitHub repository.

Small changes were made to the header.ejs file, due to the visual appearance.



header.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>To Do Application</title>
    <link rel="icon" href="/favicon/favicon.svg" type="image/x-icon">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="/css/custom.css">
  </head>
<body>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
      <a class="navbar-brand" href="https://dev.to/">To Do Application</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse me-auto" id="navbarSupportedContent">
        <ul class="navbar-nav mb-2 mb-lg-0 ms-auto">
          <li class="nav-item">
            <a class="nav-link" href="https://dev.to/">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link">Create a task</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="https://dev.to/about">About</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>
Enter fullscreen mode

Exit fullscreen mode

In footer.ejs is inserted following line <script src="https://dev.to/js/main.js"></script>.

about.ejs page was created with the appropriate route and controller.



about.ejs

<%- include('header') %>

<div class="container my-5">
    <h2 class="my-5">Simple CRUD Application with Express.js and SQLite database</h2>
    <p>The application is a simple To Do List, which implements CRUD operations in the Node.js javascript runtime environment.</p>
</div>

<%- include('footer') %>
Enter fullscreen mode

Exit fullscreen mode



taskRoutes.ejs

...
// GET About Page
router.get('/about', taskController.task_about);
...
Enter fullscreen mode

Exit fullscreen mode



taskControllers.ejs

...
// About page controller
const task_about = (request, response) => {
    response.render('about');
};

// Export controllers
module.exports = {
  ...
  task_about
};
Enter fullscreen mode

Exit fullscreen mode

The application currently looks like this.

To Do Application

And the project tree looks like this.

Project Tree



7. Create task

Currently, the application just reads the results from the database and displays them in a table.

To be able to add new tasks, it is necessary to create a new view create.ejs.



create.ejs

<%- include('header') %>

  <div class="container">
    <h2 class="mt-5 mb-3">Create a new task</h2>
    <form action="/task/create" method="post">
      <div class="mb-3">
        <label for="inputTask">Task:</label>
        <input autofocus type="text" class="form-control shadow-none mb-3" id="inputTask" name="Task">
        <label for="inputStatus">Status:</label>
        <select disabled class="form-select shadow-none" id="inputStatus" name="Status">
          <option value="In progress" selected>In progress</option>
        </select>
      </div>         
      <button type="submit" class="btn btn-primary">Add</button>
      <a class="btn btn-outline-dark cancel" href="https://dev.to/">Cancel</a>
    </form>
  </div>

<%- include('footer') %>
Enter fullscreen mode

Exit fullscreen mode

And it will look something like this.

Create New Task

Adding routes to taskRoutes is also pretty straightforward.



taskRoutes.js

...
// GET/POST Create page
router.get('/task/create', taskController.task_create_get);
router.post('/task/create', taskController.task_create_post);
...
Enter fullscreen mode

Exit fullscreen mode

In the case of the controllers, the situation is a little different.



taskControllers.js

...
// Create task page controllers
// GET
function task_create_get (request, response) {
  response.render('create');
};
// POST
function task_create_post (request, response) {
  const task = request.body.Task;
  const status = 'In progress';
  taskModel.createTask(task, status, (result) => {
    console.log(result);
    response.redirect('/');
  });
};

// Export controllers
module.exports = {
  ...
  task_create_get,
  task_create_post
};
Enter fullscreen mode

Exit fullscreen mode

task_create_get controller has the task of just rendering create.ejs file. However, when the-end user wants to add a new task, the task_create_post function enters the scene. The variable task with help of request property body and input name Task store value of user input.



taskModels.js

...
// Create new task
const createTask = (task, status, callback) => {
  const sql = `INSERT INTO Tasks (Task, Status) VALUES ('${task}', '${status}')`;
  database.appDatabase.run(sql, [], (error, row) => {
    if (error) {
      callback(error.message);
    }
    const successMessage = "The task was entered successfully."
    callback(successMessage);
  });
};

// Export models
module.exports = {
  ...
  createTask,
};

Enter fullscreen mode

Exit fullscreen mode

Now with the help of the variables task and status , the function createTask completes the query. After the appDatabase object method .run () stores the result in the database, the createTask function in callback loads either an error or a sucessMessage, and forwards it back to taskController (variable result).

For this to work at all, the app.js file needs to include the next piece of code.

...
// To recognize the incoming request object as strings or arrays
app.use (express.urlencoded ({extended: true}));
Enter fullscreen mode

Exit fullscreen mode

express.urlencoded () is a method inbuilt in express to recognize the incoming Request Object as strings or arrays.



8. Delete and update task

Delete and update task processes are very similar, and do not differ much from the create process.

The first step is to add the appropriate href tag to the existing links in the index.ejs file.



index.ejs

...
<a class="btn btn-sm btn-warning" href="/task/update/<%= task.Task_Id %>">Update</a>
<a class="btn btn-sm btn-danger" href="/task/delete/<%= task.Task_Id %>">Delete</a>
...
Enter fullscreen mode

Exit fullscreen mode

With the help of ejs tags and task.Task_Id link to each individual delete task/update task view will be defined by a unique id.



delete.ejs

<%- include('header') %>

<div class="container">
  <h2 class="mt-5 mb-3">Delete task</h2>
  <form action="/task/delete/<%= task.Task_Id %>" method="post">
    <div class="mb-3">
      <label for="inputTask">Task:</label>
      <input readonly autofocus type="text" class="form-control shadow-none mb-3" id="inputTask" name="Task" value="<%= task.Task %>">
      <label for="inputStatus">Status:</label>
      <select disabled class="form-select shadow-none" aria-label="Default select example" id="inputStatus" name="Status">
        <option value="In progress" selected>In progress</option>
        <option value="Completed">Completed</option>
        <option value="Canceled">Canceled</option>
      </select>
    </div>         
    <button type="submit" class="btn btn-danger">Delete</button>
    <a class="btn btn-outline-dark cancel" href="https://dev.to/">Cancel</a>
  </form>
</div>

<%- include('footer') %>
Enter fullscreen mode

Exit fullscreen mode



update.ejs

<%- include('header') %>

<div class="container">
  <h2 class="mt-5 mb-3">Update task</h2>
  <form action="/task/update/<%= task.Task_Id %>" method="post">
    <div class="mb-3">
      <label for="inputTask">Task:</label>
      <input autofocus type="text" class="form-control shadow-none mb-3" id="inputTask" name="Task" value="<%= task.Task %>">
      <label for="inputStatus">Status:</label>
      <select class="form-select shadow-none" aria-label="Default select example" id="inputStatus" name="Status">
        <option value="In progress" selected>In progress</option>
        <option value="Completed">Completed</option>
        <option value="Canceled">Canceled</option>
      </select>
    </div>        
    <button type="submit" class="btn btn-warning">Update</button>
    <a class="btn btn-outline-dark cancel" href="https://dev.to/">Cancel</a>
  </form>
</div>

<%- include('footer') %>
Enter fullscreen mode

Exit fullscreen mode

The routes are also very similar.



taskRoutes.js

...
// GET/POST Delete Page
router.get('/task/delete/:id', taskController.task_delete_get);
router.post('/task/delete/:id', taskController.task_delete_post);
// GET/POST Update Page
router.get('/task/update/:id', taskController.task_update_get);
router.post('/task/update/:id', taskController.task_update_post);
...
Enter fullscreen mode

Exit fullscreen mode

The links contains : id variable, it is specified in index.ejs links with <% = task.Task_Id%> and will be used using request.params.id in taskControllers.js.



taskControllers.js

...
// Delete task page controllers
// GET
const task_delete_get = (request, response) => {
  const id = request.params.id;
  taskModel.getTask(id, (result) => {
    console.log(result);
    response.render('delete', { task: result });
  });
};
// POST
const task_delete_post = (request, response) => {
  const id = request.params.id;
  taskModel.deleteTask(id, () => {
    response.redirect('/');
  });
};

// Update task page controllers
// GET
const task_update_get = (request, response) => {
  const id = request.params.id;
  taskModel.getTask(id, (result) => {
    response.render('update', { task: result });
  });
};
// POST
const task_update_post = (request, response) => {
  const task = request.body.Task;
  const status = request.body.Status;
  const id = request.params.id;
  taskModel.updateTask(task, status, id, () => {
    response.redirect('/');
  });
};

// Export controllers
module.exports = {
  ...
  task_delete_get,
  task_delete_post,
  task_update_get,
  task_update_post
};

Enter fullscreen mode

Exit fullscreen mode

Simply the controllers task_delete_get and task_update_get use the getTask function and provide the already mentioned id as an argument.

The task_update_post controller must provide two more arguments task and status in addition to id.



taskModels.js

...
// Get task
const getTask = (id, callback) => {
  const sql = `SELECT * FROM Tasks WHERE Task_Id = ${id}`;
  database.appDatabase.get(sql, [], (error, row) => {
    if (error) {
      callback(error.message);
    }
    callback (row);
  });
};

// Delete task  
const deleteTask = (id, callback) => {
  const sql = `DELETE FROM Tasks WHERE Task_Id = ${id}`;
  database.appDatabase.run(sql, [], (error, row) => {
    if (error) {
      callback(error.message);
    }
    const successMessage = "The task was successfully deleted."
    callback(successMessage);
  });
};

// Update task  
const updateTask = (task, status, id, callback) => {
  let sql = `UPDATE Tasks SET Task = '${task}', Status="${status}" WHERE (Task_ID = ${id})`;
  database.appDatabase.run(sql, [], (error, row) => {
    if (error) {
      callback(error.message);
    }
    const successMessage = "The task was successfully updated."
    callback(successMessage);
  });
};

// Export models
module.exports = {
  ...
  getTask,
  deleteTask,
  updateTask
};
Enter fullscreen mode

Exit fullscreen mode

The logic in these functions is identical to that in the createTask function. So the functions will use the id, task and status arguments, to fill in the sql query and after the appDatabase methods get the results (or error), place them in the callback and allow the functions in taskControllers to use them.



9. Data validation

It is currently possible to enter any task, even one without a single letter or with 500 letters.

Data Validation

To prevent such things, an express-validation module will be used. The first step is to create a validation directory and a taskValidator.js file in it.



taskValidator.js

// Load function check
const { check } = require('express-validator');

// Export function validateTask
exports.validateTask = [
  check('Task')
  .isLength({ min:3, max: 30 })
  .withMessage("The task must have between 3 and 30 characters")
];
Enter fullscreen mode

Exit fullscreen mode

Very simply, the check function is loaded from the express-validator module and used in the validateTask function, which is also exported at the same time.

Many different restrictions can be set, but only the .isLength() is used here. So the number of letters must be between 3 and 30, and if it is not, the .withMessage() will return a warning.

validateTask function must be used inside the taskRoutes.js file.



taskRoutes.js

...
const taskValidator = require('../validation/taskValidator');
...
router.post('/task/create', taskValidator.validateTask, taskController.task_create_post);
...
router.post('/task/update/:id', taskValidator.validateTask, taskController.task_update_post);
Enter fullscreen mode

Exit fullscreen mode

It is referred to as an argument in post methods for creating and updating a task.

It is necessary to make some changes in taskControllers file.



taskControllers.js

...
const { validationResult } = require('express-validator');
...
// Create task page controllers
// GET
function task_create_get (request, response) {
  response.render('create', { errors: {} });
};
// POST
function task_create_post (request, response) {
  const errors = validationResult(request);
  if (!errors.isEmpty()) {
    return response.render('create', { errors: errors.mapped() });
  }
  const task = request.body.Task;
  const status = 'In progress';
  taskModel.createTask(task, status, (result) => {
    console.log(result);
    response.redirect('/');
  });
};
...
// Update task page controllers
// GET
const task_update_get = (request, response) => {
  const id = request.params.id;
  taskModel.getTask(id, (result) => {
    response.render('update', { task: result, errors: {} });
  });
};
// POST
const task_update_post = (request, response) => {
  const errors = validationResult(request);
  if (!errors.isEmpty()) {
    return response.render('update', { task: request.body, errors: errors.mapped() });
  }
  const task = request.body.Task;
  const status = request.body.Status;
  const id = request.params.id;
  taskModel.updateTask(task, status, id, () => {
    response.redirect('/');
  });
};
Enter fullscreen mode

Exit fullscreen mode

The validationResult () method extracts any validation errors from the request object and places them in the result object, in this case, the errors variable.

After that, it is simply checked for errors, and if there are any, they are forwarded to the views to be displayed.



create.ejs / update.ejs

...
<input autofocus type="text" class="form-control shadow-none mb-3" id="inputTask" name="Task">
<% if (errors.Task) { %>
<div class="text-danger mb-3"><%= errors.Task.msg %></div>
<% } %>
...
Enter fullscreen mode

Exit fullscreen mode



10. Conclusion

When you are a beginner it is very difficult to make any application, and even harder to explain how you managed it. So when you find some mistake (which there are many of them) whether it is not understanding the concept, wrong code, expression, or just grammar, please correct me in the comment.

I will be very grateful.



Source: https://dev.to/dejansdev/simple-crud-app-with-express-js-sqlite-mvc-pattern-4nd8

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

GIPHY App Key not set. Please check settings

New Tab Screen With Winter Theme Background

🏝️ Tourism with NFT on BSC – On Pre-Sale until December 31st 2021 – Airdrop Ongoing