MeanJS - Full stack development framework based on Javacripts

Để thuận tiện cho việc xây dựng, quản lý và phát triển các trang web, rất nhiều các framework đã được xây dựng. Nổi tiếng nhất trong số đó phải kể đến LAMP bao gồm Linux server, Apache, MySQL và PHP. Tuy nhiên, web hiện đại yêu cầu khả năng tương tác cao hơn đồng nghĩa với việc LAMP stack không phải là giải pháp tối ưu. Đã có rất nhiều các framework khác được xây dựng dựa trên nền tảng các ngôn ngữ khác nhau như Ruby, Python, Perl,..v.v.. tuy nhiên về cơ bản vẫn dựa trên LAMP và ít có sự phá cách. Thời gian gần đây, nhiều website đã được xây dựng dựa trên NodeJS tuy nhiên vẫn còn hết sức đơn giản vì NodeJS vẫn còn hết sức thô sơ. Bài viết này xin dược phép giới thiệu một framework mới dựa trên NodeJS có khả năng xây dựng một trang web hoàn chỉnh dựa trên ngôn ngữ Javascript, đó là MEANJS

1. Giới thiệu về MeanJS

MeanJS là một Framework hoàn toàn mới được xây dựng dựa trên các thành phần bao gồm:

  • MongoDB: Một NoSQL database điển hình
  • Express: Middleware với database
  • AngularJS: Front end framwork

Điểm đặc biệt của MeanJS là được viết hoàn toàn dựa trên Javascript. Mặc dù khởi điểm là một ngôn ngữ thuần view, sử dụng chủ yếu để tương tác với DOM (Documnet Object Model), tuy nhiên sau khi Google giới thiệu V8, một trình biên dịch viết bằng C++ để xây dựng các ứng dụng Javascript trên server, vai trò của Javascript đã thay đổi khi nhiều ứng dụng trên server viết ra đời như NodeJS, SocketJS... Mặc dù vậy các ứng dụng này còn hết sức thô sơ và chỉ có thể đảm nhiệm một chức năng riêng biệt nào đó (real-time, mobile web..). Với sự xuất hiện của MeanJS, chúng ta có thể xây dựng một trang web hoàn toàn dựa trên các chương trình trên

1.1 Cài đặt

1.1.1 Dependencies

NodeJS

NodeJS có thể coi như là nền móng của tất cả các Javascript application trên server. Việc cài dặt NodeJS có thể thực hiện đơn giản trên các hệ điều hành khác nhau:

  • Ubuntu với apt-get
$ sudo apt-get install node
  • MacOS với homebrew
$ brew install node
  • Các hệ điều hành khác có thể tìm các gói cài đặt tại trang chủ của NodeJS

MongoDB

MôngoDB là một NoSQL database điển hình, các ứng dụng hiện đại đang dịch chuyển dần từ việc sử dụng MySQL sang NoSQL database và MeanJS không nằm ngoài xu thế đó. Việc cài đặt MongoDB cũng giống như node, có thể thực hiện đơn giản bằng apt get hoặc brew

Bower và Grunt

$ npm install -g bower
$ npm install -g grunt-cli

1.1.2 Cài đặt MeanJS và khởi tạo MeanJS project

Hướng dẫn cụ thể cho việc cài đặt MeanJS có thể tìm thấy trên trang chủ của MeanJS hoặc trên Github

Về cơ bản chúng ta có thể khởi tạo một MeanJS application bằng việc clone trực tiếp trên Github. Tuy nhiên, chúng ta có thể tạo MeanJS project dùng Yeoman

Yeoman

Giống như các framework khác đều cung cấp các công cụ thể có thể tạo scaffold, chúng ta có thể khởi tạo các files dành cho Mean dùng Yeoman Cài đặt Yeoman rất đơn giản, chỉ với:

$ npm install -g yo

Sau đó, chúng ta cài đặt công cụ Generate cho MeanJS

$ npm install -g generator-meanjs

Để khởi tạo MeanJS project sử dụng Yeoman chúng ta chỉ cần:

yo meanjs

Lúc này trên Yeoman sẽ đưa cho chúng ta các lựa chọn cho project, tuỳ vào yêu cầu của ứng dụng mà lựa chọn các gói cho phù hợp


     _-----_
    |       |
    |--(o)--|   .--------------------------.
   `---------´  |    Welcome to Yeoman,    |
    ( _´U`_ )   |   ladies and gentlemen!  |
    /___A___\   '__________________________'
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

