Best practices for ExpressJS (Part I)
Bài đăng này đã không được cập nhật trong 6 năm
Tổng quan:
Express không có sẵn và bắt buộc cấu trúc thư mục chặt chẽ. Thay vào đó chúng ta có thể build ứng dụng theo cách mà chúng ta muốn. Điều này thực được rất tuyệt vời đặc biệt là đối với ứng dụng nhỏ. Nó dễ dàng để bắt đầu, học tập và trải nghiệm.
Tuy nhiên, khi ứng dụng lớn và phức tạp hơn, nhiều thứ có thể gây sự lộn xộn và bối rối. Một ứng dụng tốt phải đáp ứng được các điểm chính sau:
- Cấu trúc rõ ràng, dễ hiểu, dễ phát triển (clean code)
- Dễ dàng đáp ứng sự thay đổi và mở rộng (scalable)
- Maintain đơn giản, nhanh chóng và thuận tiện (maintainable)
Như vậy cấu trúc project chính là linh hồn của dự án, nó quyết định chính đến chất lương, tốc độ của dự án. Như chúng ta đã biết, thời gian fix bug, thay đổi và maintain chiếm 70% khối lượng công việc. Dự án thành công ngoài việc làm đúng bussiness logic thì phải giảm thiếu được 70% về mức min nhất. Tức luôn tính đến khả năng mở rộng, bảo trì. Điều này cực kì quan trọng.
Phần lớn lập trình viên mới, họ nhanh chóng bắt tay vào code chức năng chính luôn mà ít quan tâm tới việc phân tích, định hướng cấu trúc dự án. Với suy nghĩ càng nhanh càng tốt, chức năng đầu tiên hoàn thành nhanh càng tốt. Nhưng dục tốc thì bất đạt
. Vì rất nhanh chóng tầm 1, 2 tuần sau. tốc độ phát triển bị chậm lại và đặc biệt khi có sự thay đổi từ phía khách hàng thì code trở nên lộn xộn, messy, và phức tạp. Kéo theo thời gian phát triển maintain bị đội lên, càng ngày code càng rối rắm như một vòng xoáy. Bản thân mình cũng đã từng trải nghiệm gỡ rối những dự án như thế này.
Dự án cần đáp ứng schedule về mặt thời gian, tức cần nhanh
. Nhưng từ nhanh
ở đây là cả quá trình phát triển và maintain dự án chứ không phải nhanh ở tính năng.
Cấu trúc thư mục
ExpressJs không tạo ra một cấu trúc chuẩn nào để tạo nên sự linh hoạt đơn giản. Nên chúng ta sẽ tự cấu trúc theo cách của mình. Design partern MVC kết hợp với cấu trúc thư mục theo chức năng là một sự lựa chọn hòan hảo. Nó đơn giản, dễ hiểu và đáp ứng được 3 nguyên tắc trên. Cấu trúc như sau:
project/
server
controllers/
comments.js
index.js
users.js
helpers/
dates.js
middlewares/
auth.js
users.js
models/
comment.js
user.js
migrations/
routes/
index.js
public/
js/
css/
img/
views/
comments/
comment.jade
users/
index.jade
tests/
app.js
.gitignore
package.json
controllers/
: thư mục chứa controllershelpers/
: thư mục chứa các chức năng, thành phần chung, chia sẻ cho nhiều phần khác nhau của dự án.middlewares/
: thư mục chứa các middlewaresmigrations/
: thư mục chứa các file migrationsmodels/
: thư mục chứa xử lý với cơ sở dữ liệuroutes/
: thư mục chứa routepublic/
: thư mục chứa các file tĩnh như image, css, javascriptviews/
: thư mục chứa các template, viewtests/
: thư mực chứa code viết Unit testapp.js
: file chính, khởi tạo dự ánpackage.json
: file config và chứa các module node đã cài đặt
Ví dụ
Chúng ta sẽ tạo ra một ứng dụng demo có sử dụng migrations
.
Yêu cầu
Trước khi bắt đầu chúng ta cần cài đặt và có hiểu biết cớ bản về các nền tảng, package và tool sau:
- NodeJS
- Express
- PostgreSQL
- Sequelize - ORM database
- Postman - A Chrome app để test API
Khởi tạo project
Chay lệnh sau:
$ mkdir -p nodejs-demo/{bin,server}
$ cd nodejs-demo
Khởi tạo npm
để tạo file package.json
$ npm init -y
Thư mục dự án bây giờ trông như thé này:
nodejs-demo
├── bin
├── package.json
└── server
Cài đặt expessjs và một số dependencies
$ npm install --save express body-parser morgan
Cờ --save
sẽ lưu package vào phần dependencies của file package.json
Tạo 1 file ở thư mục root gọi là app.js
.
$ touch app.js
Trong file này, chúng ta sẽ tạo ứng dụng Express có nội dung như sau:
const express = require('express');
const logger = require('morgan');
const bodyParser = require('body-parser');
// Set up the express app
const app = express();
// Log requests to the console.
app.use(logger('dev'));
// Parse incoming requests data (https://github.com/expressjs/body-parser)
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// Setup a default catch-all route that sends back a welcome message in JSON format.
app.get('*', (req, res) => res.status(200).send({
message: 'Welcome to the beginning of nothingness.',
}));
module.exports = app;
Trong thư mục bin
, tạo 1 file gọi là www
$ touch bin/www
Tạo nội dung file www
như sau:
// This will be our application entry. We'll setup our server here.
const http = require('http');
const app = require('../app'); // The express app we just created
const port = parseInt(process.env.PORT, 10) || 8000;
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
Để có thể restart server mỗi khi code có sự thay đổi thì chúng ta dùng package nodemon
.
$ npm install --save nodemon
Sau đó, mở file package.json
, tạo một command để run server. Command này được trong trong phần scripts
của file package.json
. Nội dung như sau:
...
"scripts": {
"start:dev": "nodemon ./bin/www",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
Bây giờ chạy ứng dụng bằng command
$ npm run start:dev
Truy cập http://localhost:8000. Chúng ta thấy message như thế này {"message":"Welcome to the beginning of nothingness."}
.
Tới đây, thư mục của chúng ta sẽ như thé này:
nodejs-demo
├── app.js
├── bin
│ └── www
├── package.json
└── server
Cài đặt Sequelize
Tiếp đến chúng ta sẽ require Sequelize
. Nó là một ORM
database. Nó còn cung cấp chức năng database migrations
giúp chúng ta quản lý database cực tốt. Để tìm hiểu chi tiết chúng ta truy cập vào trang http://docs.sequelizejs.com/manual/installation/getting-started.html
Chúng ta sẽ cái đặt package Sequelize CLI
.
$ npm install -g sequelize-cli
Tham số -g
ở đây nghĩa là cài đặt global
cho package này.
Tiếp đến chúng ta config Sequelize cho dự án. Đầu tiên tạo file .sequelizerc
trong thư mục root của dự án. Trong file này chúng ta sẽ chỉ rõ đường dẫn (path) được require bởi Sequelize:
const path = require('path');
module.exports = {
"config": path.resolve('./server/config', 'config.json'),
"models-path": path.resolve('./server/models'),
"seeders-path": path.resolve('./server/seeders'),
"migrations-path": path.resolve('./server/migrations')
};
File config.json
chứa cài đặt của toàn bộ ứng dụng như database authentication config, migrations folder
. Trong khi đó thư mục models
sẽ chứa tất cả model của ứng dụng.
Chúng ta cần cài đặt package Sequelize
như một dependencies
.
$ npm install --save sequelize pg pg-hstore
pg
sẽ chịu trách nhiệm tạo database connection trong khi đó pg-hstore
là một module để serializing
và deserializing
JSON dâta vào Postgres hstore format.
Bây giờ với các đường dẫn (path) đã định nghĩa. chúng ta sẽ chạy command để khởi tạo các thư mục đã config và code mẫu.
$ sequelize init
Cấu trúc dự án giờ trông như thé này:
nodejs-demo
├── app.js
├── bin
│ └── www
├── package.json
└── server
├── config
│ └── config.json
├── migrations
├── models
│ └── index.js
└── seeders
Chúng ta có thể thấy file server/models/index.js
đã được tạo tự động.
Nội dung như sau:
'use strict';
var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var basename = path.basename(module.filename);
var env = process.env.NODE_ENV || 'development';
var config = require(__dirname + '/../config/config.json')[env];
var db = {};
if (config.use_env_variable) {
var sequelize = new Sequelize(process.env[config.use_env_variable]);
} else {
var sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(function(file) {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(function(file) {
var model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function(modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
Trong file này, các module cần thiết để tương tác với database đã được sử dụng. Đọc config Node environment. Nếu không được chỉ định thì lấy mặc định (default) là development
. Sau đó là thiết lập một kết nối tới database. Tiếp đến là đọc thư mục models
, import tất cả các models, thêm vào đối tượng db
và áp dụng relationship
giữa các models. Tương đối dễ hiểu phải không nào?
Chỉnh sửa lại file server/models/index.js
. Code mặc định sinh ra file này là ES5 syntax
. Chúng ta sửa lại sử dụng ES6 syntax
. Nếu chưa biết ES6 các bạn có thể tim hiểu thêm trên mạng.
File server/models/index.js
như sau:
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(module.filename);
const env = process.env.NODE_ENV || 'development';
const config = require(`${__dirname}/../config/config.json`)[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable]);
} else {
sequelize = new Sequelize(
config.database, config.username, config.password, config
);
}
fs
.readdirSync(__dirname)
.filter(file =>
(file.indexOf('.') !== 0) &&
(file !== basename) &&
(file.slice(-3) === '.js'))
.forEach(file => {
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
Đâu tiên, chúng ta cần tạo một development database
.
Giả sử ta đã có một tài khoản postgresql
là sammy
và mật khẩu là 123456
.
Đăng nhập vào tài khoản này và tạo db todos-dev
.
$ sudo -su sammy psql
$ createdb todos-dev
createdb
command có thể được sử dụng sau khi cài đặt PostgreSQL.
Cập nhật lại file `config.json` như sau:
{
"development": {
"username": "sammy",
"password": 123456,
"database": "todos-dev",
"host": "127.0.0.1",
"port": 5432,
"dialect": "postgres"
},
"test": {
"username": "sammy",
"password": 123456,
"database": "todos-test",
"host": "127.0.0.1",
"port": 5432,
"dialect": "postgres"
}
}
Chúng ta nên tạo ra các thư mục cần thiết khác tùy ý trong thư mục server
.
$ cd server
$ mkdir controllers middlewares libs helpers routes
Bài viết khá dài nên mình sẽ dừng phần 1 tại đây.
Sang phần 2 mình sẽ hướng dẫn các bạn viết controller, model, migration cho feature todo tasks
.
Các bạn có thể download code tại đây
Tham khảo
All rights reserved