Node.js + TypeScript + MongoDB: JWT Authentication - Phần 1

EminelEminel
11 min read

Trong bài học này, các bạn sẽ tìm hiểu cách xây dựng backend Node.js bằng TypeScript, triển khai xác thực và ủy quyền người dùng bằng JWT, lưu trữ dữ liệu trong Redis và tạo các Docker container bằng docker-compose.

Các công nghệ sẽ được sử dụng series này:

  • Node.js – là một JavaScript runtime mã nguồn mở, đa nền tảng, được xây dựng trên V8 engine của Chrome. Nó cho phép thực thi JavaScript phía server, giúp xây dựng các ứng dụng web hiệu suất cao và mở rộng tốt.

  • Express.js – một web framework nhẹ dành cho Node.js, giúp xây dựng API và ứng dụng web nhanh chóng bằng cách cung cấp các công cụ và middleware cần thiết.

  • Typegoose – một thư viện hỗ trợ sử dụng Mongoose với TypeScript, giúp định nghĩa mô hình dữ liệu bằng các class thay vì schema truyền thống.

  • Mongoose – một ODM (Object Document Mapper) cho MongoDB, giúp thao tác dữ liệu dễ dàng thông qua mô hình hướng đối tượng.

  • bcryptjs – một thư viện hashing mật khẩu sử dụng thuật toán bcrypt, hỗ trợ bảo mật dữ liệu người dùng.

  • jsonwebtoken (JWT) – một thư viện để tạo và xác thực JSON Web Token, thường được sử dụng để quản lý xác thực và phiên làm việc của người dùng.

  • Redis – một in-memory data store thường được sử dụng để caching, lưu trữ phiên người dùng, hàng đợi tin nhắn, và các tác vụ yêu cầu truy xuất nhanh.

  • MongoDB – một cơ sở dữ liệu NoSQL dạng document, lưu trữ dữ liệu theo mô hình JSON linh hoạt và có khả năng mở rộng cao.

  • Zod – một thư viện để xác thực và định dạng dữ liệu trong TypeScript, giúp đảm bảo tính hợp lệ của đầu vào.

  • CORS (Cross-Origin Resource Sharing) – một cơ chế bảo mật của trình duyệt cho phép backend kiểm soát quyền truy cập tài nguyên từ các frontend khác nguồn.

Thiết lập môi trường phát triển

Trong bài viết này tôi sẽ hướng dẫn viết các chức năng sau:

  1. Đăng ký tài khoản mới với các trường Tên, Email, Mật khẩu và Xác nhận Mật khẩu.

  2. Đăng nhập bằng thông tin Email và Mật khẩu.

  3. Lấy thông tin hồ sơ nếu người dùng đã đăng nhập.

  4. Admin có thể lấy thông tin của tất cả người dùng trong cơ sở dữ liệu.

Cài đăt môi trường

  1. Download và install Node.js, truy cập vào đây để xem hướng dẫn chi tiết.

  2. Download và install Docker, truy cập vào đây để xem hướng dẫn chi tiết.

  3. Download và install MongoDB Compass, truy cập vào đây

  4. Download và install RedisInsight, truy cập vào đây

Luồng xác thực JWT với Redis, MongoDB và Node.js

Đây là quy trình Xác thực JWT mà chúng ta sẽ thực hiện trong bài viết này. Người dùng truy cập ứng dụng của chúng ta trong trình duyệt và cung cấp tên người dùng và mật khẩu để đăng nhập vào ứng dụng của chúng ta.

Ứng dụng frontend sau đó sẽ gửi yêu cầu đến backend với thông tin đăng nhập của người dùng. Backend sẽ xác thực người dùng và gửi lại một số cookies nếu thông tin đăng nhập hợp lệ.

Sơ đồ bên dưới minh họa luồng đăng nhập của người dùng trong ứng dụng của chúng ta

Sơ đồ bên dưới hiển thị quy trình đăng ký người dùng trong quy trình Xác thực JWT.

Cấu trúc dự án

jwt_authentication_authorization_node/
├── .vscode/
 └── extensions.json
├── config/
 ├── custom-environment-variables.ts
 └── default.ts
├── http/
 ├── getUsers.http-request
 ├── login.http-request
 ├── me.http-request
 └── register.http-request