You're using the official MEAN.JS generator.
? What would you like to call your application? MEAN
? How would you describe your application? Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js
? How would you describe your application in comma seperated key words? MongoDB, Express, AngularJS, Node.js
? What is your company/author name? ThienTd
? Would you like to generate the article example CRUD module? No
? Which AngularJS modules would you like to include?
 ◯ ngCookies
 ◯ ngAnimate
 ◯ ngTouch
❯◯ ngSanitize

Sau khi cài đặt các gói thành công, chúng ta hoàn toàn có thể kiểm tra xem MeanJS đã cài đặt thành công chưa khi khởi tạo server bằng grunt

$ grunt

Truy cập vào local server (localhost:3000), nếu như MeanJS project được khởi tạo thành công, màn hình sẽ trả về như sau:

Screen Shot 2015-03-28 at 6.09.30 PM.png

1.2 Cấu trúc thư mục của MeanJS

Một trong những đặc điểm cơ bản của các web framework hiện đại là việc chia tách Front EndBack End. Cấu trúc thư mục cũng như các file thể hiện rõ điều đó.

ducthien-MBP:example ducthien$ ls -l
total 104
-rw-r--r--   1 ducthien  staff   669 Nov  5 22:40 Dockerfile
-rw-r--r--   1 ducthien  staff  1053 Nov  4 21:20 LICENSE.md
-rwxr-xr-x   1 ducthien  staff    48 Nov  5 22:26 Procfile
-rw-r--r--   1 ducthien  staff  8904 Nov  5 22:41 README.md
drwxr-xr-x   7 ducthien  staff   238 Mar 28 18:07 app
-rw-r--r--   1 ducthien  staff   463 Mar 28 18:07 bower.json
drwxr-xr-x   9 ducthien  staff   306 Mar 28 18:07 config
-rw-r--r--   1 ducthien  staff   147 Nov  5 22:40 fig.yml
-rw-r--r--   1 ducthien  staff   414 Nov  5 00:04 generate-ssl-certs.sh
-rw-r--r--   1 ducthien  staff  3681 Nov  5 22:40 gruntfile.js
-rw-r--r--   1 ducthien  staff  1375 Nov  4 21:31 karma.conf.js
drwxr-xr-x  54 ducthien  staff  1836 Mar 28 18:07 node_modules
-rwxr-xr-x   1 ducthien  staff  1788 Mar 28 18:07 package.json
drwxr-xr-x   8 ducthien  staff   272 Mar 28 18:08 public
-rwxr-xr-x   1 ducthien  staff   815 Nov  5 22:41 server.js

package.json

Tất cả các chương trình sử dụng nền NodeJS đều có file package.json và MeanJS cũng không phải ngoại lệ. File này sẽ bao hàm tất cả các gói cần thiết dùng trong MeanJS cũng như tuỳ biết theo từng môi trường

 {
  "name": "mean",
    "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js",
    "version": "0.0.1",
    "author": "ThienTd",
    "engines": {
      "node": "0.10.x",
      "npm": "1.4.x"
    },
    "scripts": {
      "start": "grunt",
      "test": "grunt test",
      "postinstall": "bower install --config.interactive=false"
    },
    "dependencies": {
      "express": "~4.10.1",
      "express-session": "~1.9.1",
      ....
    },
    "devDependencies": {
      "supertest": "~0.14.0",
      "should": "~4.1.0",
      "grunt-env": "~0.4.1",
      ...
    }
}

bower.json

Nếu như package.json chứa đựng các gói cần thiết cho Back End thì bower.json bao hàm các packages dành cho Front End

{
  "name": "mean",
    "version": "0.0.1",
    "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js",
    "dependencies": {
      "bootstrap": "~3",
      "angular": "~1.2",
      "angular-resource": "~1.2",
      "angular-mocks": "~1.2",
      "angular-cookies": "~1.2",
      "angular-animate": "~1.2",
      "angular-touch": "~1.2",
      "angular-sanitize": "~1.2",
      "angular-bootstrap": "~0.11.2",
      "angular-ui-utils": "~0.1.1",
      "angular-ui-router": "~0.2.11"
    }
}

chúng ta có thể dễ dàng nhận ra các gói quen thuộc của AngularJS như angular-touch hay angular-resourse

app

