Tìm kiếm trang web

Cách Sử Dụng Sequelize với Node.js và MySQL


Tác giả đã chọn Quỹ Internet Mở/Tự Do Ngôn Luận để nhận khoản quyên góp như một phần của chương trình Viết Để Quyên Góp.

Giới thiệu

Sequelize là một Object Relational Mapper dựa trên Node.js giúp dễ dàng làm việc với các cơ sở dữ liệu MySQL, MariaDB, SQLite, PostgreSQL và nhiều hơn thế nữa. Một Object Relational Mapper thực hiện các chức năng như xử lý các bản ghi cơ sở dữ liệu bằng cách đại diện dữ liệu dưới dạng các đối tượng. Sequelize có một cơ chế di chuyển (migration) mạnh mẽ có thể biến đổi các sơ đồ cơ sở dữ liệu hiện có thành các phiên bản mới. Nhìn chung, Sequelize cung cấp sự hỗ trợ xuất sắc cho việc đồng bộ hóa cơ sở dữ liệu, eager loading, các liên kết (associations), giao dịch (transactions) và các migration cơ sở dữ liệu trong khi giảm thời gian phát triển và ngăn ngừa các lỗi SQL injection.

Trong hướng dẫn này, bạn sẽ cài đặt và cấu hình Sequelize với MySQL trên môi trường phát triển cục bộ của mình. Tiếp theo, bạn sẽ sử dụng Sequelize để tạo cơ sở dữ liệu và các mô hình, cũng như thực hiện các thao tác insert, select, và delete. Sau đó, bạn sẽ tạo các một liên kết (association) trong Sequelize cho các mối quan hệ một-một, một-nhiều, và nhiều-nhiều. Cuối cùng, bạn sẽ tạo các truy vấn thô (raw queries) trong Sequelize để thay thế mảng và đối tượng. Ngoài những kiến thức cơ bản này, hướng dẫn còn đề cập đến cấu hình cơ sở dữ liệu an toàn bằng biến môi trường, quản lý thay đổi schema một cách an toàn thông qua migrations, và các thực hành tốt nhất để gỡ lỗi và tối ưu hiệu suất.

Triển khai các ứng dụng frontend của bạn từ GitHub bằng Nền tảng Ứng dụng Linux-Console.net. Hãy để Linux-Console.net tập trung vào việc mở rộng ứng dụng của bạn.

Điểm quan trọng:

  • Sequelize cung cấp một cách dễ dàng để tương tác với MySQL bằng cách sử dụng các mô hình thay vì SQL thô, giúp các thao tác cơ sở dữ liệu trở nên đơn giản và có tổ chức hơn.
  • Lưu thông tin đăng nhập cơ sở dữ liệu trong các biến môi trường giúp bảo mật thông tin nhạy cảm và tránh khỏi hệ thống quản lý phiên bản.
  • Sử dụng migrations để cập nhật schema an toàn hơn so với việc dựa vào sequelize.sync(), đặc biệt là trong môi trường sản xuất.
  • Sequelize giúp dễ dàng xác định các mối quan hệ một-một, một-nhiều và nhiều-nhiều giữa các mô hình.
  • Các truy vấn SQL thô vẫn có thể được sử dụng khi bạn cần kiểm soát nhiều hơn hoặc hiệu suất tốt hơn cho các thao tác cơ sở dữ liệu phức tạp.
  • Hầu hết các vấn đề phổ biến với Sequelize, chẳng hạn như lỗi kết nối hoặc lỗi di chuyển, có thể được giải quyết bằng cách kiểm tra các biến môi trường, xem lại nhật ký và sử dụng các công cụ MySQL như EXPLAIN và SHOW PROCESSLIST.
  • Hiệu suất có thể được cải thiện bằng cách tối ưu hóa các truy vấn, thêm chỉ mục phù hợp, điều chỉnh các kết nối trong pool, và lưu trữ tạm thời dữ liệu được truy cập thường xuyên.
  • Kiểm thử di chuyển dữ liệu, thêm xử lý lỗi và giám sát hiệu suất cơ sở dữ liệu giúp các ứng dụng Sequelize đáng tin cậy và sẵn sàng cho sản xuất.

Điều kiện tiên quyết

Để hoàn thành hướng dẫn này, bạn sẽ cần:

  • Một môi trường phát triển chạy Node.js. Để thiết lập điều này, hãy làm theo hướng dẫn Cách Cài Đặt Node.js và Tạo Môi Trường Phát Triển Cục Bộ cho hệ điều hành của bạn.
  • MySQL đã được cài đặt trong môi trường làm việc của bạn. Hãy làm theo hướng dẫn Cách Cài Đặt MySQL cho môi trường của bạn, bao gồm các bước để tạo một người dùng.
  • Quen thuộc với JavaScript và các truy vấn MySQL, điều mà bạn có thể phát triển thông qua chuỗi bài học How To Code in JavaScript và hướng dẫn An Introduction to Queries in MySQL.
  • Hiểu biết cơ bản về các khái niệm cơ sở dữ liệu quan hệ, mà bạn có thể tìm thấy trong hướng dẫn, Hiểu về Cơ sở Dữ liệu Quan hệ.

Hướng dẫn này đã được thử nghiệm trên Node.js phiên bản v24.11.1 và npm phiên bản 11.6.2.

Cài đặt và cấu hình Sequelize

Trong bước này, bạn sẽ cài đặt Sequelize và tạo kết nối với cơ sở dữ liệu MySQL của bạn. Để làm điều đó, trước tiên bạn sẽ tạo một ứng dụng Node.js. Sau đó, bạn sẽ cài đặt Sequelize, cấu hình cơ sở dữ liệu MySQL và phát triển một ứng dụng đơn giản.

Cài đặt Sequelize

Bắt đầu bằng cách tạo một thư mục dự án. Trong ví dụ này, bạn có thể sử dụng hello-world. Khi thư mục đã được tạo, hãy điều hướng đến thư mục đó bằng terminal:

mkdir hello-world

cd hello-world

Sau đó, tạo một ứng dụng Node.js mẫu bằng cách sử dụng lệnh sau:

npm init

Tiếp theo, bạn sẽ được yêu cầu trả lời một số câu hỏi thiết lập. Sử dụng kết quả sau đây cho cấu hình của bạn. Nhấn ENTER để sử dụng giá trị mặc định hiển thị và đảm bảo đặt điểm vào chính là server.js. Điều này tạo ra một cấu trúc dự án dễ dàng để duy trì.

Kết quả đầu ra sẽ trông như sau, và sẽ điền vào tệp package.json:

{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Tiếp theo, tạo một tệp server.js trống bên trong thư mục dự án:

touch server.js

Sau khi thực hiện các bước trước, cấu trúc thư mục cuối cùng của bạn sẽ trông như sau:

hello-world/
├─ package.json
├─ server.js

Bây giờ bạn có thể cài đặt Sequelize bằng lệnh sau:

npm i sequelize

Sau những cập nhật này, tệp package.json bây giờ trông như sau:

{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "sequelize": "^6.37.7"
  }
}

Trong phần dependencies, bạn sẽ thấy một phụ thuộc Sequelize.

Bạn đã thiết lập dự án và cài đặt Sequelize. Tiếp theo, bạn sẽ tạo một cơ sở dữ liệu mẫu để kết nối.

Tạo một cơ sở dữ liệu mẫu

Như một phần của các yêu cầu tiên quyết, bạn đã cài đặt và cấu hình MySQL, bao gồm việc tạo một người dùng. Bây giờ bạn sẽ tạo một cơ sở dữ liệu trống.

Để làm điều đó, trước tiên, bạn cần đăng nhập vào phiên bản MySQL của mình. Nếu bạn đang chạy từ xa, bạn có thể sử dụng công cụ mà bạn ưa thích. Nếu bạn đang sử dụng phiên bản MySQL chạy cục bộ, bạn có thể sử dụng lệnh sau, thay your_username bằng tên người dùng MySQL của bạn:

mysql -u your_username -p

-u là tên người dùng và tùy chọn -p được sử dụng nếu tài khoản được bảo vệ bằng mật khẩu.

Máy chủ MySQL sẽ yêu cầu mật khẩu cơ sở dữ liệu của bạn. Nhập mật khẩu và nhấn ENTER.

Khi bạn đã đăng nhập, tạo một cơ sở dữ liệu có tên hello_world_db bằng cách sử dụng lệnh sau:

CREATE DATABASE hello_world_db;

Để xác minh xem bạn đã tạo cơ sở dữ liệu thành công hay chưa, bạn có thể sử dụng lệnh này:

SHOW DATABASES;

Kết quả của bạn sẽ tương tự như sau:

+--------------------+
|      Database      |
+--------------------+
| hello_world_db     |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

Sau khi tạo cơ sở dữ liệu mẫu, ngắt kết nối khỏi máy chủ MySQL:

mysql> QUIT

Bây giờ, bạn cần cài đặt một trình điều khiển thủ công cho cơ sở dữ liệu mà bạn chọn. Vì Sequelize chỉ cung cấp các tính năng ORM, nó không bao gồm các trình điều khiển cơ sở dữ liệu tích hợp sẵn. Do đó, bạn sẽ cần cài đặt các trình điều khiển theo sở thích của mình. Để làm điều đó, hãy điều hướng đến thư mục dự án bằng terminal và cài đặt trình điều khiển MySQL cho dự án bằng lệnh sau:

npm install --save mysql2

Trong trường hợp này, bạn đang sử dụng trình điều khiển cho MySQL.

Lưu ý: Vì hướng dẫn này sử dụng MySQL làm cơ sở dữ liệu, bạn đang sử dụng một trình điều khiển cho nó. Tùy thuộc vào cơ sở dữ liệu của bạn, bạn có thể cài đặt trình điều khiển bằng tay như sau:

  • npm install --save pg pg-hstore # Postgres
  • npm install --save mysql2
  • npm install --save mariadb
  • npm install --save sqlite3
  • npm install --save tedious # Microsoft SQL Server

Bây giờ bạn đã có một cơ sở dữ liệu mẫu, bạn có thể tạo ứng dụng Sequelize đầu tiên của mình với kết nối cơ sở dữ liệu.

Đang kết nối tới cơ sở dữ liệu MySQL

Trong phần này, bạn sẽ kết nối ứng dụng Node.js với cơ sở dữ liệu MySQL bằng Sequelize.

Để kết nối với cơ sở dữ liệu, mở server.js để chỉnh sửa bằng nano hoặc trình soạn thảo mã mà bạn ưa thích:

nano server.js

Ở đây, bạn sẽ tạo một kết nối cơ sở dữ liệu trong ứng dụng của mình bằng cách sử dụng một instance của Sequelize. Trong phương thức new Sequelize(), truyền các tham số máy chủ MySQL và thông tin đăng nhập cơ sở dữ liệu như sau, thay thế DATABASE_USERNAME và DATABASE_PASSWORD bằng thông tin đăng nhập của người dùng MySQL của bạn:

const Sequelize = require("sequelize");
const sequelize = new Sequelize(
 'hello_world_db',
 'DATABASE_USERNAME',
 'DATABASE_PASSWORD',
  {
    host: 'DATABASE_HOST',
    dialect: 'mysql'
  }
);

host là nơi máy chủ MySQL được lưu trữ, vì vậy bạn sẽ cần cung cấp URL máy chủ hoặc địa chỉ IP. Nếu bạn đang sử dụng máy chủ MySQL cài đặt cục bộ, bạn có thể thay thế DATABASE_HOST bằng localhost hoặc 127.0.0.1 làm giá trị.

Tương tự, nếu bạn đang sử dụng một máy chủ từ xa, hãy chắc chắn thay thế các giá trị kết nối cơ sở dữ liệu tương ứng với các chi tiết máy chủ từ xa thích hợp.

Lưu ý: Nếu bạn đang sử dụng bất kỳ phần mềm máy chủ cơ sở dữ liệu nào khác, bạn có thể thay thế tham số ngôn ngữ truy vấn tương ứng.

Tiếp theo, gọi phương thức authenticate() dựa trên Promise để khởi tạo kết nối cơ sở dữ liệu với ứng dụng. Để làm điều đó, hãy thêm khối mã sau vào tệp server.js của bạn:

...

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

Phương thức authenticate() được sử dụng để kết nối với cơ sở dữ liệu và kiểm tra xem thông tin đăng nhập được cung cấp có chính xác hay không. Ở đây, kết nối cơ sở dữ liệu được mở mặc định và cùng một kết nối có thể được sử dụng cho tất cả các truy vấn. Bất cứ khi nào bạn cần đóng kết nối, hãy gọi phương thức sequelize.close() sau lần gọi authenticate() này. Để tìm hiểu thêm về Sequelize, vui lòng xem hướng dẫn bắt đầu của họ.