├── src/
 ├── controllers/
  ├── auth.controller.ts
  └── user.controller.ts
 ├── middleware/
  ├── deserializeUser.ts
  ├── requireUser.ts
  ├── restrictTo.ts
  └── validate.ts
 ├── models/
  └── user.model.ts
 ├── routes/
  ├── auth.route.ts
  └── user.route.ts
 ├── schema/
  └── user.schema.ts
 ├── services/
  └── user.service.ts
 ├── utils/
  ├── appError.ts
  ├── connectDB.ts
  ├── connectRedis.ts
  └── jwt.ts
 └── app.ts
├── .env
├── .gitignore
├── docker-compose.yml
├── package.json
├── tsconfig.json
└── yarn.lock

Setup project node.js

Tạo 1 project node.js

mkdir jwt_authentication_authorization_node
cd jwt_authentication_authorization_node
npm init

Chạy lệnh bên dưới để cài đặt TypeScript như một dependency. Điều này sẽ cho phép chúng ta biên dịch mã TypeScript thành mã JavaScript thuần sử dụng trình biên dịch TypeScript.

npm install -D typescript

Chạy lệnh sau để khởi tạo dự án TypeScript. File tsconfig.json sẽ được tạo trong thư mục gốc của bạn.

npx tsc --init

Thêm các tùy chọn cấu hình sau vào file tsconfig.json của bạn để cho phép chúng ta sử dụng các decorators và tính năng khác trong mã của chúng ta.

{
  "compilerOptions": {
    "target": "es2016",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true
  }
}

Các cấu hình quan trọng trong tsconfig.json

  • experimentalDecorators: true

  • emitDecoratorMetadata: true

  • strictPropertyInitialization: false

Cài đặt các Dependencies

npm install @typegoose/typegoose bcryptjs config cookie-parser dotenv express jsonwebtoken lodash mongoose redis ts-node-dev zod cors
  • @typegoose/typegoose – Định nghĩa mô hình Mongoose bằng TypeScript.

  • mongoose – ODM kết nối và thao tác với MongoDB.

  • redis – Kết nối Redis, hỗ trợ caching và session.

  • bcryptjs – Băm mật khẩu bằng bcrypt.

  • jsonwebtoken – Tạo và xác thực JWT.

  • cookie-parser – Phân tích cookie từ request.

  • cors – Xử lý CORS giữa frontend và backend.

  • config – Quản lý cấu hình theo môi trường.

  • dotenv – Tải biến môi trường từ file .env.

  • express – Framework xây dựng API backend.

  • lodash – Các hàm tiện ích cho object, array, string.

  • zod – Xác thực và định dạng dữ liệu đầu vào.

  • ts-node-dev – Chạy TypeScript không cần build, hỗ trợ auto-restart. 🚀

Cài đặt các devDependencies

npm install -D morgan typescript
  • morgan → Middleware log request HTTP.

  • typescript → Compiler hỗ trợ TypeScript.

  • -D (hoặc --save-dev) → Chỉ cài trong môi trường phát triển, không ảnh hưởng đến production.

npm install -D @types/bcryptjs @types/config @types/cookie-parser @types/express @types/jsonwebtoken @types/lodash @types/morgan @types/node @types/cors
  • @types/bcryptjs – Kiểu TypeScript cho bcryptjs.

  • @types/config – Kiểu TypeScript cho config.

  • @types/cookie-parser – Kiểu TypeScript cho cookie-parser.

  • @types/express – Kiểu TypeScript cho express.

  • @types/jsonwebtoken – Kiểu TypeScript cho jsonwebtoken.

  • @types/lodash – Kiểu TypeScript cho lodash.

  • @types/morgan – Kiểu TypeScript cho morgan.

  • @types/node – Kiểu TypeScript cho API Node.js.

  • @types/cors – Kiểu TypeScript cho cors.

Khởi tạo và Khởi động Express Server

Tạo thư mục src và trong đó tạo file app.ts. Sao chép và dán mã nguồn mẫu cho server Express. Đây là một phần của code để bắt đầu start server Express:

//src/app.ts
require('dotenv').config();
import express from 'express';
import config from 'config';

const app = express();

// Testing
app.get('/healthChecker', (req: Request, res: Response, next: NextFunction) => {
  res.status(200).json({
    status: 'success',
    message: 'Welcome to Inovation Thinking😂😂👈👈',
  });
});


const port = config.get<number>('port');
app.listen(port, () => {
  console.log(`Server started on port: ${port}`);
});

