[ Better ] Cấu trúc thư mục NODEJS API linh hoạt cho CURD và 10 lợi ích
Dưới đây là [ Better ] Cấu trúc thư mục NODEJS API linh hoạt và cách mở rộng cấu trúc hiện tại để thêm CRUD cho đối tượng Order, Product, User tận dụng lại CURD
Table of Contents
Cấu trúc thư mục với Order
project-root/ │ ├── controllers/ │ ├── AbstractController.js │ ├── CRUDController.js │ └── ExtendedCRUDController.js │ ├── models/ │ ├── User.js │ ├── Product.js │ └── Order.js │ ├── routes/ │ ├── DynamicRoutes.js │ ├── userRoutes.js │ ├── productRoutes.js │ ├── orderRoutes.js │ └── userProductRoutes.js │ ├── services/ │ ├── AbstractService.js │ ├── CRUDService.js │ └── ExtendedCRUDService.js │ ├── config/ │ └── serverConfig.js │ ├── servers/ │ ├── userServer.js │ ├── productServer.js │ ├── orderServer.js │ └── userProductServer.js │ ├── package.json └── README.md
Nội dung các file thêm cho Order
1. Models
1.1. Order.js
const mongoose = require('mongoose'); const orderSchema = new mongoose.Schema({ product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }, user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, quantity: Number, status: String, orderDate: { type: Date, default: Date.now } }); const Order = mongoose.model('Order', orderSchema); module.exports = Order;
2. Routes
2.1. orderRoutes.js
const CRUDController = require('../controllers/CRUDController'); const CRUDService = require('../services/CRUDService'); const Order = require('../models/Order'); const createRoutes = require('./DynamicRoutes'); const orderService = new CRUDService(Order); const orderController = new CRUDController(orderService); const orderRoutes = createRoutes(orderController); module.exports = orderRoutes;
3. Servers
3.1. orderServer.js
const express = require('express'); const orderRoutes = require('../routes/orderRoutes'); const config = require('../config/serverConfig'); const mongoose = require('mongoose'); const app = express(); app.use(express.json()); app.use('/api/orders', orderRoutes); mongoose.connect(config.dbConnectionString, { useNewUrlParser: true, useUnifiedTopology: true }); app.listen(config.port, () => { console.log(`Order server is running on port ${config.port}`); });
package.json
Cập nhật tệp package.json để thêm lệnh khởi động cho Order server:
{ "scripts": { "start": "node servers/userServer.js", "start:product": "node servers/productServer.js", "start:order": "node servers/orderServer.js", "start:userProduct": "node servers/userProductServer.js" } }
README.md
Cập nhật tệp README.md để phản ánh sự bổ sung của Order.
# Dynamic CRUD Application This is a dynamic CRUD application built using Express.js and MongoDB. It supports creating, reading, updating, and deleting resources, with dynamic route handling for different models. ## Getting Started ### Prerequisites - Node.js - MongoDB ### Installing 1. Clone the repository: ```bash git clone <repository-url> cd project-root
- Install dependencies:
npm install
- Set up the environment variables:
Create a .env file in the root directory and add the following:
PORT=3000 DB_CONNECTION_STRING=mongodb://localhost:27017/mydatabase
- Start the servers:
npm start npm run start:product npm run start:order npm run start:userProduct
Project Structure
controllers/
: Contains controller classes for handling CRUD operations.models/
: Contains Mongoose models for different resources.routes/
: Contains route definitions for different resources.services/
: Contains service classes for business logic and data manipulation.config/
: Contains configuration files.servers/
: Contains server files for starting the application.
Để tổ chức các thư mục và tệp tin cho từng đối tượng (User, Product, Order) một cách rõ ràng, bạn có thể tạo các thư mục riêng biệt cho từng đối tượng.
Dưới đây là cấu trúc thư mục được tổ chức lại:
Cấu trúc thư mục được tổ chức lại
project-root/ │ ├── controllers/ │ ├── AbstractController.js │ ├── user/ │ │ └── UserController.js │ ├── product/ │ │ └── ProductController.js │ └── order/ │ └── OrderController.js │ ├── models/ │ ├── User.js │ ├── Product.js │ └── Order.js │ ├── routes/ │ ├── DynamicRoutes.js │ ├── user/ │ │ └── userRoutes.js │ ├── product/ │ │ └── productRoutes.js │ └── order/ │ └── orderRoutes.js │ ├── services/ │ ├── AbstractService.js │ ├── user/ │ │ └── UserService.js │ ├── product/ │ │ └── ProductService.js │ └── order/ │ └── OrderService.js │ ├── config/ │ └── serverConfig.js │ ├── servers/ │ ├── userServer.js │ ├── productServer.js │ └── orderServer.js │ ├── package.json └── README.md
Nội dung các file
1. Controllers
1.1. AbstractController.js
class AbstractController { constructor(service) { if (new.target === AbstractController) { throw new TypeError("Cannot construct AbstractController instances directly"); } this.service = service; } async create(req, res) { throw new Error("Method 'create()' must be implemented."); } async getAll(req, res) { throw new Error("Method 'getAll()' must be implemented."); } async getById(req, res) { throw new Error("Method 'getById()' must be implemented."); } async update(req, res) { throw new Error("Method 'update()' must be implemented."); } async delete(req, res) { throw new Error("Method 'delete()' must be implemented."); } } module.exports = AbstractController;
1.2. user/UserController.js
const AbstractController = require('../AbstractController'); class UserController extends AbstractController { constructor(service) { super(service); } async create(req, res) { try { const data = await this.service.create(req.body); res.status(201).json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getAll(req, res) { try { const data = await this.service.getAll(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getById(req, res) { try { const data = await this.service.getById(req.params.id); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async update(req, res) { try { const data = await this.service.update(req.params.id, req.body); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async delete(req, res) { try { const success = await this.service.delete(req.params.id); if (success) { res.status(204).end(); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } } module.exports = UserController;
1.3. product/ProductController.js
const AbstractController = require('../AbstractController'); class ProductController extends AbstractController { constructor(service) { super(service); } async create(req, res) { try { const data = await this.service.create(req.body); res.status(201).json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getAll(req, res) { try { const data = await this.service.getAll(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getById(req, res) { try { const data = await this.service.getById(req.params.id); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async update(req, res) { try { const data = await this.service.update(req.params.id, req.body); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async delete(req, res) { try { const success = await this.service.delete(req.params.id); if (success) { res.status(204).end(); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } } module.exports = ProductController;
1.4. order/OrderController.js
const AbstractController = require('../AbstractController'); class OrderController extends AbstractController { constructor(service) { super(service); } async create(req, res) { try { const data = await this.service.create(req.body); res.status(201).json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getAll(req, res) { try { const data = await this.service.getAll(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getById(req, res) { try { const data = await this.service.getById(req.params.id); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async update(req, res) { try { const data = await this.service.update(req.params.id, req.body); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async delete(req, res) { try { const success = await this.service.delete(req.params.id); if (success) { res.status(204).end(); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } } module.exports = OrderController;
2. Routes
2.1. DynamicRoutes.js
const express = require('express'); function createRoutes(controller) { const router = express.Router(); router.post('/', (req, res) => controller.create(req, res)); router.get('/', (req, res) => controller.getAll(req, res)); router.get('/:id', (req, res) => controller.getById(req, res)); router.put('/:id', (req, res) => controller.update(req, res)); router.delete('/:id', (req, res) => controller.delete(req, res)); return router; } module.exports = createRoutes;
2.2. user/userRoutes.js
const UserController = require('../../controllers/user/UserController'); const UserService = require('../../services/user/UserService'); const User = require('../../models/User'); const createRoutes = require('../DynamicRoutes'); const userService = new UserService(User); const userController = new UserController(userService); const userRoutes = createRoutes(userController); module.exports = userRoutes;
2.3. product/productRoutes.js
const ProductController = require('../../controllers/product/ProductController'); const ProductService = require('../../services/product/ProductService'); const Product = require('../../models/Product'); const createRoutes = require('../DynamicRoutes'); const productService = new ProductService(Product); const productController = new ProductController(productService); const productRoutes = createRoutes(productController); module.exports = productRoutes;
2.4. order/orderRoutes.js
const OrderController = require('../../controllers/order/OrderController'); const OrderService = require('../../services/order/OrderService'); const Order = require('../../models/Order'); const createRoutes = require('../DynamicRoutes'); const orderService = new OrderService(Order); const orderController = new OrderController(orderService); const orderRoutes = createRoutes(orderController); module.exports = orderRoutes;
3. Services
3.1. AbstractService.js
class AbstractService { constructor(model) { if (new.target === AbstractService) { throw new TypeError("Cannot construct AbstractService instances directly"); } this.model = model; } async create(data) { throw new Error("Method 'create()' must be implemented."); } async getAll() { throw new Error("Method 'getAll()' must be implemented."); } async getById(id) { throw new Error("Method 'getById()' must be implemented."); } async update(id, data) { throw new Error("Method 'update()' must be implemented."); } async delete(id) { throw new Error("Method 'delete()' must be implemented."); } } module.exports = AbstractService;
3.2. user/UserService.js
const AbstractService = require('../AbstractService'); class UserService extends AbstractService { constructor(model) { super(model); } async create(data) { return await this.model.create(data); } async getAll() { return await this.model.find(); } async getById(id) { return await this.model.findById(id); } async update(id, data) { return await this.model.findByIdAndUpdate(id, data, { new: true }); } async delete(id) { const result = await this.model.findByIdAndDelete(id); return result !== null; } } module.exports = UserService;
3.3. product/ProductService.js
const AbstractService = require('../AbstractService'); class ProductService extends AbstractService { constructor(model) { super(model); } async create(data) { return await this.model.create(data); } async getAll() { return await this.model.find(); } async getById(id) { return await this.model.findById(id); } async update(id, data) { return await this.model.findByIdAndUpdate(id, data, { new: true }); } async delete(id) { const result = await this.model.findByIdAndDelete(id); return result !== null; } } module.exports = ProductService;
3.4. order/OrderService.js
const AbstractService = require('../AbstractService'); class OrderService extends AbstractService { constructor(model) { super(model); } async create(data) { return await this.model.create(data); } async getAll() { return await this.model.find().populate('product user'); } async getById(id) { return await this.model.findById(id).populate('product user'); } async update(id, data) { return await this.model.findByIdAndUpdate(id, data, { new: true }); } async delete(id) { const result = await this.model.findByIdAndDelete(id); return result !== null; } } module.exports = OrderService;
4. Servers
4.1. userServer.js
const express = require('express'); const userRoutes = require('../routes/user/userRoutes'); const config = require('../config/serverConfig'); const mongoose = require('mongoose'); const app = express(); app.use(express.json()); app.use('/api/users', userRoutes); mongoose.connect(config.dbConnectionString, { useNewUrlParser: true, useUnifiedTopology: true }); app.listen(config.port, () => { console.log(`User server is running on port ${config.port}`); });
4.2. productServer.js
const express = require('express'); const productRoutes = require('../routes/product/productRoutes'); const config = require('../config/serverConfig'); const mongoose = require('mongoose'); const app = express(); app.use(express.json()); app.use('/api/products', productRoutes); mongoose.connect(config.dbConnectionString, { useNewUrlParser: true, useUnifiedTopology: true }); app.listen(config.port, () => { console.log(`Product server is running on port ${config.port}`); });
4.3. orderServer.js
const express = require('express'); const orderRoutes = require('../routes/order/orderRoutes'); const config = require('../config/serverConfig'); const mongoose = require('mongoose'); const app = express(); app.use(express.json()); app.use('/api/orders', orderRoutes); mongoose.connect(config.dbConnectionString, { useNewUrlParser: true, useUnifiedTopology: true }); app.listen(config.port, () => { console.log(`Order server is running on port ${config.port}`); });
package.json
Cập nhật tệp package.json để thêm lệnh khởi động cho Order server:
{ "scripts": { "start": "node servers/userServer.js", "start:product": "node servers/productServer.js", "start:order": "node servers/orderServer.js" } }
README.md
Cập nhật tệp README.md để phản ánh sự bổ sung của Order.
# Dynamic CRUD Application This is a dynamic CRUD application built using Express.js and MongoDB. It supports creating, reading, updating, and deleting resources, with dynamic route handling for different models. ## Getting Started ### Prerequisites - Node.js - MongoDB ### Installing 1. Clone the repository: ```bash git clone <repository-url> cd project-root
- Install dependencies:
npm install
- Set up the environment variables:
Create a .env file in the root directory and add the following:
PORT=3000 DB_CONNECTION_STRING=mongodb://localhost:27017/mydatabase
- Start the servers:
npm start npm run start:product npm run start:order
Project Structure
controllers/
: Contains controller classes for handling CRUD operations.user/
: Contains user-specific controllers.product/
: Contains product-specific controllers.order/
: Contains order-specific controllers.
user/
: Contains user-specific controllers.product/
: Contains product-specific controllers.order/
: Contains order-specific controllers.models/
: Contains Mongoose models for different resources.routes/
: Contains route definitions for different resources.user/
: Contains user-specific routes.product/
: Contains product-specific routes.order/
: Contains order-specific routes.
user/
: Contains user-specific routes.product/
: Contains product-specific routes.order/
: Contains order-specific routes.services/
: Contains service classes for business logic and data manipulation.user/
: Contains user-specific services.product/
: Contains product-specific services.order/
: Contains order-specific services.
user/
: Contains user-specific services.product/
: Contains product-specific services.order/
: Contains order-specific services.config/
: Contains configuration files.servers/
: Contains server files for starting the application.
user/
: Contains user-specific controllers.product/
: Contains product-specific controllers.order/
: Contains order-specific controllers.
user/
: Contains user-specific routes.product/
: Contains product-specific routes.order/
: Contains order-specific routes.
user/
: Contains user-specific services.product/
: Contains product-specific services.order/
: Contains order-specific services.
Authors
- Your Name
License
This project is licensed under the MIT License.
Để giải quyết yêu cầu của bạn, chúng ta có thể xây dựng một thư viện (module) cho các chức năng CRUD mà bạn có thể tái sử dụng và mở rộng một cách dễ dàng.
Đây là một phương pháp hiệu quả để giảm thiểu việc lặp lại mã và tăng tính linh hoạt của ứng dụng.
Thiết kế một thư viện CRUD
Chúng ta sẽ tạo ra một thư viện có thể xử lý các chức năng CRUD chung cho các mô hình khác nhau (ví dụ: User, Product, Order).
Thư viện này sẽ gồm các thành phần sau:
1. AbstractController.js
Đây là lớp controller trừu tượng mà các controller cụ thể sẽ kế thừa.
Nó định nghĩa các phương thức cơ bản như create, getAll, getById, update, delete.
// controllers/AbstractController.js class AbstractController { constructor(service) { this.service = service; } async create(req, res) { try { const data = await this.service.create(req.body); res.status(201).json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getAll(req, res) { try { const data = await this.service.getAll(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } } async getById(req, res) { try { const data = await this.service.getById(req.params.id); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async update(req, res) { try { const data = await this.service.update(req.params.id, req.body); if (data) { res.json(data); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async delete(req, res) { try { const success = await this.service.delete(req.params.id); if (success) { res.status(204).end(); } else { res.status(404).json({ error: 'Not Found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } } module.exports = AbstractController;
2. AbstractService.js
Lớp service trừu tượng định nghĩa các phương thức CRUD cơ bản.
// services/AbstractService.js class AbstractService { constructor(model) { this.model = model; } async create(data) { return await this.model.create(data); } async getAll() { return await this.model.find(); } async getById(id) { return await this.model.findById(id); } async update(id, data) { return await this.model.findByIdAndUpdate(id, data, { new: true }); } async delete(id) { const result = await this.model.findByIdAndDelete(id); return result !== null; } } module.exports = AbstractService;
3. Các Controllers Cụ thể (User, Product, Order)
Các controller cụ thể kế thừa từ AbstractController và sử dụng AbstractService để thực hiện các thao tác CRUD cho từng mô hình cụ thể.
// controllers/UserController.js const AbstractController = require('./AbstractController'); class UserController extends AbstractController { constructor(service) { super(service); } } module.exports = UserController;
// controllers/ProductController.js const AbstractController = require('./AbstractController'); class ProductController extends AbstractController { constructor(service) { super(service); } } module.exports = ProductController;
// controllers/OrderController.js const AbstractController = require('./AbstractController'); class OrderController extends AbstractController { constructor(service) { super(service); } } module.exports = OrderController;
4. Các Services Cụ thể (User, Product, Order)
Các service cụ thể kế thừa từ AbstractService và thực hiện các thao tác CRUD cho từng mô hình cụ thể.
// services/UserService.js const AbstractService = require('./AbstractService'); const User = require('../models/User'); class UserService extends AbstractService { constructor() { super(User); } } module.exports = UserService;
// services/ProductService.js const AbstractService = require('./AbstractService'); const Product = require('../models/Product'); class ProductService extends AbstractService { constructor() { super(Product); } } module.exports = ProductService;
// services/OrderService.js const AbstractService = require('./AbstractService'); const Order = require('../models/Order'); class OrderService extends AbstractService { constructor() { super(Order); } } module.exports = OrderService;
5. Routes
Bạn có thể sử dụng một hàm helper để tạo các routes tự động dựa trên controller cụ thể.
// routes/DynamicRoutes.js const express = require('express'); function createRoutes(controller) { const router = express.Router(); router.post('/', (req, res) => controller.create(req, res)); router.get('/', (req, res) => controller.getAll(req, res)); router.get('/:id', (req, res) => controller.getById(req, res)); router.put('/:id', (req, res) => controller.update(req, res)); router.delete('/:id', (req, res) => controller.delete(req, res)); return router; } module.exports = createRoutes;
6. Thêm vào Servers
Các server sẽ chỉ cần sử dụng các controllers và routes tương ứng.
// servers/userServer.js const express = require('express'); const config = require('../config/serverConfig'); const mongoose = require('mongoose'); const UserController = require('../controllers/UserController'); const UserService = require('../services/UserService'); const createUserRoutes = require('../routes/DynamicRoutes'); const app = express(); app.use(express.json()); const userService = new UserService(); const userController = new UserController(userService); const userRoutes = createUserRoutes(userController); app.use('/api/users', userRoutes); mongoose.connect(config.dbConnectionString, { useNewUrlParser: true, useUnifiedTopology: true }); app.listen(config.port, () => { console.log(`User server is running on port ${config.port}`); });
Tương tự, bạn sẽ cấu hình các servers cho Product và Order bằng cách thay thế UserController, UserService và các routes tương ứng.
7. Cấu hình package.json
Cập nhật scripts để khởi động các servers.
{ "scripts": { "start:user": "node servers/userServer.js", "start:product": "node servers/productServer.js", "start:order": "node servers/orderServer.js" } }
Lợi ích
- Tái sử dụng mã: Bạn có thể sử dụng lại các controllers và services cho nhiều mô hình khác nhau một cách dễ dàng.
- Mở rộng dễ dàng: Để mở rộng chức năng, bạn chỉ cần tạo một controller và service mới và kết nối chúng với routes tương ứng.
- Tính linh hoạt cao: Việc sử dụng các lớp trừu tượng và hàm helper giúp cho việc thêm mới và thay đổi các thành phần trở nên đơn giản và nhanh chóng.
Với cách tiếp cận này, bạn có thể tổ chức mã nguồn một cách rõ ràng và giảm thiểu sự phụ thuộc giữa các phần của ứng dụng, giúp dễ dàng bảo trì và mở rộng trong tương lai.
Dưới đây là cấu trúc thư mục và các tệp tin tương ứng để triển khai một ứng dụng có tính tái sử dụng cao, sử dụng các module và thư viện để quản lý các chức năng CRUD cho các đối tượng User, Product, và Order.
Cấu trúc Thư mục
project-root/ │ ├── config/ │ └── serverConfig.js │ ├── controllers/ │ ├── AbstractController.js │ ├── OrderController.js │ ├── ProductController.js │ └── UserController.js │ ├── models/ │ ├── Order.js │ ├── Product.js │ └── User.js │ ├── routes/ │ ├── DynamicRoutes.js │ ├── order/ │ │ └── orderRoutes.js │ ├── product/ │ │ └── productRoutes.js │ └── user/ │ └── userRoutes.js │ ├── services/ │ ├── AbstractService.js │ ├── OrderService.js │ ├── ProductService.js │ └── UserService.js │ └── servers/ ├── orderServer.js ├── productServer.js └── userServer.js
Giải thích cấu trúc thư mục và các tệp tin
config/
serverConfig.js
: Chứa cấu hình của server, ví dụ như chuỗi kết nối đến cơ sở dữ liệu.
serverConfig.js
: Chứa cấu hình của server, ví dụ như chuỗi kết nối đến cơ sở dữ liệu.controllers/
AbstractController.js
: Lớp controller trừu tượng định nghĩa các phương thức CRUD cơ bản nhưcreate
,getAll
,getById
,update
,delete
.UserController.js
,ProductController.js
,OrderController.js
: Các controller cụ thể cho các đối tượng User, Product và Order. Kế thừa từAbstractController
và xử lý logic cụ thể cho từng đối tượng.
AbstractController.js
: Lớp controller trừu tượng định nghĩa các phương thức CRUD cơ bản nhưcreate
,getAll
,getById
,update
,delete
.UserController.js
,ProductController.js
,OrderController.js
: Các controller cụ thể cho các đối tượng User, Product và Order. Kế thừa từAbstractController
và xử lý logic cụ thể cho từng đối tượng.models/
User.js
,Product.js
,Order.js
: Định nghĩa các schema và model cho các đối tượng User, Product và Order sử dụng Mongoose.
User.js
,Product.js
,Order.js
: Định nghĩa các schema và model cho các đối tượng User, Product và Order sử dụng Mongoose.routes/
DynamicRoutes.js
: Hàm helper để tạo các routes tự động dựa trên controller cụ thể.user/
,product/
,order/
: Các thư mục con chứa các tệp tin route cho từng đối tượng tương ứng (userRoutes.js
,productRoutes.js
,orderRoutes.js
).
DynamicRoutes.js
: Hàm helper để tạo các routes tự động dựa trên controller cụ thể.user/
,product/
,order/
: Các thư mục con chứa các tệp tin route cho từng đối tượng tương ứng (userRoutes.js
,productRoutes.js
,orderRoutes.js
).services/
AbstractService.js
: Lớp service trừu tượng định nghĩa các phương thức CRUD cơ bản nhưcreate
,getAll
,getById
,update
,delete
.UserService.js
,ProductService.js
,OrderService.js
: Các service cụ thể cho các đối tượng User, Product và Order. Kế thừa từAbstractService
và thực hiện các thao tác với cơ sở dữ liệu.
AbstractService.js
: Lớp service trừu tượng định nghĩa các phương thức CRUD cơ bản nhưcreate
,getAll
,getById
,update
,delete
.UserService.js
,ProductService.js
,OrderService.js
: Các service cụ thể cho các đối tượng User, Product và Order. Kế thừa từAbstractService
và thực hiện các thao tác với cơ sở dữ liệu.servers/
userServer.js
,productServer.js
,orderServer.js
: Các tệp server khởi động cho User, Product và Order servers. Sử dụng Express để định nghĩa các routes và kết nối tới cơ sở dữ liệu.
userServer.js
,productServer.js
,orderServer.js
: Các tệp server khởi động cho User, Product và Order servers. Sử dụng Express để định nghĩa các routes và kết nối tới cơ sở dữ liệu.
config/
serverConfig.js
: Chứa cấu hình của server, ví dụ như chuỗi kết nối đến cơ sở dữ liệu.
controllers/
AbstractController.js
: Lớp controller trừu tượng định nghĩa các phương thức CRUD cơ bản nhưcreate
,getAll
,getById
,update
,delete
.UserController.js
,ProductController.js
,OrderController.js
: Các controller cụ thể cho các đối tượng User, Product và Order. Kế thừa từAbstractController
và xử lý logic cụ thể cho từng đối tượng.
models/
User.js
,Product.js
,Order.js
: Định nghĩa các schema và model cho các đối tượng User, Product và Order sử dụng Mongoose.
routes/
DynamicRoutes.js
: Hàm helper để tạo các routes tự động dựa trên controller cụ thể.user/
,product/
,order/
: Các thư mục con chứa các tệp tin route cho từng đối tượng tương ứng (userRoutes.js
,productRoutes.js
,orderRoutes.js
).
services/
AbstractService.js
: Lớp service trừu tượng định nghĩa các phương thức CRUD cơ bản nhưcreate
,getAll
,getById
,update
,delete
.UserService.js
,ProductService.js
,OrderService.js
: Các service cụ thể cho các đối tượng User, Product và Order. Kế thừa từAbstractService
và thực hiện các thao tác với cơ sở dữ liệu.
servers/
userServer.js
,productServer.js
,orderServer.js
: Các tệp server khởi động cho User, Product và Order servers. Sử dụng Express để định nghĩa các routes và kết nối tới cơ sở dữ liệu.
Tóm tắt
Cấu trúc thư mục và các tệp tin được tổ chức theo mô hình MVC (Model-View-Controller), với các thành phần cụ thể như controllers, services, models, và routes.
Mỗi đối tượng (User, Product, Order) có các controllers và services riêng biệt để quản lý các chức năng CRUD, và các routes cũng được phân tách để dễ dàng quản lý và mở rộng.
Bằng cách này, ứng dụng của bạn có tính tái sử dụng cao và dễ dàng mở rộng khi cần thiết.
Nếu bạn muốn mở rộng chức năng của đối tượng User để bao gồm nhiều hơn chỉ các hoạt động CRUD và có các chức năng liên quan tương tác khác, bạn có thể thực hiện như sau:
1. Mở rộng Service và Controller của User
a. Thêm các phương thức vào UserService
Ví dụ, bạn có thể thêm các phương thức như getUserByUsername, updatePassword, addToFavorites, getFavorites, addRole, removeRole, getRoles, vv.
vào UserService.
Đây là các hành động tùy chỉnh hoặc mở rộng mà bạn muốn thực hiện với đối tượng User.
// services/UserService.js const AbstractService = require('./AbstractService'); const User = require('../models/User'); class UserService extends AbstractService { constructor() { super(User); } async getUserByUsername(username) { return await this.model.findOne({ username }); } async updatePassword(userId, newPassword) { return await this.model.findByIdAndUpdate(userId, { password: newPassword }, { new: true }); } async addToFavorites(userId, productId) { const user = await this.model.findById(userId); if (!user) throw new Error('User not found'); if (!user.favorites.includes(productId)) { user.favorites.push(productId); await user.save(); } return user; } async getFavorites(userId) { const user = await this.model.findById(userId).populate('favorites'); if (!user) throw new Error('User not found'); return user.favorites; } async addRole(userId, role) { const user = await this.model.findById(userId); if (!user) throw new Error('User not found'); if (!user.roles.includes(role)) { user.roles.push(role); await user.save(); } return user; } async removeRole(userId, role) { const user = await this.model.findById(userId); if (!user) throw new Error('User not found'); user.roles = user.roles.filter(r => r !== role); await user.save(); return user; } async getRoles(userId) { const user = await this.model.findById(userId); if (!user) throw new Error('User not found'); return user.roles; } } module.exports = UserService;
b. Mở rộng UserController
Cập nhật UserController để bao gồm các phương thức điều khiển mới để xử lý các yêu cầu liên quan tới User.
// controllers/UserController.js const AbstractController = require('./AbstractController'); const UserService = require('../services/UserService'); class UserController extends AbstractController { constructor() { super(new UserService()); } async getUserByUsername(req, res) { try { const username = req.params.username; const user = await this.service.getUserByUsername(username); if (user) { res.json(user); } else { res.status(404).json({ error: 'User not found' }); } } catch (error) { res.status(500).json({ error: error.message }); } } async updatePassword(req, res) { try { const userId = req.params.id; const newPassword = req.body.password; const user = await this.service.updatePassword(userId, newPassword); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } } async addToFavorites(req, res) { try { const userId = req.params.id; const productId = req.body.productId; const user = await this.service.addToFavorites(userId, productId); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } } async getFavorites(req, res) { try { const userId = req.params.id; const favorites = await this.service.getFavorites(userId); res.json(favorites); } catch (error) { res.status(500).json({ error: error.message }); } } async addRole(req, res) { try { const userId = req.params.id; const role = req.body.role; const user = await this.service.addRole(userId, role); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } } async removeRole(req, res) { try { const userId = req.params.id; const role = req.body.role; const user = await this.service.removeRole(userId, role); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } } async getRoles(req, res) { try { const userId = req.params.id; const roles = await this.service.getRoles(userId); res.json(roles); } catch (error) { res.status(500).json({ error: error.message }); } } } module.exports = UserController;
2. Cập nhật Routes
Cập nhật userRoutes.js để bao gồm các routes mới cho các phương thức tương tác liên quan tới User.
// routes/user/userRoutes.js const express = require('express'); const UserController = require('../../controllers/UserController'); const createUserRoutes = require('../DynamicRoutes'); const controller = new UserController(); const userRoutes = createUserRoutes(controller); userRoutes.get('/username/:username', (req, res) => controller.getUserByUsername(req, res)); userRoutes.put('/:id/password', (req, res) => controller.updatePassword(req, res)); userRoutes.post('/:id/favorites', (req, res) => controller.addToFavorites(req, res)); userRoutes.get('/:id/favorites', (req, res) => controller.getFavorites(req, res)); userRoutes.post('/:id/roles', (req, res) => controller.addRole(req, res)); userRoutes.delete('/:id/roles', (req, res) => controller.removeRole(req, res)); userRoutes.get('/:id/roles', (req, res) => controller.getRoles(req, res)); module.exports = userRoutes;
3. Thêm tính năng mới
Nếu bạn muốn thêm tính năng khác, bạn có thể lặp lại quy trình tương tự:
- Thêm phương thức vào
UserService
. - Thêm phương thức điều khiển tương ứng vào
UserController
. - Cập nhật
userRoutes.js
để đăng ký route cho phương thức mới.
Điều này giúp bạn duy trì sự tổ chức rõ ràng và tái sử dụng mã một cách hiệu quả trong ứng dụng của mình.
Để giảm sự lặp lại của mã try-catch và tạo một base class để xử lý các lỗi chung trong các controller, bạn có thể áp dụng một phương pháp gọi là middleware trong Express.
Middleware cho phép bạn định nghĩa một hàm xử lý lỗi chung và tái sử dụng nó trên các route khác nhau một cách dễ dàng.
Dưới đây là cách bạn có thể cải thiện mã của UserController và các controllers khác bằng cách sử dụng middleware để xử lý lỗi:
1. Middleware xử lý lỗi chung
Bạn có thể tạo một middleware xử lý lỗi chung và sử dụng nó để bọc các phương thức điều khiển của controller.
Đây là ví dụ cụ thể:
// middleware/errorHandler.js function errorHandler(err, req, res, next) { console.error(err.stack); res.status(500).json({ error: err.message }); } module.exports = errorHandler;
2. Cập nhật các Controllers để sử dụng Middleware
Các controllers sẽ bao gồm các phương thức và sử dụng middleware để xử lý lỗi chung.
Ví dụ:
// controllers/UserController.js const AbstractController = require('./AbstractController'); const UserService = require('../services/UserService'); class UserController extends AbstractController { constructor() { super(new UserService()); } async getUserByUsername(req, res) { const username = req.params.username; const user = await this.service.getUserByUsername(username); if (!user) { res.status(404).json({ error: 'User not found' }); return; } res.json(user); } async updatePassword(req, res) { const userId = req.params.id; const newPassword = req.body.password; const user = await this.service.updatePassword(userId, newPassword); res.json(user); } async addToFavorites(req, res) { const userId = req.params.id; const productId = req.body.productId; const user = await this.service.addToFavorites(userId, productId); res.json(user); } async getFavorites(req, res) { const userId = req.params.id; const favorites = await this.service.getFavorites(userId); res.json(favorites); } async addRole(req, res) { const userId = req.params.id; const role = req.body.role; const user = await this.service.addRole(userId, role); res.json(user); } async removeRole(req, res) { const userId = req.params.id; const role = req.body.role; const user = await this.service.removeRole(userId, role); res.json(user); } async getRoles(req, res) { const userId = req.params.id; const roles = await this.service.getRoles(userId); res.json(roles); } } module.exports = UserController;
3. Kết nối Middleware vào ứng dụng Express
Cuối cùng, bạn cần kết nối middleware xử lý lỗi chung vào ứng dụng Express của bạn:
// app.js hoặc server.js const express = require('express'); const errorHandler = require('./middleware/errorHandler'); const userRoutes = require('./routes/user/userRoutes'); // Import các routes và middleware khác cần thiết const app = express(); app.use(express.json()); // Sử dụng middleware xử lý lỗi chung app.use(errorHandler); // Đăng ký các routes app.use('/api/users', userRoutes); // Đăng ký các routes khác tương tự // Khởi động server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Lợi ích của phương pháp này:
- Tái sử dụng mã: Bạn không cần phải lặp lại các lệnh try-catch trong từng phương thức điều khiển, mà chỉ cần xử lý lỗi một cách chung chung trong middleware.
- Giảm độ phức tạp của controller: Các controller trở nên gọn gàng hơn với mã xử lý logic chính, không phải lo lắng về việc xử lý lỗi.
- Quản lý lỗi hiệu quả: Middleware xử lý lỗi giúp bạn log và xử lý các lỗi một cách thống nhất trong toàn bộ ứng dụng.
Qua đó, bạn có thể giảm thiểu sự lặp lại mã và cải thiện quản lý lỗi trong ứng dụng của mình.
Để giảm sự lặp lại trong việc kiểm tra sự tồn tại của người dùng (user existence check), bạn có thể sử dụng một middleware hoặc một hàm helper để thực hiện điều này một cách hiệu quả và tái sử dụng được trong toàn bộ ứng dụng.
Dưới đây là một phương pháp để làm điều này:
Sử dụng Middleware để Kiểm tra Sự Tồn Tại của User
Bạn có thể viết một middleware đơn giản để kiểm tra sự tồn tại của user dựa trên userId được truyền từ các request.
1. Middleware kiểm tra sự tồn tại của User
// middleware/checkUserExists.js const User = require('../models/User'); async function checkUserExists(req, res, next) { const userId = req.params.id; // Lấy userId từ request params try { const user = await User.findById(userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } req.user = user; // Lưu user vào req để có thể sử dụng trong các xử lý tiếp theo next(); } catch (err) { console.error(err); return res.status(500).json({ error: 'Server error' }); } } module.exports = checkUserExists;
2. Sử dụng Middleware trong các Controllers
Sau khi có middleware checkUserExists, bạn có thể áp dụng nó trong các routes hoặc controllers mà bạn cần kiểm tra sự tồn tại của user.
Ví dụ trong UserController:
// controllers/UserController.js const AbstractController = require('./AbstractController'); const UserService = require('../services/UserService'); const checkUserExists = require('../middleware/checkUserExists'); class UserController extends AbstractController { constructor() { super(new UserService()); } async getUser(req, res) { res.json(req.user); // Middleware đã lưu user vào req.user nếu tồn tại } async updateUser(req, res) { const userId = req.params.id; const newData = req.body; try { // Cập nhật thông tin user const updatedUser = await this.service.update(userId, newData); res.json(updatedUser); } catch (err) { res.status(500).json({ error: err.message }); } } async deleteUser(req, res) { const userId = req.params.id; try { // Xóa user await this.service.delete(userId); res.json({ message: 'User deleted successfully' }); } catch (err) { res.status(500).json({ error: err.message }); } } } module.exports = UserController;
3. Đăng ký Middleware trong Routes
Cuối cùng, bạn cần đăng ký middleware checkUserExists vào các routes mà bạn muốn kiểm tra sự tồn tại của user.
// routes/user/userRoutes.js const express = require('express'); const UserController = require('../../controllers/UserController'); const checkUserExists = require('../../middleware/checkUserExists'); const controller = new UserController(); const userRoutes = express.Router(); userRoutes.get('/:id', checkUserExists, (req, res) => controller.getUser(req, res)); userRoutes.put('/:id', checkUserExists, (req, res) => controller.updateUser(req, res)); userRoutes.delete('/:id', checkUserExists, (req, res) => controller.deleteUser(req, res)); module.exports = userRoutes;
Lợi ích của phương pháp này:
- Tái sử dụng mã: Middleware
checkUserExists
giúp bạn kiểm tra sự tồn tại của user một cách hiệu quả và tái sử dụng được trong nhiều routes khác nhau. - Giảm sự lặp lại: Bạn không cần phải lặp lại mã kiểm tra sự tồn tại của user trong từng phương thức điều khiển (controller).
- Quản lý mã một cách rõ ràng: Middleware giúp mã của bạn trở nên gọn gàng hơn và dễ quản lý hơn.
Qua đó, việc sử dụng middleware để kiểm tra sự tồn tại của user là một cách hiệu quả để giảm sự lặp lại của mã trong ứng dụng của bạn.
Dưới đây là tóm tắt các phương pháp đã xử lý để tạo ra một ứng dụng Node.js với chức năng CRUD và khả năng mở rộng, bao gồm cả việc xử lý lỗi và kiểm tra sự tồn tại của user một cách hiệu quả:
1. Tạo cấu trúc thư mục rõ ràng và phân chia module hợp lý
Chúng ta đã thiết kế một cấu trúc thư mục rõ ràng để quản lý các thành phần khác nhau của ứng dụng theo mô hình MVC (Model-View-Controller):
project-root/ │ ├── config/ │ └── serverConfig.js │ ├── controllers/ │ ├── AbstractController.js │ ├── OrderController.js │ ├── ProductController.js │ └── UserController.js │ ├── middleware/ │ ├── errorHandler.js │ └── checkUserExists.js │ ├── models/ │ ├── Order.js │ ├── Product.js │ └── User.js │ ├── routes/ │ ├── DynamicRoutes.js │ ├── order/ │ │ └── orderRoutes.js │ ├── product/ │ │ └── productRoutes.js │ └── user/ │ └── userRoutes.js │ ├── services/ │ ├── AbstractService.js │ ├── OrderService.js │ ├── ProductService.js │ └── UserService.js │ └── servers/ ├── orderServer.js ├── productServer.js └── userServer.js
2. Sử dụng lớp trừu tượng để giảm sự lặp lại của mã CRUD
Tạo các lớp trừu tượng (AbstractService và AbstractController) để định nghĩa các phương thức CRUD cơ bản, sau đó các lớp cụ thể như UserService và UserController sẽ kế thừa và mở rộng khi cần thiết:
// services/AbstractService.js class AbstractService { constructor(model) { this.model = model; } async create(data) { return await this.model.create(data); } async getAll() { return await this.model.find(); } async getById(id) { return await this.model.findById(id); } async update(id, data) { return await this.model.findByIdAndUpdate(id, data, { new: true }); } async delete(id) { return await this.model.findByIdAndDelete(id); } } // controllers/AbstractController.js class AbstractController { constructor(service) { this.service = service; } async create(req, res) { try { const data = req.body; const result = await this.service.create(data); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } } async getAll(req, res) { try { const result = await this.service.getAll(); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } } async getById(req, res) { try { const id = req.params.id; const result = await this.service.getById(id); if (!result) { return res.status(404).json({ error: 'Not found' }); } res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } } async update(req, res) { try { const id = req.params.id; const data = req.body; const result = await this.service.update(id, data); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } } async delete(req, res) { try { const id = req.params.id; await this.service.delete(id); res.json({ message: 'Deleted successfully' }); } catch (err) { res.status(500).json({ error: err.message }); } } }
3. Middleware xử lý lỗi chung
Tạo middleware errorHandler để xử lý lỗi một cách thống nhất và tái sử dụng trong toàn bộ ứng dụng:
// middleware/errorHandler.js function errorHandler(err, req, res, next) { console.error(err.stack); res.status(500).json({ error: err.message }); } module.exports = errorHandler;
4. Middleware kiểm tra sự tồn tại của User
Tạo middleware checkUserExists để kiểm tra sự tồn tại của user và tránh lặp lại mã kiểm tra này trong các controller:
// middleware/checkUserExists.js const User = require('../models/User'); async function checkUserExists(req, res, next) { const userId = req.params.id; try { const user = await User.findById(userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } req.user = user; next(); } catch (err) { console.error(err); return res.status(500).json({ error: 'Server error' }); } } module.exports = checkUserExists;
5. Đăng ký Middleware trong Routes
Sử dụng middleware trong các routes để kiểm tra sự tồn tại của user và xử lý lỗi chung:
// routes/user/userRoutes.js const express = require('express'); const UserController = require('../../controllers/UserController'); const checkUserExists = require('../../middleware/checkUserExists'); const errorHandler = require('../../middleware/errorHandler'); const controller = new UserController(); const userRoutes = express.Router(); userRoutes.get('/:id', checkUserExists, (req, res) => controller.getUser(req, res)); userRoutes.put('/:id', checkUserExists, (req, res) => controller.updateUser(req, res)); userRoutes.delete('/:id', checkUserExists, (req, res) => controller.deleteUser(req, res)); // Sử dụng middleware xử lý lỗi chung userRoutes.use(errorHandler); module.exports = userRoutes;
6. Kết nối Middleware vào ứng dụng Express
Kết nối middleware vào ứng dụng Express để đảm bảo các lỗi được xử lý thống nhất:
// app.js hoặc server.js const express = require('express'); const errorHandler = require('./middleware/errorHandler'); const userRoutes = require('./routes/user/userRoutes'); // Import các routes và middleware khác cần thiết const app = express(); app.use(express.json()); // Đăng ký các routes app.use('/api/users', userRoutes); // Đăng ký các routes khác tương tự // Sử dụng middleware xử lý lỗi chung app.use(errorHandler); // Khởi động server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Tóm tắt các phương pháp xử lý:
- Cấu trúc thư mục rõ ràng: Phân chia các thành phần của ứng dụng theo mô hình MVC.
- Lớp trừu tượng: Tạo
AbstractService
vàAbstractController
để định nghĩa các phương thức CRUD cơ bản và giảm sự lặp lại của mã. - Middleware xử lý lỗi: Tạo middleware
errorHandler
để xử lý lỗi chung trong toàn bộ ứng dụng. - Middleware kiểm tra sự tồn tại của User: Tạo middleware
checkUserExists
để kiểm tra sự tồn tại của user và tránh lặp lại mã kiểm tra trong các controller. - Đăng ký Middleware trong Routes: Sử dụng middleware trong các routes để đảm bảo các lỗi và kiểm tra được xử lý một cách hiệu quả.
- Kết nối Middleware vào ứng dụng Express: Đảm bảo các middleware được kết nối vào ứng dụng Express để xử lý lỗi và kiểm tra một cách thống nhất.
Qua đó, ứng dụng của bạn trở nên gọn gàng hơn, dễ bảo trì hơn và có khả năng mở rộng tốt hơn.
#Mtips5s #Contact
Fanpage: https://www.facebook.com/mtipscoder
Group trao đổi, chia sẻ: https://www.facebook.com/groups/mtipscoder
Website: https://mtips5s.com
Youtube: https://mtips5s.com
Twitter(X): @takagiks99
Instagram: @khuongkara
Threads: @khuongkara
Google Maps: @khuongkara
#Base Code #Souce Code
Bộ công cụ My Self: @tools.mtips5s.com
Github: @github
Npm: @npm
Docker: @docker
Chúc các bạn thành công!
Written by admin
Comments
This post currently has no responses.