Hầu hết các phương thức được Sequelize cung cấp đều là bất đồng bộ. Điều đó có nghĩa là bạn có thể chạy các quy trình trong ứng dụng của mình trong khi một khối mã bất đồng bộ đang thực thi. Ngoài ra, sau khi thực thi thành công khối mã bất đồng bộ, nó sẽ trả về một promise, chính là giá trị trả về cuối cùng của một quy trình. Do đó, trong các khối mã bất đồng bộ, bạn có thể sử dụng then(), catch(), và finally() để trả về dữ liệu đã được xử lý.

Tại thời điểm này, tệp server.js sẽ trông như sau:

const Sequelize = require("sequelize");

const sequelize = new Sequelize(
   'hello_world_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

Lưu và đóng tệp của bạn.

Trong thư mục dự án, chạy ứng dụng server.js bằng cách chạy lệnh sau:

node server.js

Kết quả của bạn sẽ trông giống như sau:

Connection has been established successfully!

Bạn đã tạo kết nối cơ sở dữ liệu thành công.

Trong bước này, bạn đã cài đặt Sequelize, tạo một cơ sở dữ liệu mẫu và sử dụng Sequelize để kết nối với cơ sở dữ liệu. Tiếp theo, bạn sẽ làm việc với các mô hình trong Sequelize.

Tạo Bảng Cơ Sở Dữ Liệu Sử Dụng Sequelize

Bây giờ bạn đã tạo một cơ sở dữ liệu MySQL mẫu, bạn có thể sử dụng Sequelize để tạo một bảng và điền dữ liệu vào đó. Trong Sequelize, các bảng cơ sở dữ liệu được gọi là models (mô hình). Một mô hình là một trừu tượng đại diện cho một bảng trong cơ sở dữ liệu. Các mô hình định nghĩa nhiều thông tin cho Sequelize, chẳng hạn như tên bảng, chi tiết cột và kiểu dữ liệu. Trong bước này, bạn sẽ tạo một mô hình Sequelize cho dữ liệu sách.

Để bắt đầu, tạo một tệp mới có tên book.model.js trong thư mục dự án:

nano book.model.js

Tương tự như bước trước, thêm mã Sequelize để khởi tạo cơ sở dữ liệu với một import mới cho DataTypes ở đầu file:

const { Sequelize, DataTypes } = require("sequelize");

Sequelize chứa nhiều loại dữ liệu tích hợp sẵn. Để truy cập các loại dữ liệu đó, bạn thêm một import cho DataTypes. Hướng dẫn này đề cập đến một số loại dữ liệu được sử dụng thường xuyên, chẳng hạn như STRING, INTEGER và DATEONLY. Để tìm hiểu thêm về các loại dữ liệu được hỗ trợ khác, bạn có thể tham khảo tài liệu chính thức của Sequelize.

Sau đó, bao gồm các dòng mà bạn đã sử dụng trước đây để tạo kết nối với cơ sở dữ liệu MySQL của bạn, cập nhật thông tin đăng nhập MySQL tương ứng:

...

const sequelize = new Sequelize(
   'hello_world_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

Tiếp theo, bạn sẽ tạo một mô hình có tên books, bao gồm title, author, release_date và ID subject. Để làm điều đó, hãy sử dụng phương thức sequelize.define() như được trình bày:

...

const Book = sequelize.define("books", {
   title: {
     type: DataTypes.STRING,
     allowNull: false
   },
   author: {
     type: DataTypes.STRING,
     allowNull: false
   },
   release_date: {
     type: DataTypes.DATEONLY,
   },
   subject: {
     type: DataTypes.INTEGER,
   }
});

Phương thức sequelize.define() định nghĩa một mô hình mới, đại diện cho một bảng trong cơ sở dữ liệu. Khối mã này tạo ra một bảng có tên là books và lưu trữ các bản ghi sách theo title, author, release_date, và subject.

Trong đoạn mã này, allowNull cho thấy giá trị cột của mô hình không thể là null. Tương tự, nếu bạn cần đặt một giá trị như vậy, bạn có thể sử dụng defaultValue: "value".

Tiếp theo, bạn sẽ thêm mô hình book vào cơ sở dữ liệu của mình. Để làm điều đó, bạn sẽ sử dụng phương thức sync() như sau:

...

sequelize.sync().then(() => {
   console.log('Book table created successfully!');
}).catch((error) => {
   console.error('Unable to create table : ', error);
});

Trong phương thức sync(), bạn đang yêu cầu Sequelize thực hiện một số thao tác với cơ sở dữ liệu. Với lệnh này, Sequelize sẽ tự động thực hiện một truy vấn SQL tới cơ sở dữ liệu và tạo một bảng, đồng thời in ra thông báo Bảng Book đã được tạo thành công!.

Như đã đề cập, phương thức sync() là một phương thức dựa trên promise, có nghĩa là nó cũng có thể thực hiện xử lý lỗi. Trong khối mã này, bạn sẽ kiểm tra xem bảng có được tạo thành công hay không. Nếu không, nó sẽ trả về một lỗi thông qua phương thức catch và in lỗi đó ra đầu ra.

Lưu ý: Bạn có thể quản lý việc đồng bộ hóa mô hình bằng cách truyền tham số force để buộc tạo một bảng mới nếu nó chưa tồn tại, hoặc sử dụng bảng hiện có. Dưới đây là một số ví dụ, có thể hữu ích cho bạn khi làm việc với Sequelize:

  • model.sync(): Lệnh này sẽ tạo bảng nếu bảng đó chưa tồn tại.
  • model.sync({ force: true }): Điều này tạo bảng bằng cách xóa bảng nếu bảng cùng tên đã tồn tại.

Mã cuối cùng sẽ trông giống như sau:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'hello_world_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

const Book = sequelize.define("books", {
   title: {
     type: DataTypes.STRING,
     allowNull: false
   },
   author: {
     type: DataTypes.STRING,
     allowNull: false
   },
   release_date: {
     type: DataTypes.DATEONLY,
   },
   subject: {
     type: DataTypes.INTEGER,
   }
});

sequelize.sync().then(() => {
   console.log('Book table created successfully!');
}).catch((error) => {
   console.error('Unable to create table : ', error);
});

Lưu và đóng tệp của bạn.

Chạy ứng dụng của bạn bằng cách sử dụng lệnh sau:

node book.model.js

Bạn sẽ nhận được kết quả sau trong dòng lệnh của mình:

Executing (default): SELECT 1+1 AS result
Executing (default): CREATE TABLE IF NOT EXISTS `books` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(255) NOT NULL, `author` VARCHAR(255) NOT NULL, `release_date` DATE, `subject` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Connection has been established successfully.
Executing (default): SHOW INDEX FROM `books`
Book table created successfully!

Trong kết quả đầu ra, bạn sẽ thấy nhật ký trả về chứa thông báo, Book table created successfully!. Bạn có thể xác minh điều này bằng cách kiểm tra cơ sở dữ liệu của mình để xem bảng books mới được tạo trong cơ sở dữ liệu hello_world_db.

Để xác minh việc tạo bảng mới, đăng nhập vào phiên MySQL của bạn:

mysql -u YOUR_USERNAME -p

Sau khi nhập mật khẩu, chuyển sang cơ sở dữ liệu mẫu:

USE hello_world_db;

Và sau đó chạy lệnh để hiển thị các bảng:

SHOW TABLES;

Kết quả của bạn sẽ tương tự như sau:

+---------------------------+
| Tables_in_hello_world_db |
+---------------------------+
| books                     |
+---------------------------+
1 row in set (0.00 sec)

Cuối cùng, ngắt kết nối khỏi máy chủ MySQL:

mysql> QUIT

Bạn đã xác nhận rằng việc tạo mô hình book đã thành công. Sử dụng quy trình này, bạn có thể tạo bất kỳ số lượng mô hình nào bằng cách làm theo cùng một thủ tục.

Trong bước này, bạn đã tạo một mô hình trong cơ sở dữ liệu và bắt đầu làm việc với mô hình bằng các phương thức tích hợp sẵn. Bạn cũng đã sử dụng các kiểu dữ liệu được Sequelize hỗ trợ để định nghĩa mô hình của mình. Tiếp theo, bạn sẽ làm việc với các truy vấn cơ bản của mô hình.

Sử dụng Sequelize để truy vấn cơ sở dữ liệu

Trong bước này, bạn sẽ sử dụng các truy vấn tích hợp sẵn của Sequelize để chèn, chọn, chọn với điều kiện, và xóa.

Chèn một bản ghi mới

Trong bước trước, bạn đã tạo một mô hình book trong cơ sở dữ liệu. Trong phần này, bạn sẽ chèn dữ liệu vào mô hình này.

Để bắt đầu, sao chép nội dung của book.model.js từ bước trước. Tạo một tệp mới có tên book.controller.js để xử lý logic truy vấn. Thêm mã từ book.model.js vào book.controller.js.

Trong book.controller.js, tìm phương thức sync(). Trong phương thức sync(), thêm các dòng được đánh dấu sau:

...

sequelize.sync().then(() => {
   console.log('Book table created successfully!');

   Book.create({
       title: "Clean Code",
       author: "Robert Cecil Martin",
       release_date: "2021-12-14",
       subject: 3
   }).then(res => {
       console.log(res)
   }).catch((error) => {
       console.error('Failed to create a new record : ', error);
   });

}).catch((error) => {
   console.error('Unable to create table : ', error);
});

Ở đây, bạn chèn một bản ghi sách mới vào mô hình books mà bạn đã tạo trước đó bằng phương thức sync(), phương thức này hỗ trợ thêm các bản ghi mới vào các mô hình đã tạo. Khi phương thức sync() thực thi thành công, nó sẽ chạy phương thức then(). Bên trong phương thức then(), bạn gọi phương thức create() để chèn các bản ghi mới vào mô hình.

Bạn sử dụng phương thức create() để truyền dữ liệu mà bạn cần thêm vào cơ sở dữ liệu dưới dạng một đối tượng. Phần mã được làm nổi bật sẽ chèn một mục mới vào bảng books hiện có của bạn. Trong ví dụ này, bạn thêm Clean Code của Robert Cecil Martin, được phân loại với ID subject là 3. Bạn có thể sử dụng cùng mã này, được cập nhật với thông tin cho các cuốn sách khác, để thêm các bản ghi mới vào cơ sở dữ liệu của mình.

Lưu và đóng tệp.

Chạy ứng dụng bằng cách sử dụng lệnh sau:

node book.controller.js

Kết quả của bạn sẽ giống như sau:

books {
  dataValues:
   { id: 1,
     title: 'Clean Code',
     author: 'Robert Cecil Martin',
     release_date: '2021-12-14',
     subject: 3,
     updatedAt: 2021-12-14T10:12:16.644Z,
   ...
}

Bạn đã chèn một bản ghi mới vào mô hình mà bạn đã tạo trong cơ sở dữ liệu. Bạn có thể tiếp tục thêm nhiều bản ghi khác bằng cách sử dụng cùng một quy trình.

Chọn tất cả bản ghi

Trong phần này, bạn sẽ chọn và lấy tất cả các bản ghi sách từ cơ sở dữ liệu bằng phương thức findAll(). Để làm điều đó, trước tiên hãy mở book.controller.js và xóa phương thức Book.create() trước đó. Trong phương thức sync(), thêm phương thức Book.findAll() như sau:

...

sequelize.sync().then(() => {

    Book.findAll().then(res => {
        console.log(res)
    }).catch((error) => {
        console.error('Failed to retrieve data : ', error);
    });

}).catch((error) => {
    console.error('Unable to create table : ', error);
});

...

Lưu và đóng tệp.

Tiếp theo, chạy lại ứng dụng bằng cách sử dụng lệnh sau:

node book.controller.js

Kết quả của bạn sẽ giống như sau:

[
  books {
    dataValues: {
      id: 1,
      title: 'Clean Code',
      author: 'Robert Cecil Martin',
      release_date: '2020-01-01',
      subject: 3,
      createdAt: 2021-02-22T09:13:55.000Z,
      updatedAt: 2021-02-22T09:13:55.000Z
    },
    _previousDataValues: {
      id: 1,
      title: 'Clean Code',
      author: 'Robert Cecil Martin',
      release_date: '2020-01-01',
      subject: 3,
      createdAt: 2021-02-22T09:13:55.000Z,
      updatedAt: 2021-02-22T09:13:55.000Z
    },
...
]

Đầu ra chứa tất cả dữ liệu sách dưới dạng một mảng đối tượng. Bạn đã sử dụng thành công phương thức findAll() của Sequelize để trả về tất cả dữ liệu sách từ cơ sở dữ liệu.

Lựa chọn với mệnh đề where

Trong phần này, bạn sẽ chọn các giá trị có điều kiện bằng cách sử dụng câu lệnh where. Câu lệnh where được sử dụng để chỉ định một điều kiện khi truy xuất dữ liệu. Trong hướng dẫn này, bạn sẽ lấy một cuốn sách theo ID bản ghi cụ thể từ cơ sở dữ liệu bằng phương thức findOne().

Để làm điều đó, mở book.controller.js để chỉnh sửa, xóa phương thức findAll(), và thêm các dòng sau:

...

sequelize.sync().then(() => {

    Book.findOne({
        where: {
            id: 1
        }
    }).then(res => {
        console.log(res)
    }).catch((error) => {
        console.error('Failed to retrieve data : ', error);
    });

}).catch((error) => {
    console.error('Unable to create table : ', error);
});

Ở đây, bạn chọn một bản ghi sách cụ thể từ cơ sở dữ liệu bằng phương thức findOne() với tùy chọn where. Trong ví dụ này, bạn đang lấy dữ liệu cuốn sách có id bằng 1.

Lưu và đóng tệp.

Tiếp theo, chạy ứng dụng:

node book.controller.js

Kết quả của bạn sẽ giống như sau:

books {
  dataValues: {
    id: 1,
    title: 'Clean Code',
    author: 'Robert Cecil Martin',
    release_date: '2020-01-01',
    subject: 'Science',
    createdAt: 2021-02-22T09:13:55.000Z,
    updatedAt: 2021-02-22T09:13:55.000Z
  },
  ...
}

Bạn đã sử dụng thành công các mệnh đề where để lấy dữ liệu từ các mô hình Sequelize. Bạn có thể sử dụng mệnh đề where trong ứng dụng cơ sở dữ liệu để lấy dữ liệu theo điều kiện.

Xóa một bản ghi

Để xóa một bản ghi cụ thể khỏi mô hình cơ sở dữ liệu, bạn sử dụng phương thức destroy() với tùy chọn where. Để làm điều đó, mở book.controller.js, xóa phương thức findOne() và thêm các dòng được đánh dấu sau:

...
sequelize.sync().then(() => {

  Book.destroy({
      where: {
        id: 2
      }
  }).then(() => {
      console.log("Successfully deleted record.")
  }).catch((error) => {
      console.error('Failed to delete record : ', error);
  });

}).catch((error) => {
    console.error('Unable to create table : ', error);
});

Ở đây, bạn xóa một bản ghi sách khỏi cơ sở dữ liệu bằng cách sử dụng phương thức destroy() với tùy chọn where và truyền vào id của cuốn sách cần xóa. Bạn sẽ xóa bản ghi sách có id bằng 2.

Lưu và đóng tệp.

Tiếp theo, chạy ứng dụng:

node book.controller.js

Kết quả của bạn sẽ trông như sau:

Successfully deleted record.

Bản ghi đã bị xóa.

Trong bước này, bạn đã thử nghiệm với mô hình cơ sở dữ liệu và truy vấn mô hình của mình. Bạn đã khởi tạo cơ sở dữ liệu, tạo các mô hình, chèn bản ghi, truy xuất bản ghi, truy xuất bản ghi với các điều kiện sử dụng mệnh đề where và xóa các bản ghi được chọn. Với kiến thức về Sequelize này, bạn sẽ tạo các mối quan hệ trong Sequelize. Sau đó, bạn sẽ có thể định nghĩa và làm việc với nhiều loại quan hệ khác nhau sử dụng các mô hình Sequelize.

Tạo liên kết bằng Sequelize

Trong bước này, bạn sẽ sử dụng các loại quan hệ tiêu chuẩn mà Sequelize hỗ trợ: một-một, một-nhiều và nhiều-nhiều. Bạn sẽ sử dụng dữ liệu mẫu về sinh viên, khóa học và cấp độ điểm.

Sequelize sử dụng các loại liên kết dựa trên các mối quan hệ cơ sở dữ liệu sau:

  • Mối quan hệ một-một: Mối quan hệ một-một nghĩa là một bản ghi trong một bảng liên kết chính xác với một bản ghi trong bảng khác. Trong Sequelize, bạn có thể sử dụng các kết hợp belongsTo() và hasOne() để tạo loại mối quan hệ này.

  • mối quan hệ một-nhiều: Mối quan hệ một-nhiều có nghĩa là một bản ghi trong một bảng liên kết với nhiều bản ghi trong một bảng khác. Với Sequelize, bạn có thể sử dụng phương thức liên kết hasMany() để tạo loại mối quan hệ này.

  • mối quan hệ nhiều-nhiều: Mối quan hệ nhiều-nhiều có nghĩa là nhiều bản ghi trong một bảng liên kết với nhiều bản ghi trong bảng khác. Với Sequelize, bạn có thể sử dụng các kết hợp belongsToMany() để tạo loại mối quan hệ này.

Trước khi tạo các liên kết này, bạn sẽ tạo trước một cơ sở dữ liệu mới có tên student_db và thêm các mô hình mới cùng một số dữ liệu mẫu cho sinh viên, khóa học và cấp bậc học.

Để tạo cơ sở dữ liệu, hãy làm theo quy trình giống như trong phần Cài đặt và Cấu hình Sequelize để đăng nhập vào MySQL và tạo một cơ sở dữ liệu có tên student_db. Khi cơ sở dữ liệu mới đã được tạo, hãy đăng xuất khỏi MySQL. Tiếp theo, bạn sẽ bắt đầu tạo các liên kết cơ sở dữ liệu.

Tạo quan hệ một-một với belongsTo()

Trong phần này, bạn sẽ tạo một mối quan hệ một-một sử dụng các mô hình Sequelize. Hãy tưởng tượng bạn muốn lấy thông tin chi tiết của một học sinh cùng với cấp lớp của họ. Vì một học sinh chỉ có thể có một cấp lớp duy nhất, loại quan hệ này là quan hệ một-một và bạn có thể sử dụng phương thức belongsTo().

Lưu ý: Có sự khác biệt giữa belongsTo() và hasOne(). belongsTo() sẽ thêm foreignKey vào bảng nguồn, trong khi hasOne() sẽ thêm nó vào bảng đích. Trong mọi trường hợp, nếu cả hai mối quan hệ được sử dụng cùng lúc, nó sẽ hoạt động như mối quan hệ một-một hai chiều của Sequelize.

Phương thức belongsTo() cho phép bạn tạo mối quan hệ một-một giữa hai mô hình Sequelize. Trong ví dụ này, bạn đang sử dụng các mô hình Student và Grade.

Tạo một tệp mới có tên one_to_one.js. Như bạn đã làm trong phần trước, Kết nối với cơ sở dữ liệu MySQL, thêm các dòng để tạo kết nối với cơ sở dữ liệu và xác thực người dùng MySQL của bạn vào đầu tệp. Hãy chắc chắn cập nhật thông tin đăng nhập MySQL nếu cần:

const { Sequelize, DataTypes } = require("sequelize");

const sequelize = new Sequelize(
   'student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

Trong phần này, bạn sẽ tạo ba mô hình trong cơ sở dữ liệu mới student_db: Student, Grade và Course. Bạn sẽ bắt đầu bằng việc tạo các mô hình Student và Grade. Sau đó, trong bước này, bạn sẽ tạo mô hình Course.

Đối với mô hình Student, thêm khối mã sau vào one_to_one.js:

...
const Student = sequelize.define("students", {
   student_id: {
       type: DataTypes.UUID,
       defaultValue: DataTypes.UUIDV4,
       primaryKey: true,
   },
   name: {
       type: DataTypes.STRING,
       allowNull: false
   }
});

Mô hình sinh viên này chứa hai cột: student_id và name.

Tiếp theo, thêm một khối mã cho mô hình Grade:

...
const Grade = sequelize.define("grades", {
   grade: {
       type: DataTypes.INTEGER,
       allowNull: false
   }
});

Mô hình Grade chứa cột grade.

Để minh họa các mối quan hệ, bạn sẽ cần thêm dữ liệu mẫu vào cơ sở dữ liệu. Để làm điều đó, bạn sẽ sử dụng phương thức bulk(). Thay vì chèn dữ liệu vào các hàng từng cái một, phương thức bulkCreate() cho phép bạn chèn nhiều hàng vào các mô hình cơ sở dữ liệu cùng một lúc.

Vậy bây giờ, hãy nhập dữ liệu Grade và Student vào các mô hình tương ứng trong cơ sở dữ liệu như sau:

...
const grade_data = [{grade : 9}, {grade : 10}, {grade : 11}]

const student_data = [
   {name : "John Baker", gradeId: 2},
   {name : "Max Butler", gradeId: 1},
   {name : "Ryan Fisher", gradeId: 3},
   {name : "Robert Gray", gradeId: 2},
   {name : "Sam Lewis", gradeId: 1}
]

sequelize.sync({ force: true }).then(() => {
   Grade.bulkCreate(grade_data, { validate: true }).then(() => {
       Student.bulkCreate(student_data, { validate: true }).then(() => {
           …
       }).catch((err) => { console.log(err); });
   }).catch((err) => { console.log(err); });
}).catch((error) => {
   console.error('Unable to create the table : ', error);
});

Ở đây, bạn cung cấp dữ liệu mẫu và chèn dữ liệu vào các mô hình Student và Grade. Với cơ sở dữ liệu, các mô hình và dữ liệu mẫu đã sẵn sàng, bạn đã sẵn sàng để tạo các liên kết.

Trong one-to-one.js, thêm dòng sau vào dưới khối student_data:

...
Student.belongsTo(Grade);

Tiếp theo, bạn sẽ cần kiểm tra xem mối quan hệ có hoạt động đúng không. Để làm điều đó, bạn có thể lấy tất cả dữ liệu của học sinh kèm theo cấp lớp liên quan bằng cách truyền tham số include vào phương thức findAll().

Vì bạn cần lấy cấp lớp của học sinh, bạn sẽ truyền Grade làm mô hình. Trong phương thức sequelize.sync(), thêm các dòng được đánh dấu như sau:

...
sequelize.sync({ force: true }).then(() => {
   Grade.bulkCreate(grade_data, { validate: true }).then(() => {
       Student.bulkCreate(student_data, { validate: true }).then(() => {
           Student.findAll({
               include: [{
                   model: Grade
               }]
           }).then(result => {
               console.log(result)
           }).catch((error) => {
               console.error('Failed to retrieve data : ', error);
           });
       }).catch((err) => { console.log(err); });
   }).catch((err) => { console.log(err); });
}).catch((error) => {
   console.error('Unable to create the table : ', error);
});

Mã hoàn chỉnh trông như sau:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );
sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

const Student = sequelize.define("students", {
   student_id: {
       type: DataTypes.UUID,
       defaultValue: DataTypes.UUIDV4,
       primaryKey: true,
   },
   name: {
       type: DataTypes.STRING,
       allowNull: false
   }
});

const Grade = sequelize.define("grades", {
   grade: {
       type: DataTypes.INTEGER,
       allowNull: false
   }
});

const grade_data = [{grade : 9}, {grade : 10}, {grade : 11}]

const student_data = [
   {name : "John Baker", gradeId: 2},
   {name : "Max Butler", gradeId: 1},
   {name : "Ryan Fisher", gradeId: 3},
   {name : "Robert Gray", gradeId: 2},
   {name : "Sam Lewis", gradeId: 1}
]

// One-To-One association
Student.belongsTo(Grade);

sequelize.sync({ force: true }).then(() => {
   Grade.bulkCreate(grade_data, { validate: true }).then(() => {
       Student.bulkCreate(student_data, { validate: true }).then(() => {
           Student.findAll({
               include: [{
                   model: Grade
               }]
           }).then(result => {
               console.log(result)
           }).catch((error) => {
               console.error('Failed to retrieve data : ', error);
           });
       }).catch((err) => { console.log(err); });
   }).catch((err) => { console.log(err); });
}).catch((error) => {
   console.error('Unable to create the table : ', error);
});

Lưu và đóng tệp của bạn.

Chạy tệp bằng cách sử dụng lệnh sau:

node one_to_one.js

Kết quả sẽ dài, và bạn sẽ thấy dữ liệu của tất cả học sinh cùng với các cấp bậc điểm. Dưới đây là một đoạn nhỏ của kết quả hiển thị dữ liệu học sinh:

students {
    dataValues:
   { student_id: '3e786a8f-7f27-4c59-8e9c-a8c606892288',
       name: 'Sam Lewis',
       createdAt: 2021-12-16T08:49:38.000Z,
       updatedAt: 2021-12-16T08:49:38.000Z,
       gradeId: 1,
       grade: [grades] },
    _previousDataValues:
...

Tùy thuộc vào các công cụ dòng lệnh mà bạn đang sử dụng, kết quả có thể được hiển thị dưới dạng xem mở rộng hoặc không. Nếu là chế độ xem mở rộng, nó sẽ in đối tượng grade đã mở rộng dưới dạng kết quả.

Trong phần này, bạn đã tạo một mối quan hệ một-một bằng cách sử dụng lệnh Student.belongsTo(Grade); và nhận được chi tiết theo mối liên kết mà bạn đã tạo.

Tạo mối quan hệ Một-nhiều với hasMany()

Trong phần này, bạn sẽ tạo một mối quan hệ một-nhiều sử dụng các mô hình Sequelize. Hãy tưởng tượng bạn muốn lấy tất cả các học sinh liên quan đến một cấp lớp được chọn. Vì một cấp lớp cụ thể có thể có nhiều học sinh, đây là một mối quan hệ một-nhiều.

Để bắt đầu, sao chép nội dung của one_to_one.js vào một tệp mới có tên one_to_many.js. Trong one_to_many.js, xóa các dòng sau khối student_data. Tệp one_to_many.js của bạn sẽ trông như sau:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

const Student = sequelize.define("students", {
   student_id: {
       type: DataTypes.UUID,
       defaultValue: DataTypes.UUIDV4,
       primaryKey: true,
   },
   name: {
       type: DataTypes.STRING,
       allowNull: false
   }
});
const Grade = sequelize.define("grades", {
   grade: {
       type: DataTypes.INTEGER,
       allowNull: false
   }
});

const grade_data = [ {grade : 9}, {grade : 10}, {grade : 11}]

const student_data = [
   {name : "John Baker", gradeId: 2},
   {name : "Max Butler", gradeId: 1},
   {name : "Ryan Fisher", gradeId: 3},
   {name : "Robert Gray", gradeId: 2},
   {name : "Sam Lewis", gradeId: 1}
]

Sau khối student_data, sử dụng phương thức hasMany() để tạo một mối quan hệ mới:

...
Grade.hasMany(Student)

Phương thức hasMany() cho phép bạn tạo mối quan hệ một-nhiều giữa hai mô hình Sequelize. Ở đây, bạn đang sử dụng các mô hình Grade và Student.

Tiếp theo, thêm phương thức sequelize.sync() cùng với phương thức findAll() dưới dòng hasMany():

...
sequelize.sync({ force: true }).then(() => {
   Grade.bulkCreate(grade_data, { validate: true }).then(() => {
       Student.bulkCreate(student_data, { validate: true }).then(() => {
           Grade.findAll({
               where: {
                   grade: 9
               },
               include: [{
                   model: Student
               }]
           }).then(result => {
               console.dir(result, { depth: 5 });
           }).catch((error) => {
               console.error('Failed to retrieve data : ', error);
           });
       }).catch((err) => { console.log(err); });
   }).catch((err) => { console.log(err); });
}).catch((error) => {
   console.error('Unable to create table : ', error);
});

Ở đây, bạn đang cố gắng truy cập tất cả học sinh ở một cấp lớp cụ thể—trong trường hợp này, tất cả các học sinh lớp 9. Bạn cũng đã thêm mô hình Student trong tùy chọn include.

Đây là toàn bộ mã:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
  );

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

const Student = sequelize.define("students", {
   student_id: {
       type: DataTypes.UUID,
       defaultValue: DataTypes.UUIDV4,
       primaryKey: true,
   },
   name: {
       type: DataTypes.STRING,
       allowNull: false
   }
});
const Grade = sequelize.define("grades", {
   grade: {
       type: DataTypes.INTEGER,
       allowNull: false
   }
});

const grade_data = [ {grade : 9}, {grade : 10}, {grade : 11}]

const student_data = [
   {name : "John Baker", gradeId: 2},
   {name : "Max Butler", gradeId: 1},
   {name : "Ryan Fisher", gradeId: 3},
   {name : "Robert Gray", gradeId: 2},
   {name : "Sam Lewis", gradeId: 1}
]

// One-To-Many relationship
Grade.hasMany(Student);

sequelize.sync({ force: true }).then(() => {
   Grade.bulkCreate(grade_data, { validate: true }).then(() => {
       Student.bulkCreate(student_data, { validate: true }).then(() => {
           Grade.findAll({
               where: {
                   grade: 9
               },
               include: [{
                   model: Student
               }]
           }).then(result => {
               console.dir(result, { depth: 5 });
           }).catch((error) => {
               console.error('Failed to retrieve data : ', error);
           });
       }).catch((err) => { console.log(err); });
   }).catch((err) => { console.log(err); });
}).catch((error) => {
   console.error('Unable to create table : ', error);
});

Lưu và đóng tệp của bạn.

Chạy tệp bằng lệnh sau:

node one_to_many.js

Kết quả sẽ trông giống như sau. Nó sẽ khá dài, nhưng tất cả học sinh lớp 9 sẽ được trả về như sau:

[ grades {
    dataValues:
     { id: 1,
       grade: 9,
       createdAt: 2021-12-20T05:12:31.000Z,
       updatedAt: 2021-12-20T05:12:31.000Z,
       students:
        [ students {
            dataValues:
             { student_id: '8a648756-4e22-4bc0-8227-f590335f9965',
               name: 'Sam Lewis',
               createdAt: 2021-12-20T05:12:31.000Z,
               updatedAt: 2021-12-20T05:12:31.000Z,
               gradeId: 1 },
...
          students {
            dataValues:
             { student_id: 'f0304585-91e5-4efc-bdca-501b3dc77ee5',
               name: 'Max Butler',
               createdAt: 2021-12-20T05:12:31.000Z,
               updatedAt: 2021-12-20T05:12:31.000Z,
               gradeId: 1 },
...

Trong phần này, bạn đã tạo một mối quan hệ một-nhiều bằng cách sử dụng phương thức Grade.hasMany(Student);. Trong kết quả, bạn đã truy xuất các chi tiết theo mối liên kết mà bạn đã tạo.

Tạo quan hệ Nhiều-nhiều với belongsToMany()

Trong phần này, bạn sẽ tạo các mối quan hệ nhiều-nhiều sử dụng các mô hình Sequelize. Ví dụ, hãy tưởng tượng một tình huống nơi sinh viên đăng ký các khóa học. Một sinh viên có thể đăng ký nhiều khóa học và một khóa học có thể có nhiều sinh viên. Đây là mối quan hệ nhiều-nhiều. Để triển khai điều này bằng Sequelize, bạn sẽ sử dụng các mô hình Student, Course và StudentCourse với phương thức belongsToMany().

Để bắt đầu, tạo một tệp có tên many_to_many.js và thêm các khối mã khởi tạo và xác thực cơ sở dữ liệu như sau. (Bạn có thể tái sử dụng các khối mã từ ví dụ one_to_many.js trước đó.) Hãy chắc chắn cập nhật các giá trị kết nối cơ sở dữ liệu được tô sáng theo nhu cầu.

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
);

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.');
}).catch((error) => {
    console.error('Unable to connect to the database: ', error);
});

Tiếp theo, bạn sẽ tạo các mô hình cơ sở dữ liệu cho mối quan hệ nhiều-nhiều: Student và Course. Sau đó, bạn sẽ thêm một số dữ liệu mẫu vào các mô hình đó.

...

const Student = sequelize.define("students", {
    student_id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
    },
    name: {
        type: DataTypes.STRING,
        allowNull: false
    }
});

const Course = sequelize.define("courses", {
    course_name: {
        type: DataTypes.STRING,
        allowNull: false
    }
});

const StudentCourse = sequelize.define('StudentCourse', {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
      allowNull: false
    }
  });

const course_data = [
    {course_name : "Science"},
    {course_name : "Maths"},
    {course_name : "History"}
]

const student_data = [
    {name : "John Baker", courseId: 2},
    {name : "Max Butler", courseId: 1},
    {name : "Ryan Fisher", courseId: 3},
    {name : "Robert Gray", courseId: 2},
    {name : "Sam Lewis", courseId: 1}
]

const student_course_data = [
    {studentId : 1, courseId: 1},
    {studentId : 2, courseId: 1},
    {studentId : 2, courseId: 3},
    {studentId : 3, courseId: 2},
    {studentId : 1, courseId: 2},
]

Ở đây, bạn tạo các mô hình Student và Course và cung cấp một số dữ liệu mẫu. Bạn cũng thiết lập một courseID, mà bạn sẽ sử dụng để truy xuất sinh viên theo loại quan hệ này.

Cuối cùng, bạn đã định nghĩa một mô hình mới có tên là StudentCourse, quản lý dữ liệu quan hệ giữa Student và Course. Trong ví dụ này, studentId 1 được ghi danh vào courseId 1 và courseId 2.

Bạn đã hoàn tất việc khởi tạo cơ sở dữ liệu và thêm dữ liệu mẫu vào cơ sở dữ liệu. Tiếp theo, tạo các mối quan hệ nhiều-nhiều bằng cách sử dụng phương thức belongsToMany() như minh họa sau:

...
Course.belongsToMany(Student, { through: 'StudentCourse'})
Student.belongsToMany(Course, { through: 'StudentCourse'})

Trong phương thức belongsToMany(), bạn truyền cấu hình through với tên của mô hình làm tùy chọn cấu hình. Trong trường hợp này, đó là StudentCourse. Đây là bảng quản lý các mối quan hệ nhiều-nhiều.

Cuối cùng, bạn có thể kiểm tra xem mối quan hệ có hoạt động đúng hay không bằng cách truy xuất tất cả dữ liệu khóa học cùng với các sinh viên liên quan. Bạn sẽ thực hiện việc này bằng cách truyền tham số include vào bên trong phương thức findAll(). Thêm các dòng sau vào many_to_many.js:

...
sequelize.sync({ force: true }).then(() => {
    Course.bulkCreate(course_data, { validate: true }).then(() => {
        Student.bulkCreate(student_data, { validate: true }).then(() => {
            StudentCourse.bulkCreate(student_course_data, { validate: true }).then(() => {
                Course.findAll({
                    include: {
                        model: Student,
                    },
                }).then(result => {
                    console.log(result);
                }).catch((error) => {
                    console.error('Failed to retrieve data : ', error);
                });
            }).catch((error) => {
                console.log(error);
            });
        }).catch((error) => {
            console.log(error);
        });
    }).catch((error) => {
        console.log(error);
    });
}).catch((error) => {
    console.error('Unable to create table : ', error);
});

Mã hoàn chỉnh trông như sau:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
);

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database: ', error);
});

const Student = sequelize.define("students", {
    student_id: {
       type: DataTypes.UUID,
       defaultValue: DataTypes.UUIDV4,
    },
    name: {
       type: DataTypes.STRING,
       allowNull: false
    }
});

const Course = sequelize.define("courses", {
    course_name: {
        type: DataTypes.STRING,
        allowNull: false
    }
});

const StudentCourse = sequelize.define('StudentCourse', {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
      allowNull: false
    }
  });

const course_data = [
    {course_name : "Science"},
    {course_name : "Maths"},
    {course_name : "History"}
]

const student_data = [
    {name : "John Baker", courseId: 2},
    {name : "Max Butler", courseId: 1},
    {name : "Ryan Fisher", courseId: 3},
    {name : "Robert Gray", courseId: 2},
    {name : "Sam Lewis", courseId: 1}
]

const student_course_data = [
    {studentId : 1, courseId: 1},
    {studentId : 2, courseId: 1},
    {studentId : 2, courseId: 3},
    {studentId : 3, courseId: 2},
    {studentId : 1, courseId: 2},
]

Course.belongsToMany(Student, { through: 'StudentCourse'})
Student.belongsToMany(Course, { through: 'StudentCourse'})

sequelize.sync({ force: true }).then(() => {
    Course.bulkCreate(course_data, { validate: true }).then(() => {
        Student.bulkCreate(student_data, { validate: true }).then(() => {
            StudentCourse.bulkCreate(student_course_data, { validate: true }).then(() => {
                Course.findAll({
                    include: {
                        model: Student,
                    },
                }).then(result => {
                    console.log(result);
                }).catch((error) => {
                    console.error('Failed to retrieve data : ', error);
                });
            }).catch((error) => {
                console.log(error);
            });
        }).catch((error) => {
            console.log(error);
        });
    }).catch((error) => {
        console.log(error);
    });
}).catch((error) => {
    console.error('Unable to create table : ', error);
});

Lưu và đóng tệp.

Chạy tệp bằng lệnh sau:

node many_to_many.js

Kết quả sẽ dài, nhưng sẽ trông giống như sau:

[ courses {
    dataValues:
     { id: 1,
       course_name: 'Science',
       createdAt: 2022-05-11T04:27:37.000Z,
       updatedAt: 2022-05-11T04:27:37.000Z,
       students: [Array] },
    _previousDataValues:
     { id: 1,
       course_name: 'Science',
       createdAt: 2022-05-11T04:27:37.000Z,
       updatedAt: 2022-05-11T04:27:37.000Z,
       students: [Array] },
    _changed: Set {},
    _options:
     { isNewRecord: false,
       _schema: null,
       _schemaDelimiter: '',
       include: [Array],
       includeNames: [Array],
       includeMap: [Object],
       includeValidated: true,
       attributes: [Array],
       raw: true },
    isNewRecord: false,
    students: [ [students], [students] ] },
  courses {
    dataValues:
     { id: 2,
       course_name: 'Maths',
       createdAt: 2022-05-11T04:27:37.000Z,
       updatedAt: 2022-05-11T04:27:37.000Z,
       students: [Array] },
    _previousDataValues:
...

Như bạn có thể thấy trong kết quả này, các khóa học có liên quan tới sinh viên đã được truy xuất. Trong khối courses, bạn sẽ thấy các giá trị id riêng biệt chỉ ra từng khóa học. Ví dụ, id: 1 được liên kết với course_name: Science cho lớp Khoa học, trong khi id: 2 là lớp Toán, và cứ như vậy.

Trong cơ sở dữ liệu, bạn có thể thấy ba bảng được tạo ra với dữ liệu mẫu mà bạn đã chèn vào.

Trong bước này, bạn đã sử dụng Sequelize để tạo các liên kết một-một, một-nhiều và nhiều-nhiều. Tiếp theo, bạn sẽ làm việc với các truy vấn thô.

Làm việc với các truy vấn thô

Trong bước này, bạn sẽ làm việc với các truy vấn thô trong Sequelize. Ở các bước trước, bạn đã sử dụng các phương thức tích hợp sẵn của Sequelize, chẳng hạn như insert() và findAll(), để xử lý việc chèn dữ liệu và truy xuất từ cơ sở dữ liệu. Bạn có thể đã nhận thấy rằng những phương thức đó tuân theo một mẫu nhất định khi viết truy vấn. Tuy nhiên, khi sử dụng truy vấn thô, bạn không cần phải lo lắng về các phương thức và mẫu của Sequelize. Sử dụng kiến thức về truy vấn SQL của mình, bạn có thể thực hiện nhiều loại truy vấn trong Sequelize từ đơn giản đến nâng cao.

Đây là một ví dụ về các truy vấn thô thực hiện hành động chọn tất cả các giá trị từ một bảng cụ thể, xóa các giá trị đã chọn theo điều kiện, và cập nhật bảng với các giá trị đã cho.

SELECT * FROM table_name;
DELETE FROM table_name WHERE condition;
UPDATE table_name SET y = 42 WHERE x = 12;

Trong Sequelize, các truy vấn thô có thể được sử dụng chủ yếu với hai phương pháp: thay thế bằng mảng và thay thế bằng đối tượng. Khi bạn truyền giá trị vào truy vấn SQL, bạn có thể sử dụng mảng hoặc đối tượng để thực hiện việc thay thế đó.

Trước khi viết một truy vấn thô, bạn cần cung cấp dữ liệu sinh viên trong một cơ sở dữ liệu mẫu. Theo phần trước, Tạo cơ sở dữ liệu mẫu, đăng nhập vào MySQL, tạo một cơ sở dữ liệu có tên sample_student_db, và đăng xuất khỏi MySQL.

Tiếp theo, bạn sẽ thêm một số dữ liệu thô để bắt đầu làm việc với các truy vấn thô. Tạo một tệp mới có tên add_student_records.js và thêm các khối mã sau đây, chứa các phương thức Sequelize đã thảo luận trước đó là authenticate(), sync() và bulkCreate().

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'sample_student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
   );

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.');
}).catch((error) => {
    console.error('Unable to connect to the database: ', error);
});