ducthien-MBP:app ducthien$ tree .
.
├── controllers
│   ├── core.server.controller.js
│   ├── errors.server.controller.js
│   ├── users
│   │   ├── users.authentication.server.controller.js
│   │   ├── users.authorization.server.controller.js
│   │   ├── users.password.server.controller.js
│   │   └── users.profile.server.controller.js
│   └── users.server.controller.js
├── models
│   └── user.server.model.js
├── routes
│   ├── core.server.routes.js
│   └── users.server.routes.js
├── tests
│   └── user.server.model.test.js
└── views
    ├── 404.server.view.html
    ├── 500.server.view.html
    ├── index.server.view.html
    ├── layout.server.view.html
    └── templates
        ├── reset-password-confirm-email.server.view.html
        └── reset-password-email.server.view.html

Về cơ bản app bao gồm các file và folers dành cho server side. Với những người đã quen với mô hình MVC có thể dễ dàng nhận ra các folder như models, controllers routes hay tests. Điểm lưu ý duy nhất khác biệt với các mô hình khác là views không đóng vai trò quan trọng vì đã có phần Front End riêng. Có thể coi rằng bên trong app bao gồm MC(Model , Controller)

public

Đây là nơi chứa đựng tất cả những gì cần thiết cho Front End

-rw-r--r--   1 ducthien  staff  730 Nov  4 21:29 application.js
-rw-r--r--   1 ducthien  staff  833 Mar 28 18:07 config.js
-rwxr-xr-x   1 ducthien  staff  191 Nov  5 22:26 humans.txt
drwxr-xr-x  14 ducthien  staff  476 Mar 28 18:08 lib
drwxr-xr-x   4 ducthien  staff  136 Mar 28 18:07 modules
-rwxr-xr-x   1 ducthien  staff   32 Nov  5 22:26 robots.txt

Nơi đáng lưu ý nhất bên trong public là folder modules, nơi chứa đựng các view cũng như function được phân chia theo theo mô hình MVC của AngularJS. Trong modules, folder core sẽ là nơi khởi tạo dành cho **Front End ** và các các folder khác tuỳ theo model mà developer mong muốn. Giống như mô hình MVC, bên trong mỗi folder đều có đủ 3 thành phần

core/
├── config
│   └── core.client.routes.js
├── controllers
│   ├── header.client.controller.js
│   └── home.client.controller.js
├── core.client.module.js
├── css
│   └── core.css
├── img
│   ├── brand
│   │   ├── favicon.ico
│   │   └── logo.png
│   └── loaders
│       └── loader.gif
├── services
│   └── menus.client.service.js
├── tests
│   ├── header.client.controller.test.js
│   └── home.client.controller.test.js
└── views
    ├── header.client.view.html
    └── home.client.view.html

Mỗi module sẽ đi kèm với ng-controller riêng biệt và từ đây chúng ta có thể định nghĩa các services cũng như directives trên AngularJS

Config files

Ngoài các files và folder config dành riêgn cho Front EndBack end, MeanJS cung cấp hệ thống file cấu hình dành cho cả ứng dụng:

config/
├── config.js
├── env
│   ├── all.js
│   ├── development.js
│   ├── production.js
│   ├── secure.js
│   └── test.js
├── express.js
├── init.js
├── passport.js
├── sslcerts
└── strategies
    ├── facebook.js
    ├── github.js
    ├── google.js
    ├── linkedin.js
    ├── local.js
    └── twitter.js

Chúng ta có thể chia cấu hình ra làm các loại cơ bản sau:

  • Cấu hình chung: config.js, init.js
  • Cấu hình dành cho các môi trường env/
  • Cấu hình dành cho các ứng dụng thành phần (express, passport, ssl)
  • Cấu hình tuỳ biến strategies

2. Xây dựng CRUD model trên MeanJS

CRUD - Create, Read, Update, Delete bao hàm những chức năng cơ bản mà các trang web phổ biến phải cho. Tương tự như các framework khác, MeanJS có thể tạo ra các CRUD model sử dụng Yeoman

ducthien-MBP:sample-app ducthien$ yo meanjs:crud-module books
? Which supplemental folders would you like to include in your angular module? css, img, directives, filters
? Would you like to add the CRUD module links to a menu? Yes
? What is your menu identifier(Leave it empty and press ENTER for the default "topbar" menu)? topbar
   create app/controllers/books.server.controller.js
   create app/models/book.server.model.js
   create app/routes/books.server.routes.js
   create app/tests/book.server.model.test.js
   create app/tests/book.server.routes.test.js
   create public/modules/books/config/books.client.routes.js
   create public/modules/books/controllers/books.client.controller.js
   create public/modules/books/services/books.client.service.js
   create public/modules/books/tests/books.client.controller.test.js
   create public/modules/books/config/books.client.config.js
   create public/modules/books/views/create-book.client.view.html
   create public/modules/books/views/edit-book.client.view.html
   create public/modules/books/views/list-books.client.view.html
   create public/modules/books/views/view-book.client.view.html
   create public/modules/books/books.client.module.js