Đoạn mã trên sử dụng dotenv để quản lý biến môi trường từ file .env, khởi tạo ứng dụng Express và lắng nghe trên cổng xác định, yêu cầu tạo file .env trong thư mục gốc để lưu cấu hình.

NODE_ENV=development
MONGODB_USERNAME=inovationthinking
MONGODB_PASSWORD=password123
MONGODB_DATABASE_NAME=jwtAuth

ACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBd0dybHFGalc3cjdIaXo4UEExQ0wzbVN1ZHJ2OHFIcEtOUEpqK1RMUEhxNkVSUHRtCkV6MklBRWhzZnlobW9RWHJ5YVRjQkpVMEJGeUVzemdabmpmM2JLSHBYWkM4cHhQemJJTEZ5RjkzREZJaUJINmEKdDBQblhiSERqK3pacTYyWXJrYzN4WTI2a0tOekNJOTJnMnJFbE5vUnhtVk9FcUFvT0xGdEt4VVZqMGE5bm42MAp2SFpTNmJwbnhTOFhCWUVBUGRKMWZZODZUYVRIaFpCczY5RWhkcUFFRTBVWFB2MHRDVzk3SkdVNmdFa0ZleUJaCjF5WjBMaDFQeXp1aUZsNFZXUExSZEpBTWdtcElTcC9nRWRxTTFHUkoreGtvMGhUOXFSVnRSUnc2M21IbGc5YWsKRUxGMUVjU01lME5uL0ZGUkpSV1h1VEdkQjd6WlVjbURDZ3V1YmowUnZERTMzQnNDNm1LbEFod1NjSUx1QU5sZAoxRThNZlVZQzRrenJ0eFdUZkdESDRXZGtaRllyTG84UlIzdWs2elJlaWZBZHJqTjlYTWZQZ3BneGVRRThQRE1KCmx2MmdkdEZ5eU8vbDhYZGhDVFMrSStLaGZCOWUwakRVVkMveHV1d3JGY1BaVndHNnhEL0RuaHdnelVFRjZSczIKbGpEejNjcHhKUC9oa2JFTUVPYi8vekx3V0drZjhSNENXY1UyNjFnbHorSERjZWVBNDV4bVhsa3djVkNCOUdCcwp6aGJNWXNqUU9SdGUrTXZUdU1aSmFQZnVYTW1XRWxhREo2QUFkMkRsUWJBVG82bVBGU0ErRFgxb1RrZVU1UmlECndsNXBZeWsrOHNKNHNOM2F4WHAyMkFVMVlEWVE0SXozd0lPM1Fmb1pIditQTXFKekI2VWY2N0ZML1RrQ0F3RUEKQVFLQ0FnQjE1Z25wNk9WcFRBUkFVZGNGRk9sZXp4b0hMcEJWT3ZrVkVDQXBwUFE3dkhyWE9hTUZ6d0h5Q201UQpTNVQydlFZSWU3ZEVKNWZEeEZ5YTQ1anUxU1FKci91cGxQSEMvZnA5Vm5PUm5zejNBNnhNVExiSDdCZHIxV3dhClYrblh3M3AxN3JWQm11SGhsZ1Q2RGMxMElJdHJHV01peVJmWldjRExYQXVrQmpzN213QzhpSzU5ZTVLNkc3bFIKbk5UaVRuU3piSzBJemlYUFJWUHJodDcyYnlHdDZjWVZlSlFSeUZjOEhNNjdNanR5TjB2Z2NhWWFxamt0dUZBWQpHdVhxQnFQVjZKSm1kWXowcStLM3R0WTRtazBJSnBzZC9BQ0RHTkdFTk5qTEs4ejJUYzJ2eG1pb3dkTVZtL1RuCjRobHBCUHBQV3Jlb2hibk43K3pJckV4YWIyWGtuRXlHTE5DSkdWK2FSSG5PYjF4UmM5emI2SVEyMFpvcGs3c3AKd1ZFMkxSRWJrbWJTUVhNbVcyYmxpR3h3NFJPdXdIaytORTZuVUF1M2k0WGdOd0pzTGtwSTNpUEFyaXpaMmxidwpVV2NPUC9kMG4rWndTOEFGM25TTWQ4SDBIRmlCNUtSSXhFaW11ZW1MWDVXNFh3ZjZpa0tRSDl3bkt0aU9kamdjCnJVT0ZIN2RuMm1SeW54bDR6a2YwT3Z6eWhWY3ZBUDUyUG9SVVYycUF5bjJmY0w2WjFIMStHMlRma29WVmpacU4KWHNGbUlHTjFLMzdROXN6YU4zTXByNFlvQzBadFBUYkJzK0FiUVd0dnEzb2dqdjZrV0xKazR2NldaUnZEK3AwQgpKVDFxaDNrc1FRZFlIdkkwT2RwN2tRNXpnRzlacVM1NXV3NTNIQUl2MjZ4R3VoWFR3UUtDQVFFQStNbU5xZkpTClMyQ0ZsTTQ3TUd4SFc0Wmh2Q1RuTDEweUVSUEh0YzFNN20vcVllaUNkNmJLWkpPbGN5b2VqSEoveXFwNGFNcHoKaG8wU21nMStHOGJrZmpNek42Wm1ha0cxYjVQMlp3alRrMWovaHluSFJrWG05eWMyVmpERTNUVlFYWFp6ODVwTAp3UGYxTDBoSWVGb2xSbUQySUl1OTh2WVZXdS9GMUMxak1HYnFMQnhaV1IvUnNJdFlnN3JlL0x3ekI1VnBTb0xICkR1dWQzRlBPMXR4NXRHOWpzT3N1OVR2dXZaTVljT1ZqWC9FSUVwblpSYXRUUklZam9aZSs2RFRoaDdGMTZFNlgKUkNXNjlxT2Vrd09UUlZGYXZqL25KcVZ3NkZPMkM4OEdIUjFGd3lFUU92N1doMDFFaEQ4WCtJS2tVOWRWS0FhWApzUG5Ma3ZjVGhVRitCUUtDQVFFQXhmNzZ1bThYam5OU2NYTTBUM2RXS1VidWFYNWszTjMzQ3ZzYTZ6TmhISFZ5Cno4SDljamZORzJlanZHUU5vNXdZVGoxRkZMSFdzamt5ekxHWXlxS2NLRkJxbnZHck9KUVRyWC8ra21lcDJBK08KZzdhdkt1ZGR4NVNLSjV3UVE4bnEwV0FxVUhRNENvVURTRVVlNHFIemE4SDZvNjduemNwcW9IbTZoOGo1a1hqYwpEWTlqT0lYakFsSzgySDQ0UE5QWG1rMWo0YXdidzVwL3JtS2V0OE9lSWsvTkNpclRCMUdFZVJndkVMMXZYUHVyCnZHZzFOY2c0Vmp3RUFnOUtoQ1FScCtqb1pwTmFlQllXQWFodXpuTXhpaU9HODhHWE5iWXJTSHg5Wk9SRXk4aXIKZkk4U3BSaE1xTlNwQ3RNemJVWno3Vm9GV2JaeEFTeDBEdXVOSW1YMHBRS0NBUUVBNXFKYTNPaVMxK1AwRWg0WgppdXRtUDNmVmxRaVU5VGl0V0YyQTc0NFNPcHl2cVBKV09Mdjd0citWU3EwS1F1Tkdpc2Y3OWhGd2haUzBZUElQCkxZcjFlZlRYRDBrSWVvck51MUZzeE5uTzRqTklON0pJVldJcUdvZFVmUlNhL0FNWHJIMUtRdE9RVktUSnZIcUQKREdkdFZOQkFlNjF3ZXhNY2V2LzY0cGJzOUFzRUhiNXVLZ3d3WlR6WTRzM1RPSEx6ejV6NFRpWHNpVzF1RzducAo3dy9YRjZtSHZwUllKT25aaWc4YVFsYTFDRlUzU1o4c1o4VEszYVNJMVo0S1VkUHNHOUlzM3g0MFp1MmZaRlFNCmhuZHpDSGpCNmNydDY4ckZYK3R5d1lHN1JqUkQzd0FBdnVCT1dvSUwrWmxRRElaMzltMlNPUmZiZWlvb1NlY1oKUnBpUFRRS0NBUUFlUUNmVXBqYUdLQzUzY08rVUdKcU1jZTdwSlV1Sngwd0FYSDh2WWtrN0RPSyt4VmZReEovTQp5UmZtSjY5QnlRNlpuWmpaWVpaNDRtNVZnZWpqUk5idy9lQmNhbllMamV3M3ZPK0xOTlZwVW04bXhwbWF4NEMzCmhvVlpLZW4rUVhKa0RQcEtFb2VoYTlNbGpwSDZkRjM1bjhpSWk2ZVU5SkUzOVlFL1Q4QjVybXFJazlqSUFRUy8KRFI4WFFLbWMrWXplWVdhYVN5NXV3ME13eEphVll3amRHeTRybUlGbmc5Zm1uSUJNWVhVTFV0UlpVOTZWV2dMcApnZi9teEtsUTZTWGRicU5iVUxZbzFNOEY3OU1HTGVscXZxVFd4MFF3QzZZdlMvM29sVXZCaXVaUWdKZUxxOXZDCmk4Tk1DUnE1WG1ORjUxUWI4ZGp3SWZlVmMvMjdQTEtWQW9JQkFIQ05zQ0FCMi96RVc2bXZ4cDdHSUVXVzdGNnMKZ1gxaTdBVXRpZG12ZEtORCs3WFN4VHRnc1M4ai9DZGNabmJZRWNYdEVWeU1PeUU2MFVDSlJQZktzSmJBdXhDYQpWdmJRWFNrTW9MblZNaXI2aUZxalJ0Mitka0hMTC9iVU1Sa1hTSDAzT0pHSXZTOEovbVRBVGhtOUF6N2hkN3BTCm1UNlNHdFFhWDhhbHdoVVgzT1p6V2JWazVHZXEvMWFybVo4cEhBeTkxdWNHMW9rQy9ZRkE3RDFOM2pGOWVQVGwKMVBJVFlLVDJiQmhTMm1HcVM4VkVDWGZ4dFJDenpOVHZCTUhJbS94ckx2WFZYdmYxUVVCM1BXelA2alhseTlrNwpEbm1meE5YSVFubis5Z2cvcHlNU0t0OHNXN3AyMUVuSUR5WjFCSWtydS9YVjY5SEpTWkpZeUhKYURnbz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0=
ACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUF3R3JscUZqVzdyN0hpejhQQTFDTAozbVN1ZHJ2OHFIcEtOUEpqK1RMUEhxNkVSUHRtRXoySUFFaHNmeWhtb1FYcnlhVGNCSlUwQkZ5RXN6Z1puamYzCmJLSHBYWkM4cHhQemJJTEZ5RjkzREZJaUJINmF0MFBuWGJIRGorelpxNjJZcmtjM3hZMjZrS056Q0k5MmcyckUKbE5vUnhtVk9FcUFvT0xGdEt4VVZqMGE5bm42MHZIWlM2YnBueFM4WEJZRUFQZEoxZlk4NlRhVEhoWkJzNjlFaApkcUFFRTBVWFB2MHRDVzk3SkdVNmdFa0ZleUJaMXlaMExoMVB5enVpRmw0VldQTFJkSkFNZ21wSVNwL2dFZHFNCjFHUkoreGtvMGhUOXFSVnRSUnc2M21IbGc5YWtFTEYxRWNTTWUwTm4vRkZSSlJXWHVUR2RCN3paVWNtRENndXUKYmowUnZERTMzQnNDNm1LbEFod1NjSUx1QU5sZDFFOE1mVVlDNGt6cnR4V1RmR0RINFdka1pGWXJMbzhSUjN1awo2elJlaWZBZHJqTjlYTWZQZ3BneGVRRThQRE1KbHYyZ2R0Rnl5Ty9sOFhkaENUUytJK0toZkI5ZTBqRFVWQy94CnV1d3JGY1BaVndHNnhEL0RuaHdnelVFRjZSczJsakR6M2NweEpQL2hrYkVNRU9iLy96THdXR2tmOFI0Q1djVTIKNjFnbHorSERjZWVBNDV4bVhsa3djVkNCOUdCc3poYk1Zc2pRT1J0ZStNdlR1TVpKYVBmdVhNbVdFbGFESjZBQQpkMkRsUWJBVG82bVBGU0ErRFgxb1RrZVU1UmlEd2w1cFl5ays4c0o0c04zYXhYcDIyQVUxWURZUTRJejN3SU8zClFmb1pIditQTXFKekI2VWY2N0ZML1RrQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==