const Student = sequelize.define("students", {
    student_id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
        primaryKey: true,
    },
    name: {
        type: DataTypes.STRING,
        allowNull: false
    }
});

const student_data = [
    {name : "John Baker"},
    {name : "Max Butler"},
    {name : "Ryan Fisher"},
    {name : "Robert Gray"},
    {name : "Sam Lewis"}
]

sequelize.sync({ force: true }).then(() => {

    Student.bulkCreate(student_data, { validate: true }).then((result) => {
        console.log(result);
    }).catch((error) => {
        console.log(error);
    });

}).catch((error) => {
    console.error('Unable to create table : ', error);
});

Ở đây, bạn khởi tạo kết nối cơ sở dữ liệu, tạo mô hình, và chèn một vài bản ghi sinh viên vào cơ sở dữ liệu mới.

Lưu và đóng tệp.

Tiếp theo, chạy tập lệnh này bằng lệnh sau:

node add_student_records.js

Đầu ra sẽ giống như sau. Nó sẽ khá dài, nhưng tất cả các bản ghi sinh viên mà bạn đã chèn sẽ được trả về như sau. Lưu ý rằng vì student_id là một giá trị UUID (Nhận diện Duy nhất Toàn cầu) được tạo tự động, nó sẽ khác nhau tùy thuộc vào người dùng.