Chỉ với câu lệnh yo meanjs:crud-model books chúng ta có thể khởi tạo cả *Front EndBack end dành cho books. Dựa trên các files và folders đã được tạo ra bởi Yeoman

Back end

Như đã đề cập ở phần trên, phần Back end của MeanJS thực chất chỉ bao hàm M-Model và C-Controller

Controller

Dựa trên các request từ phía client để xử lý sử dụng các action cơ bản (create, read, list, update, delete)

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    errorHandler = require('./errors.server.controller'),
    Book = mongoose.model('Book'),
    _ = require('lodash');

/**
 * Create a Book
 */
exports.create = function(req, res) {
  var book = new Book(req.body);
  book.user = req.user;

  book.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(book);
    }
  });
};
...

Ngoài ra ta hoàn toàn có thể định nghĩa thêm các action khác để đảm bảo chức năng của website

Model

Sử dụng để định dạng dữ liệu.

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	Schema = mongoose.Schema;

/**
 * Book Schema
 */
var BookSchema = new Schema({
	name: {
		type: String,
		default: '',
		required: 'Please fill Book name',
		trim: true
	},
  description: {
    type: String,
    default: '',
    required: 'Please fill the description of book',
    trim: true
  },
  author: {
    type: String,
    default: '',
    required: 'Please enter the authors of the book',
    trim: true
  },
	created: {
		type: Date,
		default: Date.now
	},
	user: {
		type: Schema.ObjectId,
		ref: 'User'
	}
});

Nếu như sau khi Yeoman khởi tạo, books chỉ bao gồm có 2 trường chính là name, createduser, chúng ta có thể thêm và bớt định dạng của book tuỳ biến theo yêu cầu được đưa ra

Front End

Như đã đề cập ở phía trên, MeanJS sử dụng AngularJS để xây dựng Front End. Các thư viện cần thiết có thể thêm vào bower.json. Trong quá trình khởi tạo sử dụng Yeoman, link tới books đã được thêm vào header của Website Screen Shot 2015-03-28 at 7.17.00 PM.png

BooksController sẽ định nghĩa các request tới server cho phù hợp với crud model. Trong trường hợp chúng ta thay đổi các trường của dữ liệu thì cần phải thay đổi dữ liệu lấy từ view trước khi gửi request tới server

angular.module('books').controller('BooksController', ['$scope', '$stateParams', '$location', 'Authentication', 'Books',
    function($scope, $stateParams, $location, Authentication, Books) {
      $scope.authentication = Authentication;

      // Create new Book
      $scope.create = function() {
        // Create new Book object
        var book = new Books ({
          name: this.name,
            description: this.description,
            author: this.author
        });

        // Redirect after save
        book.$save(function(response) {
          $location.path('books/' + response._id);

          // Clear form fields
          $scope.name = '';
          $scope.desciption = '';
          $scope.author = '';
        }, function(errorResponse) {
          $scope.error = errorResponse.data.message;
        });
      };
...

Tuy nhiên, chúng ta cần phải thêm các trường cần thiết vào trong views để có thể lấy và hiển thị dữ liệu đúng theo CRUD model

books/views/
├── create-book.client.view.html
├── edit-book.client.view.html
├── list-books.client.view.html
└── view-book.client.view.html

Dưới đây là kết quả: List of books (http://localhost:3000/#!/books) Screen Shot 2015-03-28 at 7.29.32 PM.png

Detail of book Screen Shot 2015-03-28 at 7.31.25 PM.png

3. Kết Luận

MeanJS ra đời tuy chưa lâu nhưng thực ra đang tạo ra một cơn sốt mới dành cho những nhà phát triển ứng dụng. Với MeanJS, ngoài việc tận dụng được hiệu năng cực kỳ tốt của các ứng dụng Javascript, MeanJS còn cho phép tuỳ biến khá tốt. Với MeanJS, ta có thể xây dựng một trang web hoàn thiện sử dụng Javascript hoặc sử dụng như một End user service point của một Data server thuần tuý.