12/08/2018, 13:39

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

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.

0