Executing (default): SELECT 1+1 AS result
Executing (default): DROP TABLE IF EXISTS `students`;
Connection has been established successfully.
Executing (default): DROP TABLE IF EXISTS `students`;
Executing (default): CREATE TABLE IF NOT EXISTS `students` (`student_id` CHAR(36) BINARY , `name` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`student_id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `students`
Executing (default): INSERT INTO `students` (`student_id`,`name`,`createdAt`,`updatedAt`) VALUES ('45d1f26c-ba76-431f-ac5f-f41282351710','John Baker','2022-06-03 07:27:49','2022-06-03 07:27:49'),('1cb4e34d-bfcf-4a97-9624-e400b9a1a5f2','Max Butler','2022-06-03 07:27:49','2022-06-03 07:27:49'),('954c576b-ba1c-4dbc-a5c6-8eaf22bbbb04','Ryan Fisher','2022-06-03 07:27:49','2022-06-03 07:27:49'),('e0f15cd3-0025-4032-bfe8-774e38e14c5f','Robert Gray','2022-06-03 07:27:49','2022-06-03 07:27:49'),('826a0ec9-edd0-443f-bb12-068235806659','Sam Lewis','2022-06-03 07:27:49','2022-06-03 07:27:49');
[
  students {
    dataValues: {
      student_id: '45d1f26c-ba76-431f-ac5f-f41282351710',
      name: 'John Baker',
      createdAt: 2022-06-03T07:27:49.453Z,
      updatedAt: 2022-06-03T07:27:49.453Z
    },
    _previousDataValues: {
      name: 'John Baker',
      student_id: '45d1f26c-ba76-431f-ac5f-f41282351710',
      createdAt: 2022-06-03T07:27:49.453Z,
      updatedAt: 2022-06-03T07:27:49.453Z
    },

…

Trong phần tiếp theo, bạn sẽ áp dụng các truy vấn thô sử dụng một trong các kết quả student_id trong khối mã ở trên. Hãy sao chép nó lại để bạn có sẵn cho các phần tiếp theo, nơi bạn sẽ sử dụng phương thức query() để thay thế mảng và đối tượng.

Thay thế mảng

Trong phần này, bạn sẽ sử dụng phương thức query() để thay thế mảng. Với phương thức này, Sequelize có thể thực thi các truy vấn SQL thô hoặc đã được chuẩn bị sẵn.

Để bắt đầu, sao chép nội dung của tệp server.js từ Bước 1, vì nó bao gồm phương thức khởi tạo Sequelize() và khởi tạo cơ sở dữ liệu. Dán nội dung vào một tệp mới có tên array_raw_query.js. Cập nhật tên cơ sở dữ liệu thành sample_student_db:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'sample_student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
   );

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.');
}).catch((error) => {
    console.error('Unable to connect to the database: ', error);
});

Ở cuối tệp, thêm khối mã sau để thay thế mảng, đảm bảo thay REPLACE_STUDENT_ID bằng giá trị student_id mà bạn đã sao chép trong phần trước.

...
sequelize.query(
    'SELECT * FROM students WHERE student_id = ?',
    {
      replacements: ['REPLACE_STUDENT_ID'],
      type: sequelize.QueryTypes.SELECT
    }
).then(result => {
    console.log(result);
}).catch((error) => {
    console.error('Failed to insert data : ', error);
});

Để thay thế mảng, bạn truyền phương thức query() với câu lệnh SQL và đối tượng cấu hình. Nó chứa giá trị và kiểu của replacements. Đối với replacements, bạn truyền dữ liệu dưới dạng mảng và lấy các giá trị đó bằng ký hiệu dấu hỏi (?).

Tiếp theo, vì bạn cần lấy dữ liệu về một sinh viên cụ thể, student_id được truyền làm tham số thứ hai. Sau đó, bạn truyền cặp khóa-giá trị type: sequelize.QueryTypes.SELECT, mà bạn có thể dùng để chọn dữ liệu từ cơ sở dữ liệu.

Cũng có một số loại khác, chẳng hạn như QueryTypes.UPDATE và QueryTypes.DELETE. Tùy vào yêu cầu, bạn có thể chọn loại phù hợp với mục đích của mình.

Phần dưới đây hiển thị toàn bộ khối mã. Ở đây bạn kết nối với cơ sở dữ liệu và truy xuất dữ liệu sinh viên đã chọn bằng câu truy vấn thô.

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'sample_student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
   );

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.');
}).catch((error) => {
    console.error('Unable to connect to the database: ', error);
});

sequelize.query(
    'SELECT * FROM students WHERE student_id = ?',
    {
      replacements: ['REPLACE_STUDENT_ID'],
      type: sequelize.QueryTypes.SELECT
    }
).then(result => {
    console.log(result);
}).catch((error) => {
    console.error('Failed to insert data : ', error);
});

Lưu và đóng tệp của bạn.

Tiếp theo, bạn có thể chạy tập lệnh này bằng lệnh sau:

node array_raw_query.js

Bạn sẽ thấy kết quả tương tự như sau:

Connection has been established successfully.
[ { student_id: 'STUDENT_ID_YOU_RETRIEVED',
    name: 'Robert Gray',
    createdAt: 2022-05-06T13:14:50.000Z,
    updatedAt: 2022-05-06T13:14:50.000Z } ]

Do student_id được chọn, giá trị đầu ra của bạn có thể khác nhau.

Thay thế đối tượng

Bề ngoài, việc thay thế đối tượng tương tự như thay thế mảng, nhưng cách truyền dữ liệu vào truy vấn thô là khác nhau. Trong tùy chọn thay thế, bạn truyền dữ liệu dưới dạng đối tượng, còn trong tùy chọn truy vấn, bạn sử dụng các giá trị như :key.

Để bắt đầu, hãy tạo một tệp mới có tên object_raw_query.js và dán toàn bộ các khối mã từ tệp server.js, cập nhật cơ sở dữ liệu thành sample_student_db.

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'sample_student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
   );

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.');
}).catch((error) => {
    console.error('Unable to connect to the database: ', error);
});

Sau đó, thêm khối mã sau vào cuối tệp object_raw_query.js mới:

...
sequelize.query(
  'SELECT * FROM students WHERE student_id = :id',
  {
    replacements: { id: 'REPLACE_STUDENT_ID' },
    type: sequelize.QueryTypes.SELECT
  }
 ).then(result => {
    console.log(result);
}).catch((error) => {
    console.error('Failed to insert data : ', error);
});

Ở đây, bạn lấy dữ liệu sinh viên được chọn bằng phương pháp thay thế đối tượng. Bạn tạo một đối tượng replacement, thiết lập id là thông tin sinh viên mà bạn muốn truy xuất: { id: '<mark>REPLACE_STUDENT_ID</mark>' }.

Trong query(), bạn chỉ định: 'SELECT * FROM students WHERE student_id=:id'. Sử dụng phương thức query(), bạn truyền giá trị thay thế dưới dạng một đối tượng, đó là lý do phương thức này được gọi là thay thế bằng đối tượng.

Đây là toàn bộ mã:

const {Sequelize, DataTypes} = require("sequelize");

const sequelize = new Sequelize(
   'sample_student_db',
   'DATABASE_USERNAME',
   'DATABASE_PASSWORD',
    {
      host: 'DATABASE_HOST',
      dialect: 'mysql'
    }
   );

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.');
}).catch((error) => {
    console.error('Unable to connect to the database: ', error);
});

sequelize.query(
  'SELECT * FROM students WHERE student_id = :id',
  {
    replacements: { id: 'REPLACE_STUDENT_ID' },
    type: sequelize.QueryTypes.SELECT
  }
 ).then(result => {
    console.log(result);
}).catch((error) => {
    console.error('Failed to insert data : ', error);
});

Lưu và đóng tệp.

Tiếp theo, chạy tập lệnh này bằng lệnh sau:

node object_raw_query.js

Kết quả đầu ra sẽ giống như sau:

Connection has been established successfully.
[ { student_id: 'STUDENT_ID_YOU_RETRIEVED',
    name: 'Robert Gray',
    createdAt: 2022-05-06T13:14:50.000Z,
    updatedAt: 2022-05-06T13:14:50.000Z } ]

Do student_id được chọn, giá trị đầu ra của bạn có thể khác nhau.

Trong bước này, bạn đã làm việc với các truy vấn thô của Sequelize bằng hai phương pháp khác nhau: thay thế mảng và thay thế đối tượng.

Cấu hình Biến Môi Trường để Kết Nối Cơ Sở Dữ Liệu An Toàn

Đến thời điểm này, bạn đã lưu trữ thông tin đăng nhập cơ sở dữ liệu trực tiếp trong mã của mình. Mặc dù cách tiếp cận này hoạt động cho việc thử nghiệm cục bộ, nhưng nó không an toàn. Việc mã hóa cứng các thông tin đăng nhập sẽ làm lộ thông tin nhạy cảm như mật khẩu cơ sở dữ liệu và tên máy chủ, đồng thời tăng nguy cơ vi phạm khi mã được chia sẻ hoặc triển khai. Trong phần này, bạn sẽ học cách lưu trữ các thông tin đăng nhập này một cách an toàn bằng cách sử dụng biến môi trường.

Tại sao nên sử dụng biến môi trường?

Biến môi trường hoạt động như các tham số cấu hình bên ngoài mà ứng dụng của bạn có thể đọc khi chạy. Thay vì nhúng thông tin đăng nhập trực tiếp vào mã của bạn, chúng được lưu trữ trong hệ thống của bạn hoặc trong một tệp cấu hình riêng biệt mà không bao giờ được đưa vào kiểm soát phiên bản. Cách tiếp cận này mang lại nhiều lợi ích:

  • Bảo mật: Thông tin đăng nhập của bạn vẫn được giữ ẩn khỏi các kho lưu trữ công khai và hệ thống điều khiển phiên bản.
  • Độ linh hoạt: Mỗi môi trường (phát triển, thử nghiệm, sản xuất) có thể sử dụng một cấu hình cơ sở dữ liệu khác nhau mà không cần thay đổi mã nguồn.
  • Dễ bảo trì: Cấu hình tập trung cho phép cập nhật dễ dàng, nếu mật khẩu cơ sở dữ liệu thay đổi, bạn chỉ cần sửa một file hoặc biến duy nhất.
  • Tuân thủ: Thực hành này phù hợp với phương pháp Twelve-Factor App, khuyến khích quản lý cấu hình theo môi trường cho các ứng dụng có khả năng mở rộng.

Cài đặt Biến Môi Trường

Bạn sẽ sử dụng gói dotenv để quản lý các biến môi trường trong Node.js. Thư viện này đọc các biến từ tệp .env và chèn chúng vào đối tượng process.env của Node.js, giúp các biến này có thể truy cập được trong toàn bộ ứng dụng của bạn.

Cài đặt gói dotenv bằng cách chạy lệnh sau:

npm install dotenv

Sau khi cài đặt, tạo một tệp mới có tên .env trong thư mục gốc của dự án. Tệp này sẽ chứa tất cả dữ liệu nhạy cảm cần thiết để kết nối cơ sở dữ liệu.

Dưới đây là một ví dụ về cấu hình tệp .env cho thiết lập Sequelize của bạn:

DB_NAME=hello_world_db
DB_USER=your_username
DB_PASSWORD=your_password
DB_HOST=localhost
DB_DIALECT=mysql

Mỗi biến tương ứng với một tham số cụ thể mà Sequelize sử dụng để thiết lập kết nối.

Bảo mật tệp .env

Điều quan trọng là file .env của bạn không bao giờ xuất hiện trong hệ thống quản lý phiên bản. Để tránh điều này, hãy thêm nó vào file .gitignore của bạn để Git bỏ qua khi commit các thay đổi:

.env

Trong môi trường sản xuất, các biến môi trường thường được cấu hình trực tiếp trong cài đặt của nền tảng lưu trữ của bạn hoặc trình quản lý bí mật thay vì tệp .env cục bộ.

Cập nhật cấu hình Sequelize của bạn

Bây giờ bạn đã định nghĩa các biến môi trường, hãy cập nhật việc khởi tạo Sequelize của bạn để sử dụng chúng. Bắt đầu bằng cách import và cấu hình dotenv ở đầu tệp máy chủ chính của bạn (ví dụ: server.js):

require('dotenv').config();
const Sequelize = require('sequelize');

const sequelize = new Sequelize(
  process.env.DB_NAME,
  process.env.DB_USER,
  process.env.DB_PASSWORD,
  {
    host: process.env.DB_HOST,
    dialect: process.env.DB_DIALECT
  }
);

sequelize.authenticate().then(() => {
   console.log('Connection has been established successfully.');
}).catch((error) => {
   console.error('Unable to connect to the database:', error);
});
  1. Dòng require('dotenv').config() sẽ nạp tất cả các biến được định nghĩa trong tệp .env vào process.env.
  2. Sequelize đọc các biến môi trường khi khởi tạo một phiên bản mới.
  3. Điều này đảm bảo rằng không có thông tin nhạy cảm như tên người dùng hoặc mật khẩu xuất hiện trong mã của bạn.

Cách tiếp cận này nâng cao bảo mật và cho phép các môi trường khác nhau sử dụng các cấu hình riêng biệt chỉ bằng cách thay đổi giá trị của biến môi trường.

Xác minh cấu hình

Sau khi bạn đã cập nhật cấu hình, bạn có thể kiểm tra kết nối bằng cách chạy ứng dụng Node.js của mình:

node server.js

Nếu tập tin .env của bạn được thiết lập đúng và máy chủ MySQL đang chạy, bạn sẽ thấy kết quả sau:

Connection has been established successfully.

Nếu xuất hiện lỗi, hãy kiểm tra lại rằng:

  • Thông tin đăng nhập trong tệp .env của bạn khớp với thông tin trong MySQL của bạn.
  • Máy chủ MySQL đang chạy và có thể truy cập từ máy của bạn.
  • Tệp .env nằm ở thư mục gốc của dự án của bạn và được định dạng đúng.

Các thực hành tốt nhất cho cấu hình bảo mật

Sử dụng biến môi trường là một bước đi quan trọng đầu tiên hướng tới quản lý cấu hình an toàn. Để đảm bảo an ninh và độ tin cậy liên tục, hãy xem xét các thực hành tốt bổ sung sau đây:

  • Sử dụng cấu hình riêng cho từng môi trường: Duy trì các tệp .env khác nhau cho development, staging, và production. Bạn có thể đặt tên chúng là .env.development, .env.staging, và .env.production.
  • Sử dụng trình quản lý bí mật: Đối với môi trường sản xuất, hãy sử dụng trình quản lý bí mật thay vì lưu trữ bí mật trong các tệp.
  • Xác thực các biến môi trường: Sử dụng tiện ích cấu hình hoặc tập lệnh khởi động để kiểm tra xem các biến cần thiết đã được định nghĩa trước khi ứng dụng chạy. Điều này giúp ngăn ngừa lỗi trong quá trình chạy.
  • Thường xuyên thay đổi thông tin đăng nhập: Cập nhật thông tin đăng nhập cơ sở dữ liệu định kỳ để giảm thiểu tác động của các rò rỉ tiềm ẩn.
  • Hạn chế quyền truy cập cơ sở dữ liệu: Cấu hình vai trò người dùng cơ sở dữ liệu để có mức quyền tối thiểu cần thiết cho ứng dụng.

Bằng cách triển khai các biến môi trường và tuân theo những thực tiễn này, bạn đảm bảo rằng ứng dụng Sequelize của mình duy trì cả tính linh hoạt và bảo mật trong các môi trường phát triển và sản xuất.

Các Thực Tiễn Tốt Nhất về Di cư và Đồng bộ

Khi phát triển một sơ đồ cơ sở dữ liệu, bạn có hai phương pháp chính với Sequelize: viết và chạy các migration rõ ràng, hoặc sử dụng sequelize.sync() để Sequelize tự động tạo các thay đổi trong sơ đồ. Cả hai phương pháp đều hợp lệ, nhưng chúng phục vụ những mục đích khác nhau và đi kèm với những đánh đổi riêng. Hãy cùng tìm hiểu khi nào nên sử dụng từng phương pháp, cách viết các migration an toàn, và cách chạy chúng một cách đáng tin cậy trong môi trường phát triển và sản xuất.

Di cư dữ liệu vs sequelize.sync() — khi nào nên dùng cái nào

  • Di cư dữ liệu (khuyến nghị cho môi trường sản xuất): Di cư dữ liệu là các thay đổi rõ ràng, có phiên bản cho sơ đồ cơ sở dữ liệu của bạn. Chúng được lưu trữ trong hệ thống kiểm soát nguồn, được xem xét mã, và thực thi như một phần của quy trình triển khai. Sử dụng di cư dữ liệu trong môi trường sản xuất cho bất kỳ thay đổi nào liên quan đến sơ đồ và ảnh hưởng đến tính toàn vẹn hoặc khả năng sẵn sàng của dữ liệu.

  • sequelize.sync() (chỉ sử dụng cho phát triển và nguyên mẫu): sync() tiện lợi cho phát triển cục bộ, kiểm thử và nguyên mẫu nhanh vì nó có thể tạo hoặc điều chỉnh bảng tự động. Tuy nhiên, không sử dụng sync({ force: true }) hoặc sync({ alter: true }) trong môi trường sản xuất: các tùy chọn này có thể xóa dữ liệu hoặc thực hiện thay đổi không an toàn mà không có phương án khôi phục kiểm soát.

Công cụ: sequelize-cli và các trình chạy migration

CLI chính thức, sequelize-cli, là cách đơn giản nhất để quản lý các migration với Sequelize. Đối với các nhóm cần kiểm soát nhiều hơn hoặc các quy trình làm việc tùy chỉnh, umzug là một lựa chọn phổ biến.

Để cài đặt và khởi tạo CLI trong dự án của bạn, hãy chạy:

npm install --save-dev sequelize-cli

npx sequelize-cli init

Điều này tạo ra các thư mục sau:

config/        # Stores database configuration files
models/        # Stores your Sequelize model definitions
migrations/    # Stores migration files
seeders/       # Stores seeder files for inserting initial data

Bạn nên thêm các thư mục này vào hệ thống quản lý phiên bản (ngoại trừ bất kỳ tệp nào chứa thông tin đăng nhập). Điều này đảm bảo mọi môi trường đều chạy các thay đổi cơ sở dữ liệu giống nhau một cách nhất quán.

Tạo và Chạy Các Migration

Để tạo một file di trú mới, hãy sử dụng lệnh sau:

npx sequelize-cli migration:generate --name add-isbn-to-books

Điều này tạo ra một tệp có dấu thời gian trong thư mục migrations/. Mở tệp và xác định những gì sẽ xảy ra khi bạn áp dụng migration (up) và cách hoàn tác nó (down):

module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.addColumn('books', 'isbn', {
      type: Sequelize.STRING
    });
  },
  async down(queryInterface) {
    await queryInterface.removeColumn('books', 'isbn');
  }
};
  • Hàm up thực hiện việc di chuyển cơ sở dữ liệu (thêm cột).
  • Hàm down đảo ngược thay đổi (gỡ cột).

Chạy việc di chuyển bằng cách:

npx sequelize-cli db:migrate

Để hoàn tác việc di chuyển cuối cùng, hãy sử dụng:

npx sequelize-cli db:migrate:undo

Bạn cũng có thể hoàn tác tất cả các lần di chuyển trong môi trường phát triển bằng cách chạy:

npx sequelize-cli db:migrate:undo:all

Viết các bản di chuyển an toàn, có tính lặp lại

Tuân theo các quy tắc này khi tạo bản di chuyển:

  • Đóng gói các thao tác trong giao dịch khi cơ sở dữ liệu hỗ trợ. Điều này đảm bảo việc di chuyển dữ liệu hoặc thành công hoàn toàn hoặc hoàn toàn phục hồi khi có lỗi.

    module.exports = {
      async up(queryInterface, Sequelize) {
        return queryInterface.sequelize.transaction(async (t) => {
          await queryInterface.addColumn('books', 'isbn', {
            type: Sequelize.STRING
          }, { transaction: t });
        });
      },
      async down(queryInterface) {
        return queryInterface.sequelize.transaction(async (t) => {
          await queryInterface.removeColumn('books', 'isbn', { transaction: t });
        });
      }
    };
  • Giữ các bản di cư nhỏ và tập trung. Mỗi bản di cư chỉ nên thực hiện một thay đổi logic duy nhất (thêm một cột, tạo chỉ mục, đổi tên cột). Các bản di cư nhỏ dễ dàng xem xét, thử nghiệm và hồi phục hơn.

  • Hãy phòng thủ và idempotent. Khi thích hợp, hãy kiểm tra sự tồn tại để tránh thất bại trong các môi trường có sự dịch chuyển. Sử dụng queryInterface.describeTable() hoặc các truy vấn thô để bảo vệ các thao tác.

  • Tránh các thao tác phá hủy chỉ trong một bước. Ví dụ, để xóa một cột một cách an toàn, hãy thêm cột mới, điền dữ liệu vào cột đó, chuyển các lệnh đọc sang cột mới trong mã ứng dụng, và chỉ sau đó mới xóa cột cũ trong một lần di chuyển sau.

Các quá trình di chuyển lâu dài và không gián đoạn

Các bảng lớn và các thao tác chỉ mục có thể gây ra khóa. Sử dụng các chiến lược sau để giảm thiểu tác động:

  1. Thêm các cột có thể null trước. Việc thêm một cột có thể null thường nhanh. Tránh thêm các cột với giá trị mặc định phức tạp gây yêu cầu viết lại toàn bộ bảng.
  2. Điền dữ liệu ngược trong các lô nhỏ. Triển khai một nhiệm vụ nền để điền dữ liệu vào các cột mới theo từng phần thay vì chạy một UPDATE khổng lồ duy nhất.
  3. Tạo chỉ mục mà không khóa độc quyền nếu có thể. Sử dụng các tính năng riêng của cơ sở dữ liệu (ví dụ, CONCURRENTLY trong Postgres và DDL trực tuyến gần đây của MySQL) thông qua SQL thô trong các migration.
  4. Triển khai theo cờ tính năng. Triển khai mã ứng dụng đọc cột mới phía sau cờ tính năng. Sau khi việc điền dữ liệu hoàn tất và các bài kiểm tra thành công, bật cờ và sau đó loại bỏ trường cũ.

Hoàn tác và sửa lỗi khẩn cấp

  • Cung cấp một bản di chuyển down. Mỗi bản up nên có một bản down ngược lại để ghi lại cách hoàn tác thay đổi. Ngay cả khi bạn hiếm khi chạy down trong môi trường sản xuất, nó vẫn đóng vai trò như một kế hoạch hoàn tác rõ ràng.
  • Lên kế hoạch cho việc khôi phục khẩn cấp. Nếu việc di chuyển gây ra sự cố, hãy chuẩn bị để khôi phục mã ứng dụng trước và phục hồi cơ sở dữ liệu từ bản sao lưu nếu cần. Thường xuyên kiểm tra các quy trình phục hồi.

Gieo dữ liệu và thay đổi sơ đồ

  • Giữ số lượng seeder tối thiểu và ổn định. Sử dụng seeder cho các hàng lookup hoặc cấu hình cần thiết. Tránh seeding khối lượng lớn dữ liệu kinh doanh qua migrations.
  • Tách hạt dữ liệu khỏi các di cư cấu trúc. Nếu bạn phải điền dữ liệu phụ thuộc vào thay đổi lược đồ, hãy ưu tiên sử dụng một seeder tiếp theo để trách nhiệm được giữ rõ ràng.

CI, đánh giá mã và tự động hóa triển khai

  • Chạy các bản di trú trong CI và môi trường staging trước. Thêm một bước CI chạy các bản di trú trên cơ sở dữ liệu tạm thời để phát hiện lỗi sớm.

  • Hãy xem các bản di cư như mã nguồn. Kiểm tra cú pháp và xem xét các tệp di cư trong các yêu cầu kéo. Bao gồm các kiểm tra tự động khi có thể.

  • Tự động thực hiện di chuyển trong quá trình triển khai. Đảm bảo các migration được chạy trước khi các tiến trình ứng dụng nhận lưu lượng. Ví dụ các script trong package.json:

    "scripts": {
      "migrate": "npx sequelize-cli db:migrate",
      "start": "node server.js",
      "deploy": "npm run migrate && npm run start"
    }

Trong các triển khai sử dụng container, hãy chạy các migration như một init container hoặc trong hook triển khai để các tiến trình web chỉ bắt đầu sau khi các migration hoàn tất.

Kiểm tra các bản di cư

  • Kiểm tra cả hai hướng. Chạy db:migrate rồi db:migrate:undo:all cả trên máy cục bộ và trong CI để xác nhận rằng up và down đều hoạt động.
  • Sử dụng cơ sở dữ liệu dùng một lần cho các bài kiểm tra. Nên ưu tiên các cơ sở dữ liệu hoặc container nhất thời cho các lần chạy kiểm tra có kết quả xác định.

Tuân theo những phương pháp tốt nhất này đảm bảo rằng các migration của Sequelize của bạn vẫn đáng tin cậy, có thể theo dõi và an toàn cho các dự án dài hạn.

Khắc phục sự cố và mẹo cải thiện hiệu suất

Ngay cả với một cấu hình vững chắc, các ứng dụng Sequelize đôi khi cũng có thể gặp phải những vấn đề bất ngờ như lỗi kết nối cơ sở dữ liệu, thất bại trong việc di chuyển dữ liệu, các truy vấn chạy chậm, deadlock hoặc xung đột đồng thời. Phần này cung cấp hướng dẫn từng bước về cách xác định và khắc phục những vấn đề này, cùng với các thực tiễn tốt nhất về hiệu suất giúp bạn giữ cho ứng dụng ổn định và hiệu quả.

Khắc phục sự cố thường gặp

1. Lỗi kết nối

Vấn đề: Sequelize không thể kết nối với cơ sở dữ liệu MySQL và đưa ra các lỗi như SequelizeConnectionError: connect ECONNREFUSED, Access denied for user, hoặc SequelizeHostNotFoundError.

Cách Khắc Phục:

Bắt đầu bằng cách xác nhận rằng thông tin kết nối của bạn là chính xác. Kiểm tra kỹ các giá trị trong tệp .env của bạn, bao gồm tên cơ sở dữ liệu, tên người dùng, mật khẩu, máy chủ và cổng. Ngay cả một lỗi chính tả nhỏ hoặc khoảng trắng thừa cũng có thể khiến Sequelize không thể kết nối.

Tiếp theo, hãy đảm bảo rằng máy chủ MySQL của bạn đang chạy và lắng nghe trên cổng mặc định (3306). Bạn có thể kiểm tra kết nối bằng cách đăng nhập thủ công:

mysql -u your_username -p -h your_host -P 3306

Nếu lệnh này không thành công, vấn đề có thể nằm ở dịch vụ MySQL hoặc thông tin đăng nhập của bạn.

Nếu bạn đang sử dụng gói dotenv, hãy đảm bảo rằng require('dotenv').config() xuất hiện trước khi Sequelize được khởi tạo. Bước này đảm bảo rằng tất cả các biến môi trường có sẵn khi chạy ứng dụng.

Cuối cùng, đối với các kết nối từ xa, hãy kiểm tra cài đặt tường lửa hoặc quy tắc nhóm bảo mật của bạn. Máy chủ MySQL phải chấp nhận các kết nối đến từ máy chủ của ứng dụng của bạn.

2. Thất bại trong di cư

Vấn đề: Bạn chạy npx sequelize-cli db:migrate hoặc sequelize.sync() và gặp phải các lỗi như thiếu bảng, cột trùng lặp hoặc không khớp với lược đồ.

Cách Khắc Phục:

Các vấn đề về di cư thường xảy ra khi các tệp được thực hiện theo thứ tự sai hoặc chứa lỗi trong các thao tác SQL. Sequelize xử lý các bản di cư dựa trên dấu thời gian trong tên tệp, vì vậy hãy xác nhận rằng các tệp được đặt tên chính xác.

Nếu việc di chuyển thất bại, hãy kiểm tra kỹ thông báo lỗi. Sau đó, an toàn hoàn tác lần di chuyển cuối cùng bằng cách sử dụng:

npx sequelize-cli db:migrate:undo

Sau khi chỉnh sửa tệp di cư, hãy chạy lại nó.

Luôn bao bọc logic di chuyển dữ liệu của bạn trong một giao dịch để đảm bảo tính nguyên tử: tất cả các thay đổi thành công hoặc không có thay đổi nào. Trước khi chạy các di chuyển trên môi trường sản xuất, hãy kiểm tra kỹ lưỡng trên cơ sở dữ liệu staging hoặc cục bộ để xác nhận tính chính xác.

3. Vấn đề Đồng bộ Mô hình

Vấn đề: Các mô hình Sequelize và sơ đồ cơ sở dữ liệu của bạn không khớp, dẫn đến thiếu cột, kiểu dữ liệu sai hoặc các bảng lỗi thời.

Cách Khắc Phục:

Tránh sử dụng sequelize.sync({ force: true }) trong môi trường sản xuất, vì nó sẽ xóa và tạo lại các bảng, gây mất dữ liệu không thể khôi phục. Tương tự, sử dụng sequelize.sync({ alter: true }) một cách thận trọng; nó có thể thay đổi cấu trúc cơ sở dữ liệu một cách không lường trước được.

Thay vào đó, hãy ưu tiên các bản di trú rõ ràng, định nghĩa cụ thể từng thay đổi trong lược đồ. Để so sánh các mô hình của bạn với cấu trúc cơ sở dữ liệu thực tế, hãy sử dụng:

await queryInterface.describeTable('tableName');

Điều này giúp nhận diện những sự không khớp trước khi chúng ảnh hưởng đến sản xuất.

4. Lỗi Truy vấn và Hết thời gian

Vấn đề: Bạn thấy các lỗi liên quan đến hết thời gian chờ như Connection timeout hoặc Too many connections.

Cách Khắc Phục:

Các vấn đề về hết thời gian kết nối thường do pool kết nối bị cạn kiệt hoặc các kết nối không được đóng. Hãy đảm bảo tất cả các truy vấn Sequelize được chờ xử lý đúng cách để các kết nối được trả về pool sau khi sử dụng.

Bạn cũng có thể tinh chỉnh cấu hình pool của Sequelize để phù hợp hơn với khối lượng công việc của ứng dụng của bạn:

const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
  host: DB_HOST,
  dialect: 'mysql',
  pool: {
    max: 10,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
});

Nếu ứng dụng của bạn xử lý nhiều người dùng đồng thời, hãy cân nhắc tăng tham số max_connections trong cấu hình cơ sở dữ liệu MySQL của bạn. Theo dõi các kết nối đang hoạt động bằng cách sử dụng SHOW PROCESSLIST; trong MySQL để phát hiện rò rỉ hoặc các phiên không sử dụng.

5. Hiệu suất truy vấn chậm

Vấn đề: Một số truy vấn mất nhiều thời gian hơn dự kiến, gây chậm phản hồi API hoặc độ trễ cao.

Cách chẩn đoán:

Bắt đầu bằng cách xác định những truy vấn nào đang chậm. Bạn có thể ghi lại tất cả các truy vấn mà Sequelize thực hiện bằng cách bật tính năng ghi nhật ký tạm thời:

const sequelize = new Sequelize(..., { logging: console.log });

Sau đó, sử dụng câu lệnh EXPLAIN của MySQL để hiểu cách cơ sở dữ liệu thực hiện truy vấn:

EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';

Nếu kết quả hiển thị “quét toàn bộ bảng,” truy vấn đang thiếu chỉ mục.

Bạn cũng có thể bật nhật ký truy vấn chậm của MySQL để tự động ghi lại các thao tác chậm:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

Cách Khắc Phục:

Thêm chỉ mục vào các cột được truy vấn thường xuyên (đặc biệt là các cột được sử dụng trong các mệnh đề WHERE, JOIN và ORDER BY). Tránh sử dụng SELECT *, thay vào đó, chỉ lấy rõ ràng các trường bạn cần bằng cách sử dụng tùy chọn attributes.

Nếu Sequelize tạo ra các truy vấn không hiệu quả với những phép nối không cần thiết, hãy viết lại truy vấn bằng SQL thô. Đối với các báo cáo lớn hoặc truy vấn phân tích, hãy chia chúng thành các bước nhỏ hơn hoặc sử dụng các công cụ bộ nhớ đệm như Redis để lưu kết quả lặp lại.

6. Tắc nghẽn và Lỗi Đồng Thời

Vấn đề: Bạn nhận được các lỗi như Deadlock found when trying to get lock; try restarting transaction, đặc biệt khi tải cao.

Cách chẩn đoán:

Kiểm tra trạng thái InnoDB trong MySQL để xác định các truy vấn hoặc bảng nào đã tham gia:

SHOW ENGINE INNODB STATUS\G

Đầu ra này bao gồm thông tin chi tiết về các deadlock và thời gian chờ khóa gần đây. Bạn cũng có thể bật nhật ký chung hoặc nhật ký giao dịch để xem trình tự các truy vấn dẫn đến vấn đề.

Cách Khắc Phục:

Tình trạng bế tắc xảy ra khi hai hoặc nhiều giao dịch đang chờ khóa của nhau. Để giảm thiểu chúng:

  1. Giữ các giao dịch càng ngắn càng tốt. Không gọi dịch vụ bên ngoài hoặc thực hiện tính toán nặng trong khi đang giữ khóa.

  2. Luôn truy cập các bảng và hàng theo cùng một thứ tự trong các giao dịch để tránh tình trạng chờ vòng.

  3. Đối với các tình trạng khóa chết lặp đi lặp lại, hãy triển khai cơ chế thử lại đơn giản với độ trễ tăng theo cấp số nhân:

    async function runWithRetry(fn, retries = 3) {
      for (let i = 0; i < retries; i++) {
        try {
          return await fn();
        } catch (err) {
          if (isDeadlockError(err) && i < retries - 1) {
            await sleep(100 * (i + 1));
            continue;
          }
          throw err;
        }
      }
    }
  4. Chỉ sử dụng khóa theo hàng (SELECT ... FOR UPDATE) khi cần thiết, và giải phóng chúng ngay lập tức.

  5. Chia các giao dịch lớn thành các lô nhỏ hơn để giảm tình trạng tranh chấp.

  6. Điều chỉnh mức độ cô lập giao dịch nếu cần thiết, nhưng chỉ sau khi xác nhận rằng điều đó sẽ không ảnh hưởng đến tính nhất quán của dữ liệu.

Mẹo Tối Ưu Hiệu Suất

Các vấn đề về hiệu suất thường xuất phát từ các truy vấn không hiệu quả, thiếu chỉ mục, hoặc quản lý kết nối kém. Dưới đây là một vài mẹo thực tiễn để cải thiện hiệu suất của Sequelize mà không làm giảm tính rõ ràng hoặc khả năng bảo trì.

1. Tối ưu hóa các truy vấn

Viết các truy vấn chỉ yêu cầu những gì cần thiết. Lấy dữ liệu không cần thiết sẽ tăng mức sử dụng bộ nhớ và làm chậm phản hồi. Sử dụng attributes để giới hạn các trường:

const users = await User.findAll({ attributes: ['id', 'name'] });

Luôn lọc kết quả sử dụng các cột đã lập chỉ mục và tránh tìm kiếm rộng như LIKE '%term%' khi có thể.

2. Sử dụng các chỉ mục một cách hiệu quả

Chỉ mục giúp tra cứu nhanh hơn nhưng có thể làm chậm việc ghi dữ liệu. Chỉ thêm chỉ mục ở những nơi mang lại lợi ích đáng kể. Phân tích hiệu suất truy vấn với EXPLAIN của MySQL để kiểm tra cải tiến trước và sau khi thêm chỉ mục:

await queryInterface.addIndex('users', ['email']);

3. Chỉnh sửa Bộ kết nối

Mối kết nối pool xác định số lượng kết nối cơ sở dữ liệu mà Sequelize duy trì.

  • Đối với các ứng dụng nhỏ, 5–10 kết nối thường là đủ.
  • Đối với các hệ thống tải cao, tăng kích thước pool và điều chỉnh thời gian chờ để tránh tắc nghẽn kết nối. Giám sát việc sử dụng thường xuyên bằng SHOW PROCESSLIST; của MySQL.

4. Bật ghi nhật ký và giám sát

Bật ghi log Sequelize tạm thời để xem các truy vấn SQL. Sử dụng nhật ký truy vấn chậm của MySQL để xác định các truy vấn vượt quá ngưỡng hiệu suất của bạn. Để theo dõi thời gian thực, tích hợp các công cụ giám sát như PM2, New Relic hoặc Datadog để đo độ trễ và sử dụng tài nguyên.

5. Bộ nhớ đệm cho các truy vấn thường xuyên

Lưu kết quả vào bộ nhớ đệm cho dữ liệu không thay đổi thường xuyên. Các công cụ như Redis hoặc Memcached giúp giảm các lần truy cập cơ sở dữ liệu và cải thiện thời gian phản hồi, đặc biệt đối với khối lượng công việc đọc nhiều.

6. Phân trang kết quả

Các bộ kết quả lớn tiêu tốn nhiều bộ nhớ. Luôn phân trang các truy vấn trong API:

const users = await User.findAll({ limit: 50, offset: 100 });

Phân trang giúp các phản hồi nhẹ nhàng và nhất quán ngay cả khi tải cao.

7. Sử dụng truy vấn thô cho các thao tác phức tạp

Nếu việc trừu tượng hóa của Sequelize dẫn đến SQL không hiệu quả cho một trường hợp sử dụng cụ thể, hãy thực hiện truy vấn SQL thô trực tiếp:

const [results] = await sequelize.query('SELECT name FROM users WHERE active = 1');

Sử dụng các truy vấn thô một cách tiết kiệm và ghi chép chúng rõ ràng để bảo trì trong tương lai.

Câu hỏi thường gặp

1. Sequelize trong Node.js là gì?

Sequelize là một công cụ ánh xạ đối tượng-quan hệ (ORM) cho Node.js, cung cấp một API cấp cao dựa trên JavaScript để làm việc với các cơ sở dữ liệu SQL như MySQL, PostgreSQL, SQLite và MariaDB. Thay vì viết SQL thô cho mỗi thao tác, bạn định nghĩa các mô hình (model) tương ứng với các bảng trong cơ sở dữ liệu và sử dụng các phương thức của mô hình để chèn, truy vấn, cập nhật và xóa bản ghi. Sequelize cũng hỗ trợ các mối quan hệ (quan hệ giữa các mô hình), giao dịch, di cư (migrations) và các truy vấn thô khi bạn cần SQL trực tiếp.

2. Làm thế nào để tôi kết nối Sequelize với MySQL?

Để kết nối Sequelize với MySQL, bạn tạo một thể hiện Sequelize với thông tin đăng nhập cơ sở dữ liệu và các tùy chọn của bạn. Trong một tệp Node.js, yêu cầu Sequelize, tùy chọn tải các biến môi trường, sau đó khởi tạo nó với tên cơ sở dữ liệu, tên người dùng, mật khẩu và các tùy chọn host/dialect. Ví dụ:

require('dotenv').config();
const { Sequelize } = require('sequelize');

const sequelize = new Sequelize(
  process.env.DB_NAME,
  process.env.DB_USER,
  process.env.DB_PASSWORD,
  {
    host: process.env.DB_HOST || 'localhost',
    dialect: 'mysql'
  }
);

await sequelize.authenticate();
console.log('Connected to the database successfully!');

Luôn giữ thông tin đăng nhập bên ngoài hệ thống quản lý mã nguồn (ví dụ, trong tệp .env hoặc trình quản lý bí mật).

3. Làm thế nào để tôi tạo và đồng bộ các mô hình trong Sequelize?

Trong Sequelize, các mô hình đại diện cho các bảng. Bạn định nghĩa chúng bằng cách sử dụng sequelize.define() hoặc bằng cách mở rộng lớp Model. Sau đó, bạn có thể đồng bộ chúng với cơ sở dữ liệu của mình để Sequelize tự động tạo các bảng (rất hữu ích cho phát triển). Dưới đây là một ví dụ nhỏ:

const { DataTypes } = require('sequelize');

const Book = sequelize.define('Book', {
  title: { type: DataTypes.STRING, allowNull: false },
  author: { type: DataTypes.STRING, allowNull: false },
  releaseDate: DataTypes.DATEONLY
});

// Create the table if it does not exist
await sequelize.sync();

Đối với môi trường sản xuất, tốt hơn là nên sử dụng các migration để bạn có thể kiểm soát phiên bản đối với các thay đổi của cơ sở dữ liệu.

4. Tôi làm thế nào để sử dụng Sequelize với Express?

Trong một ứng dụng Express, hãy khởi tạo Sequelize trong một tệp riêng (như db.js), xuất instance đã kết nối và các mô hình, và nhập chúng bất cứ nơi nào bạn cần trong các bộ xử lý route hoặc controller của mình. Sử dụng async/await trong các bộ xử lý route để gọi các phương thức của mô hình Sequelize, và xử lý lỗi bằng try/catch hoặc middleware lỗi. Đây là cách thực hiện trong thực tế:

// db.js
const sequelize = new Sequelize(...);
const User = sequelize.define(...);
module.exports = { sequelize, User };

// app.js
const { User } = require('./db');
app.get('/users', async (req, res) => {
  const users = await User.findAll();
  res.json(users);
});

Hãy chắc chắn rằng bạn đã chạy các migration hoặc gọi sequelize.sync() trước khi khởi động server Express của bạn để các bảng trong cơ sở dữ liệu tồn tại khi có yêu cầu đến.

5. Sự khác biệt giữa Sequelize và các truy vấn SQL thô là gì?

Sequelize cho phép bạn viết logic cơ sở dữ liệu bằng JavaScript, trong khi SQL thô nghĩa là viết các câu lệnh SQL trực tiếp. Sequelize làm cho mã của bạn sạch sẽ và dễ bảo trì hơn vì bạn có thể định nghĩa các mô hình và mối quan hệ một lần và sử dụng chúng trong toàn bộ ứng dụng. SQL thô cho bạn nhiều quyền kiểm soát hơn và đôi khi có thể nhanh hơn cho các truy vấn phức tạp.

Tóm lại, hãy sử dụng Sequelize cho hầu hết các công việc hàng ngày, và sử dụng SQL thô khi bạn cần tối ưu hiệu suất hoặc thực hiện điều gì đó mà Sequelize không thể biểu diễn một cách dễ dàng.

6. Làm thế nào để tôi có thể định nghĩa các mối quan hệ trong Sequelize?

Sequelize giúp dễ dàng mô tả các mối quan hệ giữa các bảng. Bạn có thể thiết lập các mối quan hệ một-một, một-nhiều, hoặc nhiều-nhiều bằng cách sử dụng các phương thức đơn giản như hasOne, hasMany, và belongsToMany. Ví dụ:

// One to one
User.hasOne(Profile);
Profile.belongsTo(User);

// One to many
Author.hasMany(Book);
Book.belongsTo(Author);

// Many to many
Student.belongsToMany(Course, { through: 'StudentCourse' });
Course.belongsToMany(Student, { through: 'StudentCourse' });

Khi đã được định nghĩa, bạn có thể sử dụng include trong các truy vấn của mình để tự động lấy dữ liệu liên quan.

7. Tôi nên xử lý các bản di chuyển và thay đổi lược đồ như thế nào?

Trong quá trình phát triển, bạn có thể sử dụng sequelize.sync() để nhanh chóng tạo các bảng. Nhưng trong môi trường sản xuất, bạn nên luôn sử dụng các migration. Migration là các tệp được phiên bản hóa mô tả cách mà sơ đồ cơ sở dữ liệu của bạn thay đổi theo thời gian, tương tự như cách các commit trong Git theo dõi sự thay đổi của mã nguồn.

Bạn có thể tạo một migration bằng Sequelize CLI:

npx sequelize-cli migration:generate --name add-users-table

Mỗi migration có các phương thức up và down, một để áp dụng thay đổi và một để hoàn tác nó. Chạy các migration của bạn bằng:

npx sequelize-cli db:migrate

Như vậy, tất cả mọi người trong nhóm của bạn (và mọi lần triển khai) đều giữ cùng một cấu trúc cơ sở dữ liệu.

Sử dụng sequelize.sync() để thử nghiệm nhanh, nhưng hãy dùng migrations khi ứng dụng đã hoạt động vì chúng an toàn hơn, có thể đảo ngược và dễ theo dõi hơn.

Kết luận

Trong hướng dẫn này, bạn sẽ thiết lập Sequelize với Node.js và MySQL để quản lý dữ liệu thông qua các mô hình, liên kết và di cư cơ sở dữ liệu. Bạn cũng đã bảo mật thông tin đăng nhập cơ sở dữ liệu bằng các biến môi trường và tìm hiểu các thực hành tốt nhất để xử lý sự cố và tối ưu hiệu suất.

Với những bước này, bạn giờ đây đã có một nền tảng vững chắc để xây dựng và duy trì các ứng dụng Node.js an toàn, hiệu quả, sử dụng Sequelize và MySQL cho quản lý dữ liệu.

Để tìm hiểu thêm về Sequelize, hãy xem tài liệu sản phẩm. Để xem thêm các hướng dẫn Node.js tương tự, hãy xem các bài viết sau:

  • Cách Thiết Lập Máy Chủ GraphQL trong Node.js với Apollo Server và Sequelize
  • Cách Triển Khai Bộ Nhớ Đệm trong Node.js Sử Dụng Redis
  • Cách Tạo Máy Chủ Web trong Node.js với Module HTTP

Cảm ơn bạn đã học cùng cộng đồng Linux-Console.net.