Những website động hầu hết đều phải kết nối với 1 database nào đó, database giúp lưu trữ và trả về dữ liệu cho người dùng. Hôm nay chúng ta cùng nhau tìm hiểu về cách kết nối giữa NodeJS và MySql nhang. Và xây dựng CRUD cơ bản để quản lý dữ liệu

Cài đặt

Source code

Điều kiện cơ bản các bạn phải biết cách khởi tạo project nhang, bạn nào chưa biết có thể coi lại những trước của mình. Ta chạy command để tài những package cần thiết.
Và đây là khóa học về nodejs, nên mình sẽ không tập trung vào HTML và CSS, nên phần giao diện sau khi chạy sẽ không được đẹp

npm install express mysql ejs method-override

Các bạn tiến hành tạo cấu trúc thư mục như mình

PROJECT
    app
        config
            db.config.js
        controllers
            todo.controller.js
        models
            db.js
            todo.model.js
        routes
            todo.route.js
        views
            todo
                create.ejs
                edit.ejs
                index.ejs
            404.ejs
            err.ejs
            index.ejs

    server.js
    node_modules
        ...
    package.json

và đừng quên cài MySql lên máy bạn và tạo 1 database để chúng ta kết nối và làm việc nhang.

Chạy query để sinh ra bảng *todo

CREATE TABLE IF NOT EXISTS `todo` (
  id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  title varchar(255) NOT NULL,
  description varchar(255),
  published BOOLEAN DEFAULT false
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Setup server

Ta mở file server.js và khai báo đơn giản, chắc bạn nào cũng có thể làm được chỗ này:


const express = require("express");
const app = express();
const bodyParser = require('body-parser');
const methodOverride = require('method-override');

app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine', 'ejs')
app.set('views', 'app/views');

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(methodOverride('_method', { methods: ['POST', 'GET'] }));
app.use(methodOverride(function (req, res) {
    if (req.body && typeof req.body === 'object' && '_method' in req.body) {
      var method = req.body._method;
      delete req.body._method;
      return method;
    }
  }));

app.get('/', (req, res, next) => {
    res.render('index');
})

require('./app/routes/todo.route')(app);

app.listen(3000, function() {
    console.log('server running: http://localhost:3000');
});

Có nhiều cách để có thể khai báo thằng route, thì những bài trước mình dùng app.use trực tiếp ở file server.js này, còn bây giờ mình truyền thằng app này qua file route rồi mình mới use

Cấu hình và tạo kết nối đến MySql

Mở file db.config.js

module.exports = {
    HOST: "localhost",
    USER: "root",
    PASSWORD: "password",
    DB: "nodejs_base"
}

Tùy theo mysql ở máy các bạn mà chúng ta config cho đúng.

Tiếp theo tiến hành kết nối project vs csdl. Mở file db.js trong folder model lên:

const mysql = require("mysql");
const dbConfig = require("../config/db.config");

const connection = mysql.createConnection({
    host: dbConfig.HOST,
    user: dbConfig.USER,
    password: dbConfig.PASSWORD,
    database: dbConfig.DB
});

connection.connect(error => {
    if (error) throw error;
    console.log("Successfully connected to the database");
})

module.exports = connection;

Khai báo model để làm việc với database

Làm việc với Node JS các bạn phải chuẩn bị tâm lý chúng ta sẽ phải code tay khá là nhiều. Tự tay setup những function.

Ta mở file todo.model.js khai báo những hàm cơ bản CRUD

const sql = require("./db");

const Todo = function(todo) {
    this.title = todo.title;
    this.description = todo.description;
    this.published = todo.published;
};

Todo.create = (newTodo, result) => {
    sql.query("INSERT INTO todo SET ?", newTodo, (err, res) => {
        if (err) {
            console.log("error: ", err);
            result(err, null);
            return;
        }
        console.log("created todo: ", { id: res.insertId, ...newTodo });
        result(null, { id: res.insertId, ...newTodo });
    });
};
Todo.findById = (id, result) => {
    sql.query(`SELECT * FROM todo WHERE id = ${id}`, (err, res) => {
        if (err) {
            console.log("error: ", err);
            result(err, null);
            return;
        }
        if (res.length) {
            console.log("found todo: ", res[0]);
            result(null, res[0]);
            return;
        }
        // not found todo with the id
        result({ kind: "not_found" }, null);
    });
};
Todo.getAll = (title, result) => {
    let query = "SELECT * FROM todo";
    if (title) {
        query += ` WHERE title LIKE '%${title}%'`;
    }
    sql.query(query, (err, res) => {
        if (err) {
            console.log("error: ", err);
            result(null, err);
            return;
        }
        console.log("todo: ", res);
        result(null, res);
    });
};
Todo.getAllPublished = result => {
    sql.query("SELECT * FROM todo WHERE published=true", (err, res) => {
        if (err) {
            console.log("error: ", err);
            result(null, err);
            return;
        }
        console.log("todo: ", res);
        result(null, res);
    });
};
Todo.updateById = (id, todo, result) => {
    sql.query(
        "UPDATE todo SET title = ?, description = ?, published = ? WHERE id = ?",
        [todo.title, todo.description, todo.published, id],
        (err, res) => {
            if (err) {
                console.log("error: ", err);
                result(null, err);
                return;
            }
            if (res.affectedRows == 0) {
                // not found todo with the id
                result({ kind: "not_found" }, null);
                return;
            }
            console.log("updated todo: ", { id: id, ...todo });
            result(null, { id: id, ...todo });
        }
    );
};
Todo.remove = (id, result) => {
    sql.query("DELETE FROM todo WHERE id = ?", id, (err, res) => {
        if (err) {
            console.log("error: ", err);
            result(null, err);
            return;
        }
        if (res.affectedRows == 0) {
            // not found todo with the id
            result({ kind: "not_found" }, null);
            return;
        }
        console.log("deleted todo with id: ", id);
        result(null, res);
    });
};
Todo.removeAll = result => {
    sql.query("DELETE FROM todo", (err, res) => {
        if (err) {
            console.log("error: ", err);
            result(null, err);
            return;
        }
        console.log(`deleted ${res.affectedRows} todo`);
        result(null, res);
    });
};
module.exports = Todo;

Ở đây mình sử dụng bảng todo với vài cột đơn giản để các bạn có thể hiểu được.

Khai báo Route

Chúng ta sẽ khai báo những cái route này với tiền tố là todo

Mở file todo.route.js và khai báo ngay thôi nào


module.exports = app => {
    const todo = require("../controllers/todo.controller");
    var router = require("express").Router();

    // Retrieve all todo
    router.get("/", todo.findAll);

    // Show form create Todo
    router.get("/create", todo.create);
    // Store Todo
    router.post("/", todo.store);

    // Retrieve a single todo with id
    router.get("/edit/:id", todo.edit);
    // Update a todo with id
    router.put("/:id", todo.update);

    // Delete a todo with id
    router.get("/delete/:id", todo.delete);

    // Delete all todo
    router.delete("/delete", todo.deleteAll);
    
    // Retrieve all published todo
    router.get("/published", todo.findAllPublished);

    app.use('/todo', router);

    app.get('/500', (req, res) => {
        res.render('err')
    });
    app.get('/404', (req, res) => {
        res.render('404')
    });
}

Định nghĩa các hàm xử lý

File controller này sẽ chịu trách nhiệm xử lý logic. Chúng ta tạo các function tương ứng đã khai báo bên file route.

Ta mở file todo.controller.js lên


const Todo = require("../models/todo.model");

// Show form create Todo
exports.create = (req, res) => {
    res.locals.status = req.query.status;
    res.render('todo/create');
}
// Create and Save a new Todo
exports.store = (req, res) => {
    // Validate request
    if (!req.body) {
        res.redirect('/todo/create?status=error')
    }
    
    // Create a Todo
    const todo = new Todo({
        title: req.body.title,
        description: req.body.description,
        published: !req.body.published ? false : true
    });
    // Save Todo in the database
    Todo.create(todo, (err, data) => {
        if (err)
            res.redirect('/todo/create?status=error')
        else res.redirect('/todo/create?status=success')
    });
};
// Retrieve all Todo from the database (with condition).
exports.findAll = (req, res) => {
    res.locals.deleted = req.query.deleted;
    const title = req.query.title;
    Todo.getAll(title, (err, data) => {
        if (err)
            res.redirect('/500')
        else res.render('todo/index', {todo: data});
    });
};

// Find a single Todo with a id 
exports.edit = (req, res) => {
    res.locals.status = req.query.status;

    Todo.findById(req.params.id, (err, data) => {
        if (err) {
            if (err.kind === "not_found") {
                res.redirect('/404');
            } else {
                res.redirect('/500');
            }
        } else res.render('todo/edit', { todo: data });
    });
};
// Update a Todo identified by the id in the request
exports.update = (req, res) => {
    // Validate Request
    if (!req.body) {
        res.redirect('/todo/edit/' + req.params.id + '?status=error')
    }

    Todo.updateById(
        req.params.id,
        new Todo(req.body),
        (err, data) => {
            if (err) {
                if (err.kind === "not_found") {
                    res.redirect('/404');
                } else {
                    res.redirect('/500');
                }
            } else res.redirect('/todo/edit/' + req.params.id + '?status=success');
        }
    );
};
// Delete a Todo with the specified id in the request
exports.delete = (req, res) => {
    Todo.remove(req.params.id, (err, data) => {
        if (err) {
            if (err.kind === "not_found") {
                res.redirect('/404');
            } else {
                res.redirect('/500');
            }
        } else res.redirect('/todo?deleted=true')
    });
};
// Delete all Todo from the database.
exports.deleteAll = (req, res) => {
    Todo.removeAll((err, data) => {
        if (err)
            res.redirect('/500');
        else res.redirect('/todo?deleted=true')
    });
};

// find all published Todo
exports.findAllPublished = (req, res) => {
    Todo.getAllPublished((err, data) => {
        if (err)
            res.redirect('/500')
        else res.render('todo/index', { todo: data})
    });
};

Tiến hành tạo các view

todo/create.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>Create Todo</title>
</head>
<body>
    <h2>Create new Todo</h2>

    <% if (typeof status !== 'undefined') { %>
        <%if (status == 'success') { %>
            <div style="color: green;">
                Created Successfully
            </div>
        <% } else { %>
            <div style="color: red;">
                Some error occurred while creating the Todo
            </div>
        <% } %>
        <br>
    <% } %>
    <form action="/todo" method="post">
        <label for="title">Title</label>
        <input type="text" id="title" name="title">
        <br> <br>
        <label for="description">Description</label>
        <input type="text" name="description" id="description">
        <br> <br>
        <label for="published">Publish</label>
        <input type="checkbox" name="published" id="published">
        <br><br>
        <button type="submit">Create</button>
    </form>
</body>
</html>

todo/edit.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>Edit Todo - <%= todo.title %></title>
</head>
<body>
    <h2>Edit Todo</h2>

    <% if (typeof status !== 'undefined') { %>
        <%if (status == 'success') { %>
            <div style="color: green;">
                Updated Successfully
            </div>
        <% } else { %>
            <div style="color: red;">
                Some error occurred while editing the Todo
            </div>
        <% } %>
        <br>
    <% } %>
    <form action="/todo/<%= todo.id %>" method="post">
        <input type="hidden" name="_method" value="put">

        <label for="title">Title</label>
        <input type="text" id="title" name="title" value="<%= todo.title %>">
        <br> <br>
        <label for="description">Description</label>
        <input type="text" name="description" id="description"  value="<%= todo.description %>">
        <br> <br>
        <label for="published">Publish</label>
        <input type="checkbox" name="published" id="published" <% if (todo.published) { %> checked <% } %>>
        <br><br>
        <button type="submit">Update</button>
    </form>
</body>
</html>

todo/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>Todo</title>
    <style>
        td, th {
            padding: 5px;
        }
    </style>
</head>
<body>
    <h1>Get All Todo</h1>
    <% if (typeof deleted !== 'undefined') { %>
        <%if (deleted == 'true') { %>
            <div style="color: green;">
                Deleted Successfully
            </div>
        <% } %>
        <br>
    <% } %>

    <form action="/todo" method="get">
        <input type="text" name="title" placeholder="Enter title">
        <button type="submit">Search</button>
    </form>
    <br>

    <table border="1" cellpadding="0" cellspacing="0">
        <tr>
            <th>ID</th>
            <th>Title</th>
            <th>Des</th>
            <th>Publish</th>
            <th>Action</th>
        </tr>
        <% todo.forEach((item) => { %>
            <tr>
                <td><%= item.id %></td>
                <td><%= item.title %></td>
                <td><%= item.description %></td>
                <td><% if (item.published) { %> yes <% } %></td>
                <td>
                    <a href="/todo/edit/<%= item.id %>">Edit</a> <br>
                    <a href="/todo/delete/<%= item.id %>">Delete</a>
                </td>
            </tr>
        <% }) %>
    </table>
    
</body>
</html>

404.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>404</title>
</head>
<body>
    <h1>Page not found</h1>
    <a href="/">Back Home</a>
</body>
</html>

err.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>ERROR</title>
</head>
<body>
    <h1>ERROR</h1>
    <a href="/">Back Home!</a>
</body>
</html>

index.ejs


<h1>Hello world!</h1>

Kiểm tra chức năng

Chạy command để start server và truy cập vào địa chỉ http://localhost:3000

node server

Create a new Todo

Truy cập vào địa chỉ http://localhost:3000/todo/create

Sau khi điền thông tin click Create để tạo todo.
Khi tạo thành công sẽ có thông báo Created Successfully

Get all Todo

Sau khi tạo thành công Todo, ta trở lại http://localhost:3000/todo để hiển thị tất cả các Todo có trong CSDL.

Tại cột Action ta đã khai báo 2 đường dẫn dùng để edit và delete Todo.

Update a Todo

Click vào link Edit để hiển thị form update Todo mà ta chọn

Sau khi cập nhật thông tin, bạn click Update

Cập nhật thành công sẽ trả về thông báo Updated Successfully

Ngoài ra các bạn có thể truy cập theo các route ta đã định nghĩa

Kết luận

Mình vừa chia sẻ một số kiến thức khi làm việc với MySql.
Vì Node ít hỗ trợ, nên chúng ta phải tự tay setup nhiều.
Lưu ý: Vì làm thủ công, nếu không cẩn thận dễ bị bypass.

Nguồn tham khảo:

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!