[Series] Xây dựng Hệ thống Bất động sản với Node.js & TypeScript - Bài 2: Triển khai Authentication (Đăng ký & Đăng nhập)
Chào anh em! Ở bài 1 chúng ta đã có Database. Nhưng để người dùng có thể đăng tin, lưu tin hay đánh giá, họ cần một "tấm thẻ căn cước" trong hệ thống. Bài viết này sẽ giúp bạn xây dựng hệ thống Auth bảo mật, có validate dữ liệu chặt chẽ và cấu trúc code chuẩn chỉnh.
1. Kiến trúc thư mục và Cài đặt
Một project lớn cần sự ngăn nắp. Chúng ta sẽ tổ chức code theo pattern Controller - Service - Route.
Cài đặt Dependencies Mở terminal và "vã" ngay bộ lệnh này:
# Khởi tạo project
npm init -y
# Dependencies chính
npm install express bcryptjs jsonwebtoken zod dotenv @prisma/client
# Dev Dependencies (Cho TypeScript)
npm install prisma typescript ts-node-dev @types/express @types/bcryptjs @types/jsonwebtoken --save-dev
2. Cấu hình Prisma & Schema
Chúng ta sẽ sử dụng Prisma làm ORM để tương tác với Database đã thiết kế ở Bài 1.
File: prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql" // Có thể thay bằng mysql nếu bạn thích
url = env("DATABASE_URL")
}
model users {
id Int @id @default(autoincrement())
email String @unique
fullname String
phone String
emailVerified Boolean @default(false)
phoneVerified Boolean @default(false)
password String
avatar String?
balance BigInt @default(0)
score Int @default(0)
resetPwdToken String?
resetPwdExpiry DateTime?
idPricing Int @default(1) // Mặc định gói miễn phí
}
Lưu ý: Sau khi sửa schema, đừng quên chạy lệnh:
npx prisma generate và npx prisma migrate dev --name init_auth
3. Validation với Zod & Middleware
Đừng bao giờ tin tưởng dữ liệu từ Client! Chúng ta dùng Zod để định nghĩa schema và một Middleware để chặn các request "lỗi" ngay từ cửa ngõ.
File: src/schemas/auth.schema.ts
import { z } from 'zod';
export const registerSchema = z.object({
email: z.string().email("Email không hợp lệ"),
password: z.string().min(6, "Mật khẩu phải ít nhất 6 ký tự"),
fullname: z.string().min(2, "Tên quá ngắn"),
phone: z.string().min(8, "Số điện thoại không hợp lệ")
});
export const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(6)
});
File: src/middlewares/validate.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export const validate = (schema: ZodSchema) => {
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
schema.parse(req.body);
next();
} catch (e: any) {
res.status(400).json({ error: e.errors });
}
};
};
4. Xử lý Logic tại Service Layer
Service là nơi chứa "trí thông minh" của ứng dụng. Chúng ta xử lý Hash password bằng bcryptjs và tạo Token bằng jsonwebtoken.
File: src/services/auth.service.ts
import prisma from '../prisma/client';
import bcrypt from 'bcryptjs';
import { signToken } from '../utils/jwt';
export const registerUser = async (email: string, password: string, fullname: string, phone: string) => {
const existingUser = await prisma.users.findUnique({ where: { email } });
if (existingUser) throw new Error('Email đã tồn tại trên hệ thống');
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await prisma.users.create({
data: { email, password: hashedPassword, fullname, phone, idPricing: 1 }
});
const token = signToken({ userId: newUser.id, email: newUser.email });
return { user: newUser, token };
};
export const loginUser = async (email: string, password: string) => {
const user = await prisma.users.findUnique({ where: { email } });
if (!user) throw new Error('Email hoặc mật khẩu không chính xác');
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new Error('Email hoặc mật khẩu không chính xác');
const token = signToken({ userId: user.id, email: user.email });
return { user, token };
};
5. Hoàn thiện Controller & Routes
Cuối cùng, chúng ta kết nối mọi thứ lại với nhau.
File: src/controllers/auth.controller.ts
import { Request, Response } from 'express';
import { registerUser, loginUser } from '../services/auth.service';
export const register = async (req: Request, res: Response) => {
try {
const { email, password, fullname, phone } = req.body;
const result = await registerUser(email, password, fullname, phone);
// Lưu ý: BigInt cần convert sang String trước khi trả về JSON
res.status(201).json(JSON.parse(JSON.stringify(result, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
)));
} catch (err: any) {
res.status(400).json({ error: err.message });
}
};
// ... Tương tự cho login
File: src/routes/auth.routes.ts
import { Router } from 'express';
import { register, login } from '../controllers/auth.controller';
import { validate } from '../middlewares/validate.middleware';
import { registerSchema, loginSchema } from '../schemas/auth.schema';
const router = Router();
router.post('/register', validate(registerSchema), register);
router.post('/login', validate(loginSchema), login);
export default router;
6. Tổng kết
Vậy là chúng ta đã hoàn thành hệ thống Authentication cơ bản nhưng cực kỳ chắc chắn cho dự án Bất động sản:
Bảo mật: Password được hash 10 vòng với Bcrypt.
Xác thực: Sử dụng JWT (JSON Web Token) thời hạn 7 ngày.
Tin cậy: Dữ liệu đầu vào được Validate nghiêm ngặt bởi Zod.
All rights reserved