File .env chứa các khóa token công khai và riêng tư mã hóa base64, cùng thông tin đăng nhập cơ sở dữ liệu MongoDB cho container Docker;

Tạo thư mục config và hai file default.tscustom-environment-variables.ts, rồi thêm mã vào file default.ts.

// config/default.ts
export default {
  port: 8000,
  accessTokenExpiresIn: 15,
  origin: 'http://localhost:3000',
};

Tiếp theo, hãy thêm mã sau vào file custom-environment-variables.ts.

// custom-environment-variables.ts
export default {
  dbName: 'MONGODB_USERNAME',
  dbPass: 'MONGODB_PASSWORD',
  accessTokenPrivateKey: 'ACCESS_TOKEN_PRIVATE_KEY',
  accessTokenPublicKey: 'ACCESS_TOKEN_PUBLIC_KEY',
};

File custom-environment-variables.ts sẽ cho phép chúng ta nhập các biến môi trường mà chúng ta đã định nghĩa trong file .env.

Bây giờ hãy thêm lệnh start vào file package.json.

"scripts": {
    "start": "ts-node-dev --respawn --transpile-only src/app.ts"
  }

Nếu bạn đã làm theo tất cả các hướng dẫn ở trên, file package.json cuối cùng của bạn sẽ trông như thế này:

