Tìm hiểu MEANJS qua việc làm ứng dụng viết blog
Bài đăng này đã không được cập nhật trong 8 năm
Hôm nay chúng ta cùng tìm hiểu kỹ hơn về bộ MEAN thông qua việc viết 1 ứng dụng viết blog sử dụng bộ 4 công nghệ này (Mongo DB, Express JS, Angular JS, Node Js). Việc cài đặt các bạn có thể tham khảo ở đây .
Trong bài viết này tôi có sử dụng :
- Node JS
- Express JS
- Mongo DB
- Mongoose
- Jade
Cấu trúc folder của ứng dụng
express
libs
functions.js
node_modules
public
css
fonts
ico
img
js
upload
views
includes
layouts
404.jade
index.jade
package.json
server.js
File package.json
Tại đây ta nhập các thông tin về ứng dụng chúng ta làm
{
"name": "sample_blog",
"description": "Make sample blog with MEAN",
"version": "1.0.0",
"private": true,
"dependencies": {
"express": "4.14.0",
"mongoose" : "4.5.7",
"pug" : "2.0.0-beta4"
}
}
Sau đó ta cài các bộ này bằng lênh
npm install
Sử dụng Jade template để tạo giao diện cho ứng dụng
Chúng ta sẽ sử dụng jade làm template để xây dựng lên giao diện blog. Jade có cơ chế extends template rất linh hoạt, nếu ai đã từng sử dụng Laravel( PHP framework ) thì chắc chắn sẽ thích điều này.
Khi sử dụng jade chúng ta không cần quan tâm tới thẻ đóng html nữa, cú pháp cực kỳ gọn nhẹ , dễ đọc và dễ hiểu.
Khi template con extends 1 template cha thì mặc định nó sẽ có hết nội dung từ template cha.
Để cho dễ hiểu hãy xem file master.jade mà tôi tạo ra như sau:
doctype html
html
head
title My first Blog :: #{title}
block styles
link(href="/themes/css/bootstrap.min.css" rel="stylesheet")
link(href="/themes/css/font-awesome.min.css" rel="stylesheet")
link(href="/themes/css/style.css" rel="stylesheet")
body
div#wrapper
include ../includes/header.jade
div(class="container")
block contents
include ../includes/footer.jade
block scripts
script(src="/themes/js/jquery.1.10.2.min.js")
script(src="/themes/js/bootstrap.min.js")
script(src="/themes/js/tinymce4x/tinymce.min.js")
script(src="/themes/js/main.js")
Tôi có định nghĩa ra 3 khối đó là block styles
, block scripts
và block contents
.
block styles
là nơi để template con có thể thêm những style riêng của mình vào ngoài những style mà template cha quy định.
block scripts
là nơi để template con có thể thêm những đoạn script riêng của mình vào ngoài những script mà template cha quy định.
block contents
đây là nơi chứa nội dung mà template con định nghĩa ra.
Dùng include
để chèn nội dung của file template khác vào 1 file. ( Cái này giống như @include trong Laravel ).
Trong bài này chúng ta có include 2 file đó là
div.nav
nav(class="navbar navbar-default" rol="navigation")
div.container-fluid
div(class="collapse navbar-collapse" id="bs-example-navbar-collapse-1")
ul(class="nav navbar-nav")
li.active
a(href="/") Home
li
a(href="/create-post") Create Post
Và
div.nav
Sample make blog
Mục đích của bài này tôi muốn chia sẻ với các bạn:
- Làm sao để xây dựng 1 trang web động sử dụng nodejs
- Kết hợp nodejs và mongodb như thế nào
- Export module trong nodejs
Load các Module cần thiết
Việc đầu tiên và rất quan trọng đó là load các module bạn muốn sử dụng.
File để chạy ứng dụng của chúng ta là server.js
.
var functions = require('./libs/functions.js');
var fs = require('fs');
var express = require('express');
var jade = require('jade');
var bodyParser = require('body-parser');
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var mongoose = require('mongoose');
Cấu hình cho ứng dụng
// Khởi tạo app với express
var app = express();
// Đường dẫn tới thư mục pulic
app.use('/themes/', express.static(__dirname + '/public/'));
// Đường dẫn tới thư mục upload
app.use('/pictures/', express.static(__dirname + '/public/upload/'));
// Đường dẫn tới thư mục views
app.set('views', __dirname + '/views/');
// Sử dụng template engine là jade
app.set('view engine', 'jade');
app.engine('jade', require('jade').__express);
// Sử dụng bodyParser để upload ảnh và pasrse request POST
app.use(bodyParser.urlencoded({
extended: true
}));
Kết nối đến Mongo DB
// Kết nối tới mongo
mongoose.connect('mongodb://localhost/test');
// Khởi tạo đối tượng connection để test kết nối
var dbMongo = mongoose.connection;
// Handle sự kết open và error khi kết nối mongo
dbMongo.on('error', console.error.bind(console, 'connection error:'));
// PostSchema
var PostSchema = mongoose.Schema({
title : String,
slug : String,
picture : String,
teaser : String,
content : String,
author: String,
time : Number
});
//Model Post với PostSchema tương ứng
var Post = mongoose.model('Post', PostSchema);
Các trang chính
Trong bài này tôi sẽ làm 3 trang đó là trang danh sách các blogs, chi tiết và trang tạo blog. Trang danh sách sẽ liệt kê tất cả các blogs, sau đó click vào 1 trong các blog đó sẽ ra trang chi tiết nội dung cụ thể của blog đó. Và trang tạo bài viết để có thể viết 1 bài mới sau đó bổ sung vào trang danh sách.
Trang danh sách Blogs
app.get('/', function(req, res){
// Lấy tất cả danh sách bài viết
// Đổ dữ liệu và template index và output ra html
var posts = Post.find({}, function(err, result) {
// Sắp xếp bài viết mới nhất lên đầu
result = result.sort({'id' : -1});
res.render('index', { title : 'Home page' , posts : result, functions : functions});
});
});
Và file index.js là
extends ./layouts/master.jade
block contents
div(class="row")
- each post in posts
div(class="post-item col-sm-4")
a(href="#{ functions.urlPost(post) }")
h5(class="col-sm-12") #{post.title}
div.picture-crop
img(src="/pictures/#{post.picture}" class="col-sm-12 picture")
Qua file index.js
này ta có thể thấy nét tương đồng với template của Laravel. Đầu tiên là extends
tức là lấy giao diện khung của master.js sau đó phần block_contents
sẽ là nội dung bên dưới và ta sẽ gọi từng blog rồi hiển thị tên cũng như ảnh đại diện của blog đó và có link để vào trang chi tiết.
Trang chi tiết
app.get('/post/:title/:id.html', function(req, res) {
// Get id param
var id = req.params.id || 0;
// Tìm bài viết tưong ứng với ID
Post.findById(id, function(err, post) {
// Trả về HTML chi tiết tưong ứng với bài viết
if(post) {
res.render('post/detail', {title : post.title, post : post});
return false;
}
// Không tìm thấy thì hiện trang 404
res.render('404');
});
});
extends ../layouts/master.jade
block contents
h1 #{post.title}
p.well #{post.teaser}
div !{post.content}
Trang tạo blog
app.get('/create-post', function(req, res) {
res.render('post/create', { title : 'Create a post' });
});
// Xử lý đăng bài viết
app.post('/create-post', multipartMiddleware, function(req, res) {
// Khởi tạo đối tượng post
var post = new Post;
post.title = req.body.title;
post.slug = functions.removeAccent(req.body.title);
post.teaser = req.body.teaser;
post.content = req.body.content;
// Upload ảnh
var file = req.files.picture;
var originalFilename = file.name;
var fileType = file.type.split('/')[1];
var fileSize = file.size;
var pathUpload = __dirname + '/public/upload/' + originalFilename;
var data = fs.readFileSync(file.path);
fs.writeFileSync(pathUpload, data);
if( fs.existsSync(pathUpload) ) {
post.picture = originalFilename;
}
// Lưu bài viết vào Mongo và trả về thông tin đăng thành công hay chưa?
post.save(function(err, obj) {
if(!err) {
res.render('post/create', { status : 'success', message : 'Post successful!' });
return false;
}
});
});
Và giao diện tạo blog
extends ../layouts/master.jade
block contents
div(class="page-header")
h5 Create a post
case status
when "success"
div(class="alert alert-success")
p #{message}
when "error"
div(class="alert alert-danger")
p #{message}
form(class="form form-horizontal" action="" method="POST" enctype="multipart/form-data")
div(class="form-group")
label(class="col-sm-2 control-label") Picture
div(class="col-sm-6")
input(class="form-control" type="file" name="picture")
div(class="form-group")
label(class="col-sm-2 control-label") Title
div(class="col-sm-6")
input(class="form-control" type="text" name="title" placeholder="Title")
div(class="form-group")
label(class="col-sm-2 control-label") Teaser
div(class="col-sm-6")
textarea(class="form-control" name="teaser" placeholder="Teaser")
div(class="form-group")
label(class="col-sm-2 control-label") Content
div(class="col-sm-6")
textarea(class="form-control content-editor" name="content" placeholder="Content")
div(class="form-group")
div(class="col-sm-2 col-sm-offset-2")
button(type="submit" class="btn btn-sm btn-primary") Update
Vậy toàn bộ source phần xử lý ở file server.js
sẽ như sau
// Require functions
var functions = require('./libs/functions.js');
var fs = require('fs');
var express = require('express');
var jade = require('jade');
var bodyParser = require('body-parser');
var multipart = require('connect-multiparty');
var mongoose = require('mongoose');
var multipartMiddleware = multipart();
// Connect Mongo
mongoose.connect('mongodb://localhost/test');
var dbMongo = mongoose.connection;
var PostSchema = mongoose.Schema({
title : String,
slug : String,
picture : String,
teaser : String,
content : String,
author: String,
time : Number
});
var Post = mongoose.model('Post', PostSchema);
dbMongo.on('error', console.error.bind(console, 'connection error:'));
dbMongo.once('open', function(){
console.log('MongoDb connected');
});
// Config app
var app = express();
app.use('/themes/', express.static(__dirname + '/public/'));
app.use('/pictures/', express.static(__dirname + '/public/upload/'));
app.set('views', __dirname + '/views/');
app.set('view engine', 'jade');
app.engine('jade', require('jade').__express);
app.use(bodyParser.urlencoded({
extended: true
}));
// Handle request
app.get('/', function(req, res){
var posts = Post.find({}, function(err, result) {
// Sort by blog latest
result = result.sort({'id' : -1});
res.render('index', { title : 'Home page' , posts : result, functions : functions});
});
});
app.get('/post/:title/:id.html', function(req, res) {
var id = req.params.id || 0;
Post.findById(id, function(err, post) {
if(post) {
res.render('post/detail', {title : post.title, post : post});
return false;
}
res.render('404');
});
});
app.get('/create-post', function(req, res) {
res.render('post/create', { title : 'Create a post' });
});
app.post('/create-post', multipartMiddleware, function(req, res) {
var post = new Post;
post.title = req.body.title;
post.slug = functions.removeAccent(req.body.title);
post.teaser = req.body.teaser;
post.content = req.body.content;
var file = req.files.picture;
var originalFilename = file.name;
var fileType = file.type.split('/')[1];
var fileSize = file.size;
var pathUpload = __dirname + '/public/upload/' + originalFilename;
var data = fs.readFileSync(file.path);
fs.writeFileSync(pathUpload, data);
if( fs.existsSync(pathUpload) ) {
post.picture = originalFilename;
}
post.save(function(err, obj) {
if(!err) {
res.render('post/create', { status : 'success', message : 'Post successful!' });
return false;
}
});
});
var server = app.listen(32000, function() {
console.log('Listening on port %d', server.address().port);
});
Và giờ chúng ta chạy thử thôi bằng lệnh
node server.js
Kết luận
Qua bài viết chúng ta tìm hiểu cơ bản qua về cách làm việc của MEAN, chủ yếu ở đây là tìm hiểu về làm việc với Mongo DB thông qua Mongoose
và tạo template bằng Jade template
. Bài viết trình bày ở mức cơ bản nên có thể có nhiều thiếu sót cũng như chưa được tối ưu.
All rights reserved