Tìm hiểu MEANJS qua việc làm ứng dụng viết blog
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 ...
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.