{
  "name": "jwt_authentication_authorization_nod",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "ts-node-dev --respawn --transpile-only src/app.ts"
  },
    ....
}

Cuối cùng, mở terminal của bạn và chạy lệnh npm start bắt đầu để khởi động server Express. Gõ liên kết http://localhost:8000/healthChecker này để kiểm tra server.

Cài đặt Redis và MongoDB sử dụng Docker Compose

Tôi tin rằng bạn đã cài đặt docker trên máy tính của mình, khi đã đọc hướng dẫn mà tôi đã đính kèm link ở đầu bài viết.

Trong thư mục gốc, tạo file docker-compose.yml và thêm mã bên dưới.

version: '3.8'
services:
  mongo:
    image: mongo:latest
    container_name: mongo
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USERNAME}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD}
      MONGO_INITDB_DATABASE: ${MONGODB_DATABASE_NAME}
    env_file:
      - ./.env
    volumes:
      - mongo:/data/db
    ports:
      - '6000:27017'
  redis:
    image: redis:latest
    container_name: redis
    ports:
      - '7000:6379'
    volumes:
      - redis:/data
volumes:
  mongo:
  redis:

Chú ý là phiên bản docker-compose của bạn phải > 2 nhé, nếu sử dụng version < 2 vui lòng update lên theo lệnh sau:

sudo apt update
sudo apt install -y curl
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Bây giờ, hãy mở terminal của bạn và chạy lệnh bên dưới để khởi động các container Mongo và Redis.

docker-compose up -d

Kết nối với MongoDB server với Mongoose

Vì chúng ta đã thiết lập và run cơ sở dữ liệu MongoDB, nên chúng ta cần kết nối ứng dụng Express của mình với Mongoose. Trong thư mục src, tạo một thư mục utils và trong thư mục utils này, hãy tạo file connectDB.ts và dán code này vào đó.

// src/utils/connectDB.ts
import mongoose from 'mongoose';
import config from 'config';

const dbUrl = `mongodb://${config.get('dbName')}:${config.get(
  'dbPass'
)}@localhost:6000/jwtAuth?authSource=admin`;

const connectDB = async () => {
  try {
    await mongoose.connect(dbUrl);
    console.log('Database connected...');
  } catch (error: any) {
    console.log(error.message);
    setTimeout(connectDB, 5000);
  }
};

export default connectDB;

URL kết nối cơ sở dữ liệu có cấu trúc như sau:

const dbUrl = `mongodb://username:password@host:port/database?authSource=admin`
  • mongodb://: Chỉ định rằng đây là một kết nối đến cơ sở dữ liệu MongoDB.

  • username:password: Thông tin tài khoản để xác thực kết nối với MongoDB. Thay usernamepassword bằng thông tin thật của bạn.

  • @: Phân tách thông tin đăng nhập với địa chỉ máy chủ.

  • host:port: Địa chỉ và cổng của server MongoDB. host là tên hoặc địa chỉ IP của server MongoDB, và port là cổng mà MongoDB đang chạy (mặc định là 27017).

  • /database: Tên cơ sở dữ liệu mà bạn muốn kết nối. Thay database bằng tên cơ sở dữ liệu của bạn.

  • ?authSource=admin: Tham số authSource xác định cơ sở dữ liệu mà MongoDB sẽ sử dụng để xác thực người dùng (ở đây là admin).

Kết nối với Redis server

Tiếp theo, hãy kết nối ứng dụng express của chúng ta với Redis container. Trong thư mục utils, tạo một file connectRedis.ts mới, sau đó sao chép và dán đoạn code sau:

// src/utils/connectRedis.ts
import { createClient } from 'redis';

const redisUrl = `redis://localhost:7000`;
const redisClient = createClient({
  url: redisUrl,
});

const connectRedis = async () => {
  try {
    await redisClient.connect();
    console.log('Redis client connected...');
  } catch (err: any) {
    console.log(err.message);
    setTimeout(connectRedis, 5000);
  }
};

connectRedis();

redisClient.on('error', (err) => console.log(err));

export default redisClient;
  • Import Redis client: import { createClient } from 'redis';

  • Xây dựng URL kết nối Redis: const redisUrl = redis://localhost:7000;

  • Tạo Redis client: const redisClient = createClient({ url: redisUrl });

  • Hàm kết nối Redis:

    • Thử kết nối với await redisClient.connect().

    • Nếu kết nối thành công, hiển thị thông báo thành công.

    • Nếu thất bại, in lỗi và thử lại sau 5 giây.

  • Gọi hàm kết nối: connectRedis();

  • Lắng nghe lỗi Redis: redisClient.on('error', (err) => console.log(err));

  • Xuất Redis client: export default redisClient;

Bây giờ, hãy mở file app.ts và sử dụng connectDB mà chúng ta đã định nghĩa trong file connectDB.ts

//src/app.ts
require('dotenv').config();
import express from 'express';
import config from 'config';
import connectDB from './utils/connectDB';

const app = express();

const port = config.get<number>('port');
app.listen(port, () => {
  console.log(`Server started on port: ${port}`);
  // ? call the connectDB function here
  connectDB();
});

Tạo database schema với Typegoose

Bây giờ, hãy tạo một thư mục models trong thư mục src và trong thư mục models, hãy tạo file user.model.ts.

Typegoose sử dụng rất nhiều decorators để định nghĩa model Mongoose.

// src/models/user.model.ts
import {
  DocumentType,
  getModelForClass,
  index,
  modelOptions,
  pre,
  prop,
} from '@typegoose/typegoose';
import bcrypt from 'bcryptjs';

@index({ email: 1 })
@pre<User>('save', async function () {
  // Hash password if the password is new or was updated
  if (!this.isModified('password')) return;

  // Hash password with costFactor of 12
  this.password = await bcrypt.hash(this.password, 12);
})
@modelOptions({
  schemaOptions: {
    // Add createdAt and updatedAt fields
    timestamps: true,
  },
})

// Export the User class to be used as TypeScript type
export class User {
  @prop()
  name: string;

  @prop({ unique: true, required: true })
  email: string;

  @prop({ required: true, minlength: 8, maxLength: 32, select: false })
  password: string;

  @prop({ default: 'user' })
  role: string;

  // Instance method to check if passwords match
  async comparePasswords(hashedPassword: string, candidatePassword: string) {
    return await bcrypt.compare(candidatePassword, hashedPassword);
  }
}

// Create the user model from the User class
const userModel = getModelForClass(User);
export default userModel;
  • Tôi đã tạo một class user và thêm tất cả các thuộc tính mà model của chúng ta yêu cầu với các decorator Typegoose và export class đó.

  • Tôi đã sử dụng utility function getModelForClass để tạo model Mongoose từ class User mà tôi đã xác định ở trên.

  • Tôi đã sử dụng pre-save hook để băm mật khẩu chỉ khi mật khẩu mới hoặc đã được sửa đổi.

  • Sau đó tôi đã thêm trường email làm index.

Okay, bài viết đến đây cũng khá dài rồi, hẹn gặp lại các bạn ở phần 2 để hoàn thiện nốt những thứ còn dang dở. Xin chào và hẹn gặp lại.

0
Subscribe to my newsletter

Read articles from Eminel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Eminel
Eminel

Hello, my name is Eminel a software engineer specializing in scalable software architecture, microservices, and AI-powered platforms. Passionate about mentoring, performance optimization, and building solutions that drive business success.