Sau 2 năm làm việc với Node.js qua các dự án Discord Bot, backend API, và automation tools, mình đã tích lũy được khá nhiều tips hữu ích. Bài này mình sẽ chia sẻ những trick thực tế giúp code Node.js hiệu quả hơn! 🚀
🎯 Tại sao Node.js?
Trước khi vào tips, mình muốn nói tại sao Node.js lại phổ biến:
- JavaScript everywhere - Một ngôn ngữ cho cả frontend và backend
- NPM ecosystem - Hàng triệu packages có sẵn
- Async I/O - Xử lý concurrent requests cực tốt
- Community - Cộng đồng lớn, nhiều tài liệu
💡 Tips Quan Trọng Nhất
1. Luôn dùng Environment Variables
Đừng bao giờ hardcode sensitive data! Dùng .env file:
// ❌ SAI - Đừng làm thế này!
const token = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTA";
const dbUrl = "mongodb://localhost:27017/mydb";
// ✅ ĐÚNG - Dùng environment variables
require('dotenv').config();
const token = process.env.BOT_TOKEN;
const dbUrl = process.env.MONGODB_URL;
// .env file:
// BOT_TOKEN=your_token_here
// MONGODB_URL=mongodb://localhost:27017/mydb
Pro tip: Thêm .env vào .gitignore! Mình từng
push token lên GitHub và bị leak 😅
2. Error Handling đúng cách
Lỗi phổ biến nhất: không handle errors → app crash!
// ❌ SAI - App sẽ crash khi có lỗi
async function fetchUserData(userId) {
const data = await fetch(`/api/users/${userId}`);
return data.json();
}
// ✅ ĐÚNG - Handle errors properly
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return { success: true, data };
} catch (error) {
console.error('Fetch error:', error.message);
return { success: false, error: error.message };
}
}
// Usage
const result = await fetchUserData('123');
if (result.success) {
console.log('User data:', result.data);
} else {
console.log('Error:', result.error);
}
3. Async/Await > Callbacks/Promises Hell
Callback hell là ác mộng! Dùng async/await cho clean code:
// ❌ Callback Hell
getUserData(userId, (err, user) => {
if (err) return console.error(err);
getPosts(user.id, (err, posts) => {
if (err) return console.error(err);
getComments(posts[0].id, (err, comments) => {
if (err) return console.error(err);
console.log(comments);
});
});
});
// ✅ Async/Await - Dễ đọc hơn nhiều!
async function getUserContent(userId) {
try {
const user = await getUserData(userId);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
return comments;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
4. Promise.all() cho Parallel Requests
Đừng await từng request một khi chúng độc lập!
// ❌ SAI - Chậm (6 giây nếu mỗi request 2s)
async function fetchAllData() {
const users = await fetchUsers(); // 2s
const posts = await fetchPosts(); // 2s
const comments = await fetchComments(); // 2s
return { users, posts, comments };
}
// ✅ ĐÚNG - Nhanh (2 giây vì chạy parallel)
async function fetchAllData() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return { users, posts, comments };
}
Real example: Trong Ma Đạo Bot, mình dùng Promise.all() để fetch data từ 5 APIs khác nhau. Giảm thời gian từ 10s xuống còn 2s! 🚀
5. Module Pattern đúng chuẩn
Organize code thành modules để dễ maintain:
// ❌ Tất cả trong 1 file (index.js) - Messy!
const express = require('express');
const app = express();
app.get('/users', (req, res) => { /* ... */ });
app.post('/users', (req, res) => { /* ... */ });
app.get('/posts', (req, res) => { /* ... */ });
// ... 500 dòng code ...
// ✅ Tách thành modules
// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getUsers);
router.post('/', userController.createUser);
module.exports = router;
// index.js
const userRoutes = require('./routes/userRoutes');
app.use('/users', userRoutes);
🔥 Advanced Tips
6. Middleware Pattern trong Express
Middleware giúp reuse logic authentication, validation, logging...
// middleware/auth.js
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next(); // Tiếp tục đến route handler
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// Usage
app.get('/profile', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
app.post('/posts', authMiddleware, (req, res) => {
// req.user đã available ở đây
});
7. Nodemon cho Development
Đừng restart server manually mỗi lần sửa code!
// Install
npm install --save-dev nodemon
// package.json
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}
// Run
npm run dev // Auto restart khi file thay đổi!
8. Logging với Winston
console.log() OK cho dev, nhưng production cần proper logging:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// Development thì log ra console
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// Usage
logger.info('User logged in', { userId: '123' });
logger.error('Database connection failed', { error: err.message });
9. Rate Limiting
Bảo vệ API khỏi spam/abuse:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 phút
max: 100, // Max 100 requests per windowMs
message: 'Too many requests, please try again later.'
});
app.use('/api/', limiter);
⚡ Performance Tips
10. Caching với Redis
Cache data thường xuyên truy cập để giảm load database:
const redis = require('redis');
const client = redis.createClient();
async function getUserById(userId) {
// Check cache trước
const cached = await client.get(`user:${userId}`);
if (cached) {
console.log('Cache hit!');
return JSON.parse(cached);
}
// Nếu không có trong cache, query database
const user = await db.users.findById(userId);
// Lưu vào cache (expire sau 1 giờ)
await client.setex(`user:${userId}`, 3600, JSON.stringify(user));
return user;
}
11. Database Connection Pooling
Đừng tạo connection mới mỗi request!
// ❌ SAI - Tạo connection mỗi request
app.get('/users', async (req, res) => {
const db = await MongoClient.connect(url);
const users = await db.collection('users').find().toArray();
await db.close();
res.json(users);
});
// ✅ ĐÚNG - Reuse connection pool
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGODB_URL, {
maxPoolSize: 10 // Connection pool size
});
app.get('/users', async (req, res) => {
const users = await User.find(); // Reuse connection
res.json(users);
});
🛡️ Security Best Practices
12. Helmet.js cho Express Security
const helmet = require('helmet');
app.use(helmet()); // Sets various HTTP headers for security
13. Input Validation
Luôn validate input từ users!
const { body, validationResult } = require('express-validator');
app.post('/users',
body('email').isEmail(),
body('password').isLength({ min: 8 }),
body('username').trim().isLength({ min: 3, max: 20 }),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process valid data
}
);
📦 Package.json Best Practices
14. Semantic Versioning
{
"dependencies": {
"express": "^4.18.0", // ^4.18.0 = >=4.18.0 <5.0.0
"mongoose": "~7.0.0", // ~7.0.0 = >=7.0.0 <7.1.0
"lodash": "4.17.21" // Exact version
}
}
15. Scripts tổ chức tốt
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"lint": "eslint .",
"format": "prettier --write ."
}
}
🎓 Bài Học Từ Thực Tế
Lesson 1: Memory Leaks
Ma Đạo Bot từng bị crash mỗi 2-3 ngày. Nguyên nhân: event listeners không được cleanup! 😅
// ❌ Memory leak
setInterval(() => {
client.on('message', handler); // Listener mới mỗi 1s!
}, 1000);
// ✅ Cleanup properly
const handler = (msg) => { /* ... */ };
client.on('message', handler);
// Khi cần remove
client.off('message', handler);
Lesson 2: Unhandled Promise Rejections
App crash không rõ lý do? Có thể do unhandled rejections:
// Global handler
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Log to monitoring service
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Graceful shutdown
process.exit(1);
});
🚀 Bonus: Useful NPM Packages
- dotenv - Environment variables
- nodemon - Auto-restart on file changes
- express - Web framework
- mongoose - MongoDB ODM
- axios - HTTP client
- joi - Data validation
- winston - Logging
- pm2 - Production process manager
- helmet - Security headers
- cors - CORS middleware
🎯 Kết Luận
Node.js rất mạnh mẽ nhưng cũng dễ "bắn vào chân mình" nếu không cẩn thận! Những tips này mình học được qua 2 năm làm việc và... vô số lần debug đến 3-4 giờ sáng 😂
Remember:
- ✅ Luôn handle errors
- ✅ Dùng environment variables
- ✅ Async/await cho clean code
- ✅ Promise.all() cho parallel operations
- ✅ Validate inputs
- ✅ Log properly
- ✅ Monitor & optimize
Happy coding! 🚀