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
Đ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:
- https://www.npmjs.com/package/mysql
- https://philipm.at/2017/method-override_in_expressjs.html
- https://github.com/datnquit/Build-NodeJS-with-Mysql
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!