PROMPT: Xây dựng Website Cửa Hàng Bán Sách (BookStore)
Xây dựng một website cửa hàng bán sách hoàn chỉnh fullstack, chạy local, hỗ trợ đa ngôn ngữ (Tiếng Việt / English), bao gồm giao diện người dùng, trang quản trị admin, và tích hợp thanh toán QR Code.
Layer
Công nghệ
Frontend
React 18+ (Vite)
Styling
Tailwind CSS 3+
State Mgmt
Zustand hoặc React Context
Routing
React Router v6
i18n
react-i18next
Backend
Node.js + Express.js
ORM
Prisma
Database
PostgreSQL
Authentication
JWT (Access Token + Refresh Token)
Payment
QR Code (VietQR API / static QR)
Image Upload
Multer (lưu local /uploads)
Validation
Zod (shared giữa FE & BE)
BookStore/
├── client/ # Frontend React
│ ├── public/
│ │ └── locales/ # i18n translation files
│ │ ├── vi/
│ │ │ └── translation.json
│ │ └── en/
│ │ └── translation.json
│ ├── src/
│ │ ├── assets/ # Hình ảnh, icons
│ │ ├── components/ # Shared components
│ │ │ ├── ui/ # Button, Input, Modal, Card, Badge, Pagination...
│ │ │ ├── layout/ # Header, Footer, Sidebar, AdminLayout
│ │ │ └── common/ # LanguageSwitcher, StarRating, SearchBar, CartIcon
│ │ ├── features/ # Feature-based modules
│ │ │ ├── auth/ # Login, Register, ForgotPassword
│ │ │ ├── books/ # BookList, BookDetail, BookCard, BookFilter
│ │ │ ├── cart/ # CartPage, CartItem, CartSummary
│ │ │ ├── checkout/ # CheckoutPage, QRPayment, OrderConfirm
│ │ │ ├── orders/ # OrderHistory, OrderDetail, OrderTracking
│ │ │ ├── reviews/ # ReviewForm, ReviewList, ReviewItem
│ │ │ ├── profile/ # UserProfile, EditProfile, AddressBook
│ │ │ └── admin/ # Dashboard, BookMgmt, OrderMgmt, UserMgmt, CategoryMgmt
│ │ ├── hooks/ # Custom hooks (useAuth, useCart, useBooks, useDebounce)
│ │ ├── lib/ # axios instance, helpers, constants
│ │ ├── store/ # Zustand stores (authStore, cartStore)
│ │ ├── routes/ # Route config, ProtectedRoute, AdminRoute
│ │ ├── i18n.ts # i18next config
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── index.html
│ ├── tailwind.config.js
│ ├── vite.config.ts
│ ├── tsconfig.json
│ └── package.json
│
├── server/ # Backend Node.js
│ ├── prisma/
│ │ ├── schema.prisma # Database schema
│ │ └── seed.ts # Seed data
│ ├── src/
│ │ ├── controllers/ # Route handlers
│ │ │ ├── auth.controller.ts
│ │ │ ├── book.controller.ts
│ │ │ ├── category.controller.ts
│ │ │ ├── cart.controller.ts
│ │ │ ├── order.controller.ts
│ │ │ ├── review.controller.ts
│ │ │ ├── user.controller.ts
│ │ │ └── payment.controller.ts
│ │ ├── routes/ # Express routes
│ │ │ ├── auth.routes.ts
│ │ │ ├── book.routes.ts
│ │ │ ├── category.routes.ts
│ │ │ ├── cart.routes.ts
│ │ │ ├── order.routes.ts
│ │ │ ├── review.routes.ts
│ │ │ ├── user.routes.ts
│ │ │ ├── payment.routes.ts
│ │ │ └── index.ts
│ │ ├── middlewares/ # Middleware
│ │ │ ├── auth.middleware.ts # JWT verify
│ │ │ ├── admin.middleware.ts # Admin role check
│ │ │ ├── upload.middleware.ts # Multer config
│ │ │ ├── validate.middleware.ts # Zod validation
│ │ │ └── error.middleware.ts # Global error handler
│ │ ├── services/ # Business logic
│ │ │ ├── auth.service.ts
│ │ │ ├── book.service.ts
│ │ │ ├── cart.service.ts
│ │ │ ├── order.service.ts
│ │ │ ├── review.service.ts
│ │ │ └── payment.service.ts
│ │ ├── validators/ # Zod schemas
│ │ │ ├── auth.validator.ts
│ │ │ ├── book.validator.ts
│ │ │ ├── order.validator.ts
│ │ │ └── review.validator.ts
│ │ ├── utils/ # Helpers
│ │ │ ├── jwt.ts
│ │ │ ├── password.ts
│ │ │ ├── qrcode.ts # QR code generation
│ │ │ └── pagination.ts
│ │ ├── types/ # TypeScript types
│ │ │ └── index.ts
│ │ └── app.ts # Express app setup
│ ├── uploads/ # Uploaded images
│ ├── tsconfig.json
│ └── package.json
│
├── .env # Environment variables
├── .gitignore
├── package.json # Root workspace (optional scripts)
├── PROMPT.md
└── README.md
generator client {
provider = " prisma-client-js "
}
datasource db {
provider = " postgresql "
url = env (" DATABASE_URL " )
}
enum Role {
USER
ADMIN
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPING
DELIVERED
CANCELLED
}
enum PaymentStatus {
UNPAID
PAID
FAILED
REFUNDED
}
model User {
id String @id @default (cuid () )
email String @unique
password String
fullName String
phone String ?
avatar String ?
role Role @default (USER )
refreshToken String ?
addresses Address []
orders Order []
reviews Review []
cart Cart ?
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
}
model Address {
id String @id @default (cuid () )
userId String
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
fullName String
phone String
street String
ward String
district String
city String
isDefault Boolean @default (false )
orders Order []
createdAt DateTime @default (now () )
}
model Category {
id String @id @default (cuid () )
name String @unique
nameEn String ?
slug String @unique
description String ?
image String ?
books Book []
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
}
model Book {
id String @id @default (cuid () )
title String
titleEn String ?
slug String @unique
author String
isbn String ? @unique
description String ?
descriptionEn String ?
price Decimal @db.Decimal (12 , 0 )
salePrice Decimal ? @db.Decimal (12 , 0 )
stock Int @default (0 )
images String []
thumbnail String ?
publisher String ?
publishedYear Int ?
pages Int ?
language String ? @default (" vi " )
categoryId String
category Category @relation (fields : [categoryId ] , references : [id ] )
reviews Review []
cartItems CartItem []
orderItems OrderItem []
avgRating Float @default (0 )
totalReviews Int @default (0 )
isFeatured Boolean @default (false )
isActive Boolean @default (true )
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
@@index ([categoryId ] )
@@index ([title ] )
@@index ([author ] )
}
model Review {
id String @id @default (cuid () )
rating Int // 1-5
comment String ?
userId String
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
bookId String
book Book @relation (fields : [bookId ] , references : [id ] , onDelete : Cascade )
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
@@unique ([userId , bookId ] )
}
model Cart {
id String @id @default (cuid () )
userId String @unique
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
items CartItem []
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
}
model CartItem {
id String @id @default (cuid () )
cartId String
cart Cart @relation (fields : [cartId ] , references : [id ] , onDelete : Cascade )
bookId String
book Book @relation (fields : [bookId ] , references : [id ] )
quantity Int @default (1 )
@@unique ([cartId , bookId ] )
}
model Order {
id String @id @default (cuid () )
orderCode String @unique // Mã đơn hàng: ORD-20260311-XXXX
userId String
user User @relation (fields : [userId ] , references : [id ] )
addressId String
address Address @relation (fields : [addressId ] , references : [id ] )
items OrderItem []
subtotal Decimal @db.Decimal (12 , 0 )
shippingFee Decimal @db.Decimal (12 , 0 ) @default (0 )
discount Decimal @db.Decimal (12 , 0 ) @default (0 )
total Decimal @db.Decimal (12 , 0 )
status OrderStatus @default (PENDING )
paymentStatus PaymentStatus @default (UNPAID )
paymentMethod String @default (" QR_CODE " )
note String ?
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
@@index ([userId ] )
@@index ([orderCode ] )
}
model OrderItem {
id String @id @default (cuid () )
orderId String
order Order @relation (fields : [orderId ] , references : [id ] , onDelete : Cascade )
bookId String
book Book @relation (fields : [bookId ] , references : [id ] )
title String // Snapshot tên sách tại thời điểm đặt
price Decimal @db.Decimal (12 , 0 ) // Snapshot giá tại thời điểm đặt
quantity Int
}
Method
Endpoint
Mô tả
Auth
POST
/register
Đăng ký tài khoản
No
POST
/login
Đăng nhập, trả JWT
No
POST
/refresh-token
Làm mới access token
No
POST
/logout
Đăng xuất, xóa refresh
Yes
GET
/me
Lấy thông tin user hiện tại
Yes
Method
Endpoint
Mô tả
Auth
GET
/
Danh sách sách (pagination, filter, search)
No
GET
/featured
Sách nổi bật
No
GET
/:slug
Chi tiết sách
No
POST
/
Thêm sách mới
Admin
PUT
/:id
Cập nhật sách
Admin
DELETE
/:id
Xóa sách
Admin
Categories (/api/categories)
Method
Endpoint
Mô tả
Auth
GET
/
Danh sách thể loại
No
POST
/
Thêm thể loại
Admin
PUT
/:id
Sửa thể loại
Admin
DELETE
/:id
Xóa thể loại
Admin
Method
Endpoint
Mô tả
Auth
GET
/
Lấy giỏ hàng hiện tại
Yes
POST
/items
Thêm sách vào giỏ
Yes
PUT
/items/:itemId
Cập nhật số lượng
Yes
DELETE
/items/:itemId
Xóa sách khỏi giỏ
Yes
DELETE
/
Xóa toàn bộ giỏ hàng
Yes
Method
Endpoint
Mô tả
Auth
POST
/
Tạo đơn hàng từ giỏ hàng
Yes
GET
/my-orders
Lịch sử đơn hàng của user
Yes
GET
/:id
Chi tiết đơn hàng
Yes
PUT
/:id/cancel
Hủy đơn hàng
Yes
GET
/
Tất cả đơn hàng (admin)
Admin
PUT
/:id/status
Cập nhật trạng thái đơn
Admin
Method
Endpoint
Mô tả
Auth
GET
/book/:bookId
Danh sách review của sách
No
POST
/book/:bookId
Viết review
Yes
PUT
/:id
Sửa review
Yes
DELETE
/:id
Xóa review
Yes
Method
Endpoint
Mô tả
Auth
POST
/qr-code/:orderId
Tạo QR code thanh toán
Yes
POST
/confirm/:orderId
Xác nhận đã thanh toán (admin hoặc webhook)
Admin
Users (/api/users) — Admin
Method
Endpoint
Mô tả
Auth
GET
/
Danh sách users
Admin
PUT
/:id
Cập nhật user
Admin
DELETE
/:id
Xóa user
Admin
Trang chủ (/) — Banner, sách nổi bật, danh mục, sách mới
Danh sách sách (/books) — Grid sách + sidebar filter (thể loại, giá, rating) + search bar + pagination
Chi tiết sách (/books/:slug) — Ảnh, thông tin, giá, nút thêm giỏ hàng, danh sách review
Đăng nhập (/login)
Đăng ký (/register)
Giỏ hàng (/cart) — Danh sách sản phẩm, tăng/giảm số lượng, tổng tiền
Thanh toán (/checkout) — Chọn địa chỉ, xác nhận đơn, hiển thị QR Code thanh toán
Lịch sử đơn hàng (/orders) — Danh sách đơn + trạng thái
Chi tiết đơn hàng (/orders/:id)
Trang cá nhân (/profile) — Thông tin, đổi mật khẩu, quản lý địa chỉ
Dashboard (/admin) — Thống kê: tổng đơn hàng, doanh thu, số sách, số user (cards + chart đơn giản)
Quản lý sách (/admin/books) — Bảng CRUD, upload ảnh, form thêm/sửa
Quản lý thể loại (/admin/categories) — CRUD thể loại
Quản lý đơn hàng (/admin/orders) — Bảng đơn hàng, cập nhật trạng thái, xác nhận thanh toán
Quản lý người dùng (/admin/users) — Danh sách user, đổi role
Authentication & Authorization
Đăng ký: email, password (hash bcrypt), fullName, phone
Đăng nhập: trả về accessToken (15 phút) + refreshToken (7 ngày, lưu DB + httpOnly cookie)
Middleware phân quyền: USER / ADMIN
Auto refresh token khi accessToken hết hạn (axios interceptor ở FE)
Admin tạo/sửa/xóa sách với form: title, author, ISBN, mô tả, giá gốc, giá khuyến mãi, số lượng tồn, thể loại, upload nhiều ảnh
Hiển thị danh sách dạng bảng với pagination, search, filter theo trạng thái
Search bar toàn cục: tìm theo tên sách, tác giả (debounce 300ms)
Filter sidebar: thể loại (checkbox), khoảng giá (range slider), đánh giá (star filter)
Sort: mới nhất, giá tăng/giảm, bán chạy, đánh giá cao
Thêm sách vào giỏ (kiểm tra tồn kho)
Tăng/giảm số lượng, xóa item
Hiển thị tổng tiền, số lượng
Icon giỏ hàng trên header có badge số lượng
Giỏ hàng lưu server-side (user đã đăng nhập)
User đặt hàng → hệ thống tạo đơn với trạng thái PENDING + UNPAID
Sinh QR Code bằng VietQR API format: https://img.vietqr.io/image/{bankId}-{accountNo}-{template}.png?amount={total}&addInfo={orderCode}
Hiển thị QR Code để user quét chuyển khoản
Admin xác nhận thanh toán thủ công → cập nhật paymentStatus = PAID
Khi thanh toán xong → trừ tồn kho
User đã mua sách mới được review (1 review / sách / user)
Rating 1-5 sao + comment
Hiển thị danh sách review + average rating trên trang chi tiết
Cập nhật avgRating và totalReviews trên bảng Book sau mỗi review
User: xem danh sách đơn, chi tiết, hủy đơn (chỉ khi PENDING)
Admin: xem tất cả đơn, filter theo trạng thái, cập nhật trạng thái (PENDING → CONFIRMED → SHIPPING → DELIVERED)
Cards: Tổng đơn hàng, Doanh thu tháng, Số sách, Số user mới
Biểu đồ doanh thu 7 ngày gần nhất (dùng recharts hoặc chart.js)
Đơn hàng gần đây (5 đơn mới nhất)
Hỗ trợ Tiếng Việt (mặc định) và English
Nút chuyển đổi ngôn ngữ trên Header (icon cờ hoặc dropdown)
Dịch toàn bộ UI labels, messages, button text, placeholder
Nội dung sách: dùng trường titleEn, descriptionEn, nameEn (category) — hiển thị theo ngôn ngữ đang chọn, fallback về tiếng Việt nếu không có bản dịch
Color scheme : Tông ấm — primary: amber/brown (#92400e, #d97706), neutral: warm gray, accent: emerald cho success
Typography : Font sans-serif (Inter hoặc Be Vietnam Pro cho tiếng Việt)
Layout : Header cố định, max-width 1280px cho content, responsive mobile-first
Components : Rounded corners (rounded-lg), subtle shadows, hover transitions
Toast notifications : Dùng react-hot-toast cho feedback (thêm giỏ hàng, đặt hàng thành công...)
Loading : Skeleton loader cho danh sách sách, spinner cho actions
Empty states : Hiển thị illustration + message khi giỏ hàng trống, chưa có đơn hàng...
Tạo file seed với:
2 users: 1 admin (admin@bookstore.com / admin123), 1 user (user@bookstore.com / user123)
8 categories: Văn học, Kinh tế, Kỹ năng sống, Thiếu nhi, Khoa học, Công nghệ, Lịch sử, Tâm lý
20+ sách mẫu phân bổ đều các thể loại (dữ liệu tiếng Việt thực tế, có cả titleEn)
Một số review mẫu
Environment Variables (.env)
# Database
DATABASE_URL = " postgresql://postgres:password@localhost:5432/bookstore"
# JWT
JWT_ACCESS_SECRET = " your-access-secret-key"
JWT_REFRESH_SECRET = " your-refresh-secret-key"
JWT_ACCESS_EXPIRES_IN = " 15m"
JWT_REFRESH_EXPIRES_IN = " 7d"
# Server
PORT = 5000
CLIENT_URL = " http://localhost:5173"
# VietQR (thanh toán QR)
VIETQR_BANK_ID = " 970422"
VIETQR_ACCOUNT_NO = " 0123456789"
VIETQR_ACCOUNT_NAME = " BOOKSTORE"
VIETQR_TEMPLATE = " compact2"
# 1. Cài đặt dependencies
cd server && npm install
cd ../client && npm install
# 2. Setup database
cd server
npx prisma migrate dev --name init
npx prisma db seed
# 3. Chạy Backend (port 5000)
cd server && npm run dev
# 4. Chạy Frontend (port 5173)
cd client && npm run dev
TypeScript cho cả FE và BE
ESLint + Prettier đã config
Error handling thống nhất: { success: boolean, data?: any, message?: string, errors?: any[] }
Pagination response: { data: [], meta: { page, limit, total, totalPages } }
API prefix: /api/v1/...
CORS config cho localhost
Tất cả giá tiền dùng đơn vị VNĐ, format: xxx.xxx đ
Image upload: max 5MB, chấp nhận jpg/png/webp