Author Archives: Phở Code

NodeJS – Tạo yêu cầu HTTP

Trong Node có lớp http.ClientRequest dùng để lưu những thông tin về một gói tin HTTP được gửi đến các webserver, chúng ta có thể tạo ra hầu như tất cả mọi yêu cầu HTTP, các lớp kiểu này thường được dùng để gửi các yêu cầu tự động. Trong bài này chúng ta sẽ tìm hiểu cách tạo một yêu cầu HTTP từ client, trong bài sau chúng ta sẽ tìm hiểu và xây dựng REST server và dùng các yêu cầu HTTP này để test server đó.

Nếu bạn đã từng dùng các hệ điều hành Linux thì có lẽ bạn sẽ biết đến các trình gửi yêu cầu HTTP là wget hoặc curl. Chúng ta sẽ viết đoạn code mô phỏng trình wget.

Chúng ta tạo một file có tên wget.js với nội dung như sau:

var http = require('http');
var url = require('url');
var util = require('util');
var argUrl = process.argv[2];
var parsedUrl = url.parse(argUrl, true);
var options = {
    host: null,
    port: -1,
    path: null,
    method: 'GET'
};
options.host = parsedUrl.hostname;
options.port = parsedUrl.port;
options.path = parsedUrl.pathname;
if(parsedUrl.search)
    options.path += "?" + parsedUrl.search;
var req = http.request(options, function(res) {
    util.log('STATUS: ' + res.statusCode);
    util.log('HEADERS: ' + util.inspect(res.headers));
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
        util.log('BODY: ' + chunk);
    });
    res.on('error', function(err) {
        util.log('RESPONSE ERROR: ' + err);
    });
});
req.on('error', function(err) {
    util.log('REQUEST ERROR: ' + err);
});
req.end();

Sau đó chúng ta có thể chạy đoạn code trên như sau: node wget.js http://www.google.com

capture

Chúng ta sẽ nhận về được một gói tin trả lời từ server của google. Trong đó dòng đầu tiên là mã trạng thái STATUS, ở đây chúng ta nhận được mã là 302, mã này là mã redirect, nghĩa là chúng ta sẽ được chuyển tới một đường dẫn khác, trong trường HEADERS chúng ta có một vài thông tin khác, trong đó location là đường dẫn mà chúng ta sẽ được chuyển tới, ở đây là http://www.google.com.vn/?gfe_rd=cr&ei=WpDkV-CjEKzY8gf-n4uICw, nếu bạn mở trình duyệt web lên rồi truy cập http://www.google.com thì bạn cũng sẽ được chuyển tới đường dẫn như trên. Phần BODY là nội dung HTML trả về, nếu chúng ta dùng trình duyệt để gửi thì đoạn code HTML này sẽ được “vẽ” trên trình duyệt.

var argUrl = process.argv[2];
var parsedUrl = url.parse(argUrl, true);

Trong đoạn code trên, chúng ta tạo một đối tượng thuộc lớp URL từ phương thức url.parse() là parsedUrl, phương thức này nhận vào đường dẫn mà chúng ta đã nhập (ở trên là http://www.google.com), tham số thứ hai cho biết các tham số đi kèm trong đường dẫn có được mã hóa hay không.

var options = {
    host: null,
    port: -1,
    path: null,
    method: 'GET'
};
options.host = parsedUrl.hostname;
options.port = parsedUrl.port;
options.path = parsedUrl.pathname;

Đối tượng options sẽ lưu một số thông tin trong gói tin yêu cầu được gửi đi, như host là đường dẫn website, port là cổng, pathname là đường dẫn phía sau hostname, ví dụ www.google.com/ thì pathname'/', method là phương thức gửi lên như GET, PUT, POST

if(parsedUrl.search)
     options.path += "?" + parsedUrl.search;

Nếu chúng ta đưa vào đường dẫn có tham số, chẳng hạn http://www.google.com/?query=germany, thì đoạn ?query=germany sẽ được lưu trong thuộc tính search của đối tượng parsedUrl, ở đây chúng ta chèn vào sau thuộc tính path của đối tượng options.

var req = http.request(options, function(res) {
    util.log('STATUS: ' + res.statusCode);
    util.log('HEADERS: ' + util.inspect(res.headers));
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
        util.log('BODY: ' + chunk);
    });
    res.on('error', function(err) {
        util.log('RESPONSE ERROR: ' + err);
    });
});

Phương thức http.request() nhận vào đối tượng options và một hàm callback, hàm này sẽ được gọi khi có gói tin trả về từ server, trong hàm này chúng ta chỉ đơn giản là in nội dung gói tin đó ra màn hình. Tham số res trong hàm callback là một đối tượng thuộc lớp EventEmitter, đối tượng này lắng nghe sự kiện dataerror, sự kiện data xảy ra khi có dữ liệu được trả về, sự kiện error xảy ra khi có lỗi trả về.

req.on('error', function(err) {
    util.log('REQUEST ERROR: ' + err);
});

Phương thức http.request() trả về một đối tượng lớp http.ClientRequest. Bản thân đối tượng này kế thừa từ lớp EventEmitter và sẽ tạo ra sự kiện errordata. Sự kiện data xảy ra khi có dữ liệu đến, còn sự kiện error xảy ra khi có lỗi xảy ra. Ở đây chúng ta chỉ bắt sự kiện error, bạn có thể chạy lại đoạn code trên và đưa vào tham số là www.google.com (bỏ phần http://) thì sẽ có lỗi.

NodeJS – Ví dụ về Express

Chúng ta sẽ viết ứng dụng tính số Fibonacci với Express.

Dãy số Fibonacci là một dãy số có dạng như sau: 0 1 1 2 3 5 8 13 21 34…

Trong đó 2 số đầu tiên luôn luôn là 0 và 1, các số thứ 2 trở đi sẽ có giá trị bằng tổng của 2 số đứng trước nó. Dãy số này được phát minh vào năm 1202 bởi Lenonardo of Pisa.

Đầu tiên chúng ta tạo một project với tên fibonacci (hoặc tên bất kỳ tùy bạn đặt) và thực hiện cài đặt các module như bình thường:

C:\Users\PhoCode>express --ejs fibonacci
...
C:\Users\PhoCode>cd fibonacci
C:\Users\PhoCode>npm install
...

Đầu tiên chúng ta sửa file app.js ở thư mục gốc của project như sau:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var fibonacci = require('./routes/fibonacci');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);
app.use('/fibonacci', fibonacci.index);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});


// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

module.exports = app;

Trong đoạn code trên chúng ta chèn thêm 2 dòng là:

var fibonacci = require('./routes/fibonacci');
...
app.use('/fibonacci', fibonacci.index);

Lưu ý là dòng require() nên được đặt cùng với các dòng require() khác cho dễ quản lý, còn dòng app.use() là phần routing, bạn có thể đặt ở đâu cũng được nhưng phải đặt trước đoạn code dùng để báo lỗi 404 này:

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

Lý do là vì Express so sánh các đường dẫn từ trên xuống dưới, nếu không thấy đường dẫn nào khớp thì sẽ trả về trang 404.

Tiếp theo cũng trong thư mục chứa file app.js, chúng ta tạo một file có tên math.js với nội dung như sau:

var fibonacci = exports.fibonacci = function(n) {
    var fibos = [];
    fibos[0] = 0;
    fibos[1] = 1;
    fibos[2] = 1;
    for(var i = 3 ; i <= n ; i++) 
        fibos[i] = fibos[i - 2] + fibos[i - 1];
    return fibos[n];
}

Đây chỉ là module math chứa hàm tính số Fibonacci viết bằng vòng lặp rất đơn giản.

Tiếp theo chúng ta sẽ tạo các  file có đuôi là .ejs, đây chỉ là các file chứa code HTML để in nội dung lên trang web.

Đầu tiên chúng ta tạo một file có tên top.ejs trong thư mục views với nội dung như sau:

<html>
<head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
    <h1><%= title %></h1>
    <div class='navbar'>
        <p>
            <a href='/'>Home</a>
            |
            <a href='/fibonacci'>Fibonacci calculator</a>
        </p>
    </div>

Cũng trong thư mục views chúng ta tạo file bottom.ejs với nội dung như sau:

</body>
</html>

Tiếp theo chúng ta chỉnh lại file views/index.ejs như sau:

<% include top %>
<p>Welcome to the Math calculator</p>
<% include bottom %>

File index.ejs được dùng để trả về cho đường dẫn '/', các dòng <% include top %><% include bottom %> có tác dụng chép đoạn code trong file top.ejsbottom.ejs vào vị trí tương ứng trong file index.ejs. Đây chỉ là một thủ thuật để chúng ta phân chia các thành phần của một trang web thành các file riêng biệt, sau này khi tạo các trang khác chúng ta cũng chỉ cần thêm các câu lệnh include như thế là được, thay vì phải code lại từ đâu. Các đoạn code nằm trong cặp <%= %> hoặc <% %> là code của riêng Express, được dùng để kết nối với code JavaScript chứ không hoàn toàn là code HTML.

Bây giờ chúng ta tạo file fibonacci.ejs trong thư mục views với nội dung như sau:

<% include top %>
<% if (typeof fiboval !== "undefined") { %>
    <p>Fibonacci's index: <%= fibonum %></p> 
    <p>Value: <%= fiboval %></p>
<% } %>
<p>Enter Fibonacci's index:<p>
<form name='fibonacci' action='/fibonacci' method='get'>
    <input type='text' name='fibonum' />
    <input type='submit' value='Submit' />
</form>
<% include bottom %>

Cuối cùng chúng ta tạo một file có tên fibonacci.js trong thư mục routes với nội dung như sau:

var math = require('../math');
exports.index = function(req, res) {
    if(req.query.fibonum) {
        res.render('fibonacci', {
            title: "Calculate Fibonacci numbers",
            fibonum: req.query.fibonum,
            fiboval: math.fibonacci(req.query.fibonum)
        });
    } else {
        res.render('fibonacci', {
            title: "Calculate Fibonacci numbers",
            fiboval: undefined
        });
    }
};

Thế là xong, chúng ta có thể chạy project (bằng lệnh npm start) để xem kết quả:

capture

Trang localhost:3000 được trả về từ file views/index.ejs.

capture

Khi chúng ta click vào đường link Fibonacci calculator thì sẽ được chuyển đến trang /fibonacci có file trả về là views/fibonacci.ejs. Ở trang này chúng ta có thể nhập số thứ tự và trang web sẽ trả về giá trị Fibonacci đó, chẳng hạn số fibonacci thứ 20 có giá trị là 4181.

Chúng ta sẽ tìm hiểu những gì diễn ra ở server như sau:

Trong file app.js, chúng ta có 3 đường dẫn routing, 2 đường dẫn đầu tiên là '/''/users' do Express tự tạo ra (bạn có thể nhập localhost:3000/users để thấy câu thông báo respond with a resource, đường dẫn thứ 3 là '/fibonacci' do chúng ta tạo ra. Các hàm res.render() (trong file index.jsfibonacci.js) sẽ dựng trang web với các file .ejs tương ứng.

Có nghĩa là khi chúng ta truy cập localhost:3000/, Express sẽ chạy đoạn code trong file routes/index.js:

var express = require('express');
var router = express.Router();

/* GET home page. */

router.get('/', function(req, res, next) {
    res.render('index', { title: 'Express' });
});

module.exports = router;

Dòng res.render('index', { title: 'Express' }); có chức năng trả về nội dung HTML trong file views/index.ejs cho người dùng, ngoài ra hàm res.render() không chỉ gọi file index.ejs không mà còn truyền tham số vào đó nữa, ở đây tham số là title có giá trị là Express. Các tham số này sẽ được dùng trong các file .ejs, file index.ejs lại được dựng từ 2 file là top.ejsbottom.ejs, trong đó chỉ có file top.ejs là sử dụng giá trị trong tham số title (ở dòng code <%= title %>).

Tương tự, khi chúng ta click vào đường link để được chuyển đến trang /fibonacci thì hàm index() trong file routes/fibonacci.js sẽ được gọi, bởi vì trong file app.js chúng ta đã chỉ định đường dẫn /fibonacci sẽ gọi hàm này bằng dòng app.use('/fibonacci', fibonacci.index), hàm res.render() sẽ trả về nội dung HTML trong file fibonacci.ejs cho người dùng, đi kèm là một số tham số là title, fibovalfibonum.

Trong file fibonacci.ejs chúng ta khai báo một form với một textbox và một nút Submit để nhập số thứ tự để tính toán giá trị Fibonacci, textbox ở đây được đặt tên là fibonum, khi người dùng click nút Submit thì trình duyệt của người dùng sẽ gửi một yêu cầu HTTP đến server với đường dẫn /fibonacci kèm theo tham số fibonum, trong file fibonacci.js chúng ta kiểm tra xem tham số đó có được truyền lên hay không, nếu có thì chúng ta tính toán và trả về trong tham số fiboval, nếu không thì trả về giá trị undefined, cũng trong file fibonacci.ejs chúng ta kiểm tra xem giá trị fiboval này có khác undefined hay không, nếu khác thì tức là có một giá trị đã được tính toán và chúng ta cho in ra kèm theo với form luôn.

Ban đầu bạn có thể thấy nó hơi rối rắm nhưng thật ra cũng không có gì khó, bạn chỉ cần ngồi ngẫm nghĩ một tí là sẽ hiểu rõ cách những file này hoạt động cùng nhau như thế nào thôi 🙂

NodeJS – Web Framework Express

Như trong bài trước đã nói, đối tượng http.Server cung cấp các hàm cấp thấp cho phép chúng ta làm việc sâu hơn với các thành phần của server như giao thức, gói tin… tức là chúng ta có thể viết server theo ý của chúng ta một cách linh hoạt hơn, tuy nhiên trên thực tế thì khi làm một ứng dụng web chúng ta chỉ nên chỉ nên chú trọng vào các chức năng của web chứ không nên đi sâu vào phần kỹ thuật làm gì.

Do đó cộng đồng Node đã phát triển một số module có chức năng đảm nhiệm các thành phần đó cho chúng ta rồi, bạn có thể xem danh sách ở đây: https://github.com/nodejs/node/wiki/modules#web-frameworks

Các module được phân loại thành các chức năng chính sau đây:

  • Router: có chức năng điều hướng các yêu cầu HTTP đến các hàm xử lý tương ứng. Đây cũng là tính năng có trong các web framework phổ biến khác như Ruby on Rails, Django, Sinatra…
  • Static file servers: có các hàm trả về các file tài nguyên cho client
  • Framework: hỗ trợ phát triển ứng dụng, hầu hết các framework này đều được phát triển dựa trên các framework phổ biến khác như Django, Rails, WebMachine, CakePHP… trong đó Express là web framework phổ biến nhất của Node.
  • Middleware: các module loại này hoạt động giữa phần nhận gói tin HTTP và phần chuyển gói tin đó đến các hàm xử lý.

Express

Trang chủ của module này có địa chỉ là: http://expressjs.com/

Để có thể sử dụng Express thì trước tiên chúng ta phải cài đặt, bạn mở terminal (cmd trong Windows) lên và gõ 2 dòng lệnh sau:

C:\Users\PhoCode>npm install -g express
...
C:\Users\PhoCode>npm install -g express-generator

Hai dòng lệnh trên cài module expressexpress-generator, trong đó express là web framework chính, còn express-generator là module giúp tạo một project Express mẫu, tức là nó tự tạo cho chúng ta các file và thư mục cần thiết.

C:\Users\PhoCode>express --version
4.13.4

Chúng ta có thể xem phiên bản express mới vừa được cài bằng cách chạy câu lệnh express --version. Hiện phiên bản express mà mình dùng để viết các bài hướng dẫn này là phiên bản 4.13.4.

Tiếp theo chúng ta sẽ tạo một project Express bằng câu lệnh:

C:\User\PhoCode>express --ejs FirstProject
    
    create : FirstProject
    create : FirstProject/package.json
    create : FirstProject/app.js
    create : FirstProject/public
    create : FirstProject/routes
    create : FirstProject/routes/index.js
    create : FirstProject/routes/users/js
    create : FirstProject/public/stylesheets
    create : FirstProject/public/stylesheets/style.css
    create : FirstProject/views
    create : FirstProject/views/index.ejs
    create : FirstProject/views/error.ejs
    create : FirstProject/public/images
    create : FirstProject/public/javascripts
    create : FirstProject/bin
    create : FirstProject/bin/www

    install dependencies:
      > cd FirstProject && npm install
    
    run the app:
      > SET DEBUG=FirstProject:* & npm start

Câu lệnh express --ejs FirstProject sẽ tạo một project có tên là FirstProject, bao gồm một thư mục có tên FirstProject với các file và thư mục như trên.

Một project Express sử dụng một số module ngoài nữa nên chúng ta phải cài thêm một số module vào thư mục này bằng câu lệnh:

C:\Users\PhoCode>cd FirstProject && npm install
...

Câu lệnh trên bao gồm 2 câu lệnh là cd FirstProjectnpm install, câu lệnh cd chỉ là chuyển thư mục hiện tại vào thư mục FirstProject, còn câu lệnh npm install sẽ đọc danh sách các module được sử dụng trong file package.json rồi tạo thư mục node_modules và cài đặt các module đó vào thư mục này.

Sau đó để chạy project này thì chúng ta chạy lệnh sau:

C:\Users\PhoCode\FirstProject>SET DEBUG=FirstProject:* & npm start

> FirstProject@0.0.0 start C:\Users\PhoCode\FirstProject
> node ./bin/www

 FirstProject:server Listening on port 3000 + 0ms

Trong đó câu lệnh SET DEBUG=FirstProject:* cho phép chúng ta xem các hoạt động đang xảy ra trên server, còn npm start sẽ chạy server. Mặc định server sẽ chạy trên cổng 3000. Chúng ta có thể vào trình duyệt và gõ localhost:3000 hoặc 127.0.0.1:3000 để xem kết quả.

Đây chỉ là một project trống không với một vài câu thông báo cơ bản.

capture

Các câu lệnh npm install hay SET DEBUG... có thể khác tùy thuộc vào phiên bản Express khác nhau. Tuy nhiên các câu lệnh này đều có hướng dẫn sau khi bạn chạy lệnh express --ejs để tạo project, bạn xem lại sau khi chạy lệnh tạo project thì màn hình có in ra một số câu thông báo, trong đó các dòng thông báo cuối cùng là các dòng hướng dẫn bạn cài các module và chạy server (phần install dependenciesrun the app).

File app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
        message: err.message,
        error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
       message: err.message,
       error: {}
    });
});


module.exports = app;

File app.js là file chính, có thể coi như là file main trong C++, Java… vậy, file này được tạo ra ở thư mục gốc của project. Chúng ta sẽ tìm hiểu một số câu lệnh trong file này.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');

Như đã nói ở trên, một project tạo từ Express có sử dụng một số module ngoài nữa như serve-favicon, morgan, cookie-parser... mà những module này không được tự động cài khi chúng ta cài Express, do đó chúng ta phải chạy lệnh npm install để npm cài các module còn thiếu. Ngoài ra Express còn tạo 2 module riêng là routes/indexroutes/users, 2 module này có nhiệm vụ xử lý các yêu cầu HTTP tương ứng.

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

Phương thức app.set() thiết lập các thông số cho project. Các dòng trên thiết lập đường dẫn đến các module View và engine được dụng để dựng trang web của project, chúng ta sẽ tìm hiểu về View sau.

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

Phương thức app.use() cấu hình các module middleware như favicon, logger, bodyParser...v.v

Ngoài ra phương thức app.use() còn được dùng để chỉ định hàm xử lý cho từng yêu cầu HTTP nữa. Trong đoạn code trên URL '/' sẽ được chuyển đến module routes, còn '/users' sẽ được chuyển đến module users.

Chúng ta sẽ tìm hiểu kỹ hơn trong các bài sau.

NodeJS – HTTP Server

Đối tượng HTTP Server là đối tượng chính trong một ứng dụng Node, mục đích chính của đối tượng này là lắng nghe và giao tiếp với các kết nối HTTP. Mặc dù sau này hầu hết (hoặc chắc chắn) chúng ta sẽ sử dụng một web framework khác là Express để phát triển ứng dụng vì framework này đã đơn giản hóa mọi thứ, chúng ta chỉ cần quan tâm đến việc phát triển các tính năng chứ không cần quan tâm đến mấy thứ cấp dưới như giao thức, gói tin…v.v. nhưng vì Express cũng được xây dựng dựa trên đối tượng HTTP Server nên ít nhất chúng ta cũng nên tìm hiểu qua.

Trong bài cài đặt Node, chúng ta đã viết thử một web server đơn giản như sau:

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type' : 'text/plain'});
    res.end('Hello, World!\n');
}).listen(8124, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8124');

Hàm http.createServer() sẽ tạo một đối tượng http.Server, và bởi vì đối tượng này kế thừa từ lớp EventEmitter nên chúng ta có thể viết lại đoạn code trên cho dễ nhìn hơn như sau:

var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
    res.writeHead(200, {'Content-Type' : 'text/plain'});
    res.end('Hello, World!\n');
});
server.listen(8124, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8124');

Khi sự kiện request xảy ra (tức là khi có người truy cập vào website), hàm function(req, res) {...} sẽ được gọi, hàm này có 2 tham số là reqres, trong đó req là một đối tượng lớp http.IncomingMessage, còn res là đối tượng lớp http.ServerResponse. Đối tượng req chứa thông tin của gói tin được gửi lên, đối tượng res sẽ chứa những dữ liệu trả về cho trình duyệt. Hàm listen() sẽ chạy server và lắng nghe trên cổng do chúng ta chỉ định, ở đây là cổng 8124.

Chúng ta sẽ sửa lại đoạn code trên một tí để server trả về một số thông tin khác thay vì chỉ in ra dòng chữ Hello, World như trên:

var http = require('http');
var util = require('util');
var url  = require('url');
var os   = require('os');
var server = http.createServer();
server.on('request', function(req, res) {
    var reqUrl = url.parse(req.url, true);
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var content = "";
 
    if(reqUrl.pathname === '/') {
        content += "<html>";
        content += "<head>";
        content += "<title>Hello, World!</title>";
        content += "</head>";
        content += "<body>";
        content += "<p><a href='/osinfo'>OS Info</a></p>";
        content += "</body>";
        content += "</html>";
    } else if (reqUrl.pathname === '/osinfo') {
        content += "<html>";
        content += "<head>";
        content += "<title>Operating System Info</title>";
        content += "</head>";
        content += "<body>";
        content += "<h1>Operating System Info</h1>";
        content += "<table>";
        content += "<tr><th>TMP Directory</th><td>" + os.tmpDir() + "</td></tr>";
        content += "<tr><th>Host Name</th><td>" + os.hostname() + "</td></tr>";
        content += "<tr><th>OS Type</th><td>" + os.type() + " " + os.platform() + " " + os.arch() + "</td></tr>";
        content += "<tr><th>Memory</th><td>total: " + os.totalmem() + ", free: " + os.freemem() + "</td></tr>"; 
        content += "</table>";
        content += "</body>";
        content += "</html>";
    }
 
    res.end(content);
 
});
server.listen(8124);
console.log('Listening to http://127.0.0.1:8124');

Nếu bạn đã từng làm việc với PHP thì đoạn code trên làm một số công việc tương tự như hàm sysinfo() trong PHP, module os có chức năng cung cấp các thông tin về server, nếu muốn bạn có thể tìm hiểu thêm về module này trên mạng.

Điều quan trọng hơn cần quan tâm trong đoạn code trên là phần routing, tức là phần xử lý từng đường dẫn url riêng biệt. Khi chúng ta gõ 127.0.0.1:8124 lên trình duyệt thì trình duyệt sẽ gửi một gói tin HTTP đến server và server sẽ truyền gói tin đó vào tham số req trong hàm xử lý, tham số này chứa một số thông tin có thể dùng cho việc routing là request.urlrequest.method.

Ở đoan code trên chúng ta dùng trường request.url để routing, đây là một chuỗi chứa đường dẫn phía sau địa chỉ server, ví dụ 127.0.0.1:8124 thì request.url chỉ là '/', nếu 127.0.0.1:8124/osinfo thì request.url'/osinfo'. Ngoài ra chúng ta không dùng tham số này một cách bình thường mà thay vào đó chúng ta tạo một đối tượng lớp URL bằng phương thức url.parse(), sau đó mỗi lần kiểm tra đường dẫn để trả về nội dung thì chúng ta xem trường url.pathname.

Ngoài url ra thì có một tham số chúng ta cũng hay quan tâm tới là request.method, nếu bạn đã từng làm web trước đây thì bạn sẽ biết là một yêu cầu gửi lên server thường có các kiểu method như GET, POST, PUT... ở đây chúng ta chưa đụng tới và sẽ tìm hiểu trong các bài sau.

Ngoài ra cách kiểm tra đường dẫn bằng cách so sánh chuỗi trong đoạn code trên thường không hiệu quả với các ứng dụng lớn, vì các ứng dụng lớn thường có đường dẫn rất phức tạp và có cả tham số nên thường chúng ta sẽ sử dụng các phương pháp khớp mẫu chuỗi để routing, chúng ta sẽ tìm hiểu thêm trong các bài sau.

Chạy đoạn code trên chúng ta có kết quả tương tự như sau:

capture

NodeJS – EventEmitter

EventEmitter là một lớp trong Node, lớp này có chức năng chính là phát sinh sự kiện, vì Node chạy theo hướng lập trình sự kiện nên lớp này là một trong số những lớp cốt lõi của Node, cũng vì thế nên dù EventEmitter không liên quan gì tới web nhưng đóng một vai trò rất quan trọng.

Ví dụ

Chúng ta tạo một file có tên pulser.js với nội dung như sau:

var events = require('events');
var util = require('util');

function Pulser() {
    events.EventEmitter.call(this);
}

util.inherits(Pulser, events.EventEmitter);

Pulser.prototype.start = function() {
    var self = this;
    this.id = setInterval(function() {
    self.emit('pulse');
    }, 1000);
}

var pulser = new Pulser();
pulser.on('pulse', function() {
    console.log('pulse received');
});
pulser.start();

Trong đoạn code trên chúng ta định nghĩa lớp Pulser kế thừa từ lớp EventEmitter.

var events = require('events');
var util = require('util');

Đầu tiên chúng ta cần dùng 2 gói là eventsutil.

function Pulser() {
    events.EventEmitter.call(this);
}
util.inherits(Pulser, events.EventEmitter);

Tiếp theo đoạn code trên chúng ta định nghĩa lớp Pulser bằng hàm khởi tạo Pulser(), dòng events.EventEmitter.call(this)util.inherits(...) có tác dụng sao chép toàn bộ những gì có trong lớp EventEmitter vào lớp Pulser, hay nói cách khác là ở đây chúng ta tiến hành cho lớp Pulser kế thừa từ lớp EventEmitter.

Pulser.prototype.start = function() {
    ...
    self.emit('pulse');
    ...
}

Tiếp theo chúng ta định nghĩa hàm start() cho đối tượng prototype của lớp Pulser, hàm này về cơ bản thì cứ sau 1 giây sẽ phát đi một sự kiện bằng phương thức emit().

Phương thức emit() là phương thức của lớp EventEmitter nhưng chúng ta đã cho kế thừa trong lớp Pulser nên có thể gọi từ lớp Pulser. Phương thức emit() sẽ làm công việc phát sinh một sự kiện để các đối tượng khác có thể lắng nghe và “bắt” sự kiện này, tham số đầu vào của phương thức emit() gồm có 1 chuỗi là tên sự kiện dùng để phân biệt các sự kiện, sau đó là danh sách các tham số, ở đây chúng ta không đưa vào tham số nào, nhưng giả sử nếu muốn bạn có thể truyền bất cứ thứ gì cũng được, ví dụ emit('pulse', 1, 'hello world', 2.7465).

var pulser = new Pulser();
pulser.on('pulse', function() {
    console.log('pulse received');
});
pulser.start();

Tiếp theo chúng ta tạo một đối tượng lớp Pulser(), sau đó chúng ta cho đối tượng này “lắng nghe” sự kiện pulse bằng phương thức on(), phương thức này nhận vào tên sự kiện và hàm sẽ xử lý sự kiện đó, ở đây sự kiện phát đi không mang theo tham số nào, nhưng giả sử như nếu có thì nếu muốn bắt các tham số đó, chúng ta chỉ cần khai báo trong hàm xử lý là được, ví dụ:

pulser.on('pulse', function(arg1, arg2, someArg) {
    console.log('pulse received', arg1, arg2, someArg);
});

Chạy đoạn code trên chúng ta được kết quả như sau:

C:\Users\PhoCode>node pulser.js
pulse received
pulse received
pulse received
pulse received
pulse received
pulse received
pulse received
...

Bạn có thể phát ra bao nhiêu sự kiện cũng được, và mỗi sự kiện có thể có bao nhiêu phương thức lắng nghe cũng được.

NodeJS – Hệ thống quản lý gói npm

Như trong bài hướng dẫn cài đặt, chúng ta đã biết npm là một phần mềm quản lý và phân phối gói dành cho Node, nếu bạn có sử dụng các hệ điều hành Linux thì npm giống mấy thứ như apt-get, rpm, yum, MacPorts...v.v vậy. Mục đích của npm là phân phối các gói Node trên mạng Internet cho tất cả người dùng chỉ với vài dòng lệnh, bạn có thể tìm các gói, tải về và cài đặt rất nhanh chóng.

Bản thân npm cũng là một gói của Node và được viết theo quy tắc chuẩn của CommonJS.

Định dạng của một gói

Gói npm là một thư mục trong đó chứa một file có tên là package.json, file này lưu các thông tin mô tả về gói. Các thông tin này được ghi theo định dạng chuẩn CommonJS Package 1.0. Nếu bạn muốn đi sâu vào tìm hiểu về file package.json của gói npm thì có thể gõ lệnh sau:

C:\Users\PhoCode>npm help json

Thông thường một file package.json có nội dung như sau:

{ 
    name: "packageName",         // tên gói
    version: "1.0",              // số phiên bản
    main: "mainModuleName",      // tên module chính
    modules: {                   // danh sách các module đi kèm
        "mod1" : "lib/mod1",     
        "mod2" : "lib/mod2"
    }
}

Đoạn code trên viết bằng định dạng JSON nên sẽ không có gì lạ nếu bạn đã từng làm việc với JavaScript.

Trong đó có 2 trường quan trọng là nameversion. Nếu bạn có ý định viết một gói và đăng lên hệ thống npm thì trước tiên bạn nên xem tên gói mà mình định đặt có bị trùng hay chưa bằng cách tìm trên http://search.npmjs.org hoặc dùng lệnh tìm kiếm sau:

C:\Users\PhoCode>npm search <tên gói>

Tên module chính trong trường main chỉ định module sẽ được chạy đầu tiên khi chúng ta gọi hàm require('packageName'). Một gói cũng có thể chứa nhiều module khác và được liệt kê trong trường modules.

Một gói cũng có thể sử dụng các gói khác, các gói này được ghi trong trường dependencies, ví dụ:

"dependencies" : {
    "foo" : "1.0.0 - 2.x.x",
    "bar" : ">=1.0.2 < 2.1.2"
}

Ngoài ra còn có trường description (mô tả) và trường keyword (từ khóa) có tác dụng hỗ trợ người khác tìm gói của chúng ta trên hệ thống npm dễ dàng hơn. Trường homepage ghi địa chỉ website của người phát triển gói (hoặc bất cứ địa chỉ nào mà người đó muốn), trường author ghi thông tin của người phát triển, ví dụ:

"description" : "Package nay duoc phat trien boi Pho Code",
"homepage" : "https://phocode.com",
"author" : phocode7@gmail.com

Trường directories lưu danh sách thư mục của gói, ví dụ:

directories : { lib: './lib', bin: './bin' },

Trường scripts lưu danh sách các lệnh của gói, ví dụ install, activate, uninstall, update... Ví dụ chúng ta xem danh sách các lệnh của gói npm bằng lệnh sau:

C:\Users\PhoCode>npm help scripts

Lệnh trên sẽ mở website tài liệu của gói npm.

Tìm một gói

Mặc định khi cài thì các gói sẽ được tải về từ địa chỉ http://npmjs.org. Để cài thì bạn chỉ cần gõ lệnh:

C:\Users\PhoCode>npm install <tên gói>

Nếu bạn không biết tên gói thì có thể tìm trên 2 website là http://npmjs.org hoặc http://search.npmjs.org.

Ngoài ra bạn có thể dùng lệnh search của npm để tìm nữa, ví dụ giả sử chúng ta tìm các gói có liên quan đến từ khóa mp3 thì gõ lệnh sau:

C:\Users\PhoCode>npm search mp3
mediatags Tools extracting for media meta-data tags =coolaj16 uttil m4a aac mp3 id3 jpeg exiv xmp
node3p    An Amazon MP3 downloader for NodeJS       =ncb000gt

Chúng ta tìm được 2 gói là mediatags  và node3p, nếu muốn cài mediatags thì chỉ cần dùng lệnh install:

C:\Users\PhoCode>npm install mediatags

Sau khi cài gói nếu muốn xem tài liệu về gói đó thì chúng ta có thể dùng lệnh view:

C:\Users\PhoCode>npm view zombie

Một số lệnh trong npm

Tiếp theo chúng ta sẽ tìm hiểu một số lệnh như làm thế nào để tải gói về, sử dụng hoặc xóa một gói.

Xem hướng dẫn sử dụng

Lệnh help trong npm cho biết tất cả mọi thứ về gói/lệnh/module cụ thể, ví dụ:

C:\Users\PhoCode>npm help npm

Dòng trên sẽ mở website để xem hướng dẫn về gói npm.

Xem thông tin gói

Lệnh view sẽ in nội dung của file package.json, nếu nội dung quá dài thì chúng ta có thể yêu cầu chỉ hiển thị một trường cụ thể nào đó trong file này. Ví dụ:

C:\Users\PhoCode>npm view google-openid dependencies
{ express: '>= 0.0.1', openid: '>= 0.1.1 <= 0.1.1' }

Lệnh trên sẽ in nội dung trường dependencies trong file package.json của gói google-openid.

Cài một gói

Đơn giản là chỉ cần dùng lệnh npm install với tên gói là được:

C:\Users\PhoCode>npm install openid
openid@2.0.4 node_modules/openid
...

Lưu ý là các gói sẽ được tải về trong thư mục node_modules trong thư mục hiện tại của terminal, tức là đoạn lệnh trên sẽ tải gói openid về thư mục C:\Users\PhoCode\node_modules. Các gói được cài như vậy sẽ chỉ có thể đọc được từ các ứng dụng nằm cùng thư mục hoặc các ứng dụng nằm trong thư mục con.

Nếu muốn ứng dụng nào cũng có thể đọc được thì chúng ta cài gói đó vào thư mục cài đặt Node bằng cách thêm tùy chọn -g vào sau lệnh install:

C:\Users\PhoCode>npm install -g openid

Liệt kê các gói đã được cài đặt

Lệnh npm list sẽ liệt kê danh sách các gói đã được cài đặt trong thư mục hiện tại của terminal, danh sách được hiển thị theo dạng cây, bao gồm cả các module con có trong từng gói. Ví dụ:

C:\Users\PhoCode>npm list
├─┬ mediatags@0.1.0
│ ├─┬ futures@2.3.3
│ │ ├── asyncify@2.1.2
│ │ ├── chainify@2.1.2
│ │ ├── events.node@0.4.9
│ │ ├── forEachAsync@2.2.1
│ │ ├── future@2.3.1
│ │ ├── join@2.3.2
│ │ ├── loop@2.1.2
│ │ └── sequence@2.2.1
│ └─┬ walk@2.3.9
│ └── foreachasync@3.0.0
├─┬ openid@2.0.4
│ └─┬ request@2.74.0
│ ├── aws-sign2@0.6.0
│ ├── aws4@1.4.1
│ ├─┬ bl@1.1.2
│ │ └─┬ readable-stream@2.0.6
│ │ ├── core-util-is@1.0.2
│ │ ├── inherits@2.0.3
│ │ ├── isarray@1.0.0
│ │ ├── process-nextick-args@1.0.7
│ │ ├── string_decoder@0.10.31
│ │ └── util-deprecate@1.0.2
│ ├── caseless@0.11.0
│ ├─┬ combined-stream@1.0.5
...

Nếu không muốn xem theo dạng cây thì chúng ta có thể xem theo dạng đường dẫn thư mục bằng cách thiết lập tham số parseable như sau:

C:\Users\PhoCode>npm set parseable=true
C:\Users\PhoCode>npm list
C:\Users\PhoCode\node_modules\mediatags
C:\Users\PhoCode\node_modules\mediatags\node_modules\futures
C:\Users\PhoCode\node_modules\mediatags\node_modules\futures\node_modules\asyncify
C:\Users\PhoCode\node_modules\mediatags\node_modules\futures\node_modules\chainify
C:\Users\PhoCode\node_modules\mediatags\node_modules\futures\node_modules\events.node
C:\Users\PhoCode\node_modules\mediatags\node_modules\futures\node_modules\forEachAsync
C:\Users\PhoCode\node_modules\mediatags\node_modules\futures\node_modules\future
...

Cập nhật phiên bản mới cho các gói

Để xem danh sách các gói đã cũ (hay trên mạng đã có phiên bản mới) chúng ta dùng lệnh outdated:

C:\Users\PhoCode>npm outdated
less@1.3.3 node_modules/less current=1.3.1
gdata-js@2.0.1 node_modules/gdata-js current=0.0.4
consolidate@0.7.0 node_modules/consolidate current=0.5.0

Lệnh outdated hiển thị danh sách các gói đã cũ bao gồm số phiên bản đang cài và số phiên bản mới nhất. Để cập nhật một gói thì chúng ta dùng lệnh update, ví dụ:

C:\Users\PhoCode>npm update express
connect@1.4.1 ./node_modules/express/node_modules/connect
mime@1.2.2 ./node_modules/express/node_modules/mime
qs@0.1.0 ./node_modules/express/node_modules/qs
express@2.3.6 ./node_modules/express

Xóa một gói

Nếu bạn đang dùng thử một gói mà thấy nó “cùi bắp” quá thì có thể xóa đi bằng lệnh uninstall, ví dụ:

C:\Users\PhoCode>npm uninstall openid
unbuild openid@2.0.4

NodeJS – Module

Module là các thành phần cơ bản để xây dựng nên một ứng dụng Node. Một module chứa các hàm API, tức là chúng ta chỉ biết chính xác hàm đó có tên gì, có tham số gì, dữ liệu trả về là gì chứ không biết chi tiết chúng hoạt động như thế nào.

Trong các bài trước chúng ta đã làm việc với module, bản thân mỗi file JavaScript đã là một module rồi. Trong phần này chúng ta sẽ tìm hiểu sâu hơn.

Định nghĩa một module

Trong bài trước chúng ta có viết một file có tên ls.js, trong file đó có dòng code như sau:

var fs = require('fs');

Trong dòng code trên chúng ta dùng hàm require(), hàm này có chức năng tìm module fs và chép vào trình thông dịch, và chúng ta có thể sử dụng các hàm có trong module đó. Đối tượng fs (biến var fs) chứa code được định nghĩa trong module fs.

Tiếp theo chúng ta xem xét ví dụ sau:

var count = 0;
exports.next = function() {
    return count++;
}

Chúng ta tạo một file có tên simple.js với đoạn code trên, sau đó mở terminal lên và gõ lệnh node, chỉ gõ node để mở trình thông dịch Node lên thôi chứ không chạy file simple.js. Lưu ý nếu thư mục hiện tại trên terminal không phải là thư mục chứa file simple.js thì bạn phải chuyển đường dẫn đến thư mục đó (bằng lệnh cd trên cả Windows và Linux, nếu không biết bạn có thể tìm trên Google cách dùng). Sau đó chúng ta chạy một số lệnh như sau:

C:\Users\PhoCode>node
> var s = require('./simple');
> s.next();
0
> s.next();
1
> s.next();
2
> s.next();
3
>

Trong file simple.js, đối tượng exports được trả về từ hàm require('./simple'). Cũng có nghĩa là cứ mỗi lần chúng ta gọi s.next() thì hàm next() được định nghĩa trong file simple.js sẽ được gọi, ở đây hàm next() trả về giá trị của biến count và tăng biến đó lên 1 đơn vị, do đó các lần gọi tiếp theo giá trị sẽ tăng dần.

Quy tắc ở đây là bất cứ thứ gì từ hàm cho tới đối tượng nào mà được gán vào làm một trường của đối tượng exports thì đều có thể gọi được ở ngoài, nếu không phải là một trường của đối tượng exports thì sẽ không thể gọi tới được, cho dù có require() hay không.

Lưu ý là các đối tượng global được định nghĩa trong một file .js cũng chỉ có thể nhìn thấy ở trong file đó thôi, chứ không thể gọi từ file khác được, nếu muốn gọi từ nơi khác thì chúng ta cũng phải gán vào đối tượng exports.

Các module trong Node

Các module có trong Node thường được viết theo cách viết của module CommonJS (chúng ta sẽ tìm hiểu về CommonJS sau) nhưng không giống hoàn toàn. Trong Node, mỗi module được lưu trong một file, cách lưu các file này cũng rất linh hoạt.

Tên một module chính là tên file .js nhưng không có phần đuôi, tức là chúng ta viết require('./simple') thì Node sẽ tự hiểu là tìm file simple.js.

Ngoài các module là code thì Node cũng hỗ trợ các thư viện đã được dịch ra thành file nhị phân, ở đây các file này có đuôi là .node. Tuy nhiên chúng ta sẽ không đi sâu vào tìm hiểu các module kiểu này, bạn chỉ cần nhớ là các file có đuôi .node là các file module bình thường như các file .js thôi, có điều là chúng đã được dịch thành mã nhị phân.

Ngoài ra còn có các module cốt lõi đã được biên dịch thành file khả thi (có đuôi .exe trong Windows).

Đường dẫn để tìm module của Node có 3 loại là đường dẫn tương đối, đường dẫn tuyệt đối hoặc top-level.

Ví dụ:

  • Đường dẫn tương đối : ./simple.js, ../simple.js
  • Đường dẫn tuyệt đối : C:/simple.js
  • Đường dẫn top-level: simple.js (module nằm chung thư mục)

Module nằm trong một app

Thường thì các module sẽ nằm trong một ứng dụng nào đó hoặc không.

Một ứng dụng thường là một thư mục chứa các file và thư mục con trong đó, các thư mục con này có thể chứa file code, file tài nguyên, hoặc chứa các module khác. Các module biết chúng được đặt ở đâu và khi cần thì có thể gọi đến nhau một cách dễ dàng.

Ví dụ dưới đây là thư mục của một gói rất phổ biến trong Node là gói Express, gói này chứa các file và thư mục con bên trong nó theo cấu trúc cây.

express/
    bin/
    History.md
    index.js
    lib/
        express.js
        http.js
        https.js
        request.js
        response.js
        /router
            collection.js
            index.js
            methods.js
            route.js
        utils.js
        view/
            partial.js
            view.js
        view.js
    LICENSE
    Makefile
    node_modules\
        qs\
    package.json
    Readme.md

Trong gói Express thì module thường được sử dụng nhiều nhất là utils.js. Tùy vào vị trí được đặt ở đâu mà các module khác có câu lệnh gọi khác nhau, ví dụ:

var utils = requrire('./lib/utils');
var utils = requrire('./utils');
var utils = requrire('../utils');

Cách tìm module của Node

Ngoài thư mục của ứng dụng thì các module thường còn được đặt trong thư mục node_modules, khi gọi thì chúng ta chỉ cần dùng tên module là đủ (tức là đường dẫn kiểu top-level), ví dụ:

var express = require('express');

Node sẽ tự động tìm module trong các thư mục node_modules, thư mục này có rất nhiều, Node sẽ tìm thư mục node_modules trong thư mục của file gọi đến module đó, nếu không thấy thì tiếp tục tiềm trong thư mục cha của thư mục chứa file gọi đến module đó và nếu không tìm thấy nữa thì Node sẽ tiếp tục đi dần dần lên các thư mục cha tiếp theo cho đến chạm đến thư mục gốc thì thôi.

Trong ví dụ trước, gói Express có một thư mục tên là node_modules, trong thư mục này chứa một thư mục khác có tên là qs, tức là module qs và module này có thể được gọi tới bởi bất kì file nào trong thư mục express.

var qs = require('qs');

Như vậy tóm lại cách dễ nhất để gọi các module là tạo thư mục node_modules và đặt chúng ta trong thư mục này, thư mục node_modules nên được đặt ở vị trí cao nhất để các module khác có thể thấy được. Ví dụ:

projects/
    drawapp/
        index.js
        lib/
            draw.js
            node_modules/
            svg.js
        node_modules/
            express/
            bin/
            History.md
            index.js
            lib/
            LICENSE
            Makefile
            node_modules/
                qs/
            package.json
            Readme.md
        package.json
    node_modules/

 

Trong ví dụ trên, những module nằm trong thư mục drawapp có thể thấy được các module nằm trong thư mục projects/node_modules, và ngược lại các module trong thư mục node_modules này không thể thấy được module qsqs nằm ở trong cùng trong khi Node chỉ tìm các module bằng cách đi ngược về các thư mục cha.

Tương tự, những module nằm trong thư mục lib/node_modules có thể thấy được từ draw.jssvg.js nhưng không thấy được từ index.js.

Lý do tại sao lại có thể có nhiều thư mục node_modules là vì làm như thế thì Node cho phép chúng ta có thể dùng một module với nhiều phiên bản khác nhau thay vì chỉ dùng một thư mục duy nhất rồi chép tất cả vào gây lộn xộn.

Tìm trong biến NODE_PATH

Ngoài các thư mục node_modules thì Node còn cho phép chúng ta đặt module trong các thư mục được định nghĩa trong biến môi trường NODE_PATH, biến này lưu danh sách các đường dẫn thư mục mà Node có thể tìm trong đó, mỗi thư mục cách nhau bằng dấu chấm phẩy (trong Windows) hoặc dấu 2 chấm (trong Linux).

NodeJS – Sử dụng Node

Trong phần này chúng ta sẽ tìm hiểu cách sử dụng Node.

Chạy file mã nguồn Node

Bạn có thể mở terminal lên và gõ lệnh node --help để xem hướng dẫn sử dụng Node (bằng tiếng Anh).

C:\User\PhoCode>node --help
Usage: node [options] [ -e script | script.js ] [arguments]
...

Ngay dòng đầu tiên là cách để chạy một file node, chỉ đơn giản là ghi lệnh node <tên file> ra là được, nếu muốn có thể thêm một số tùy chọn (options) và tham số đi kèm (arguments).

Bạn có thể dùng bất cứ trình editor nào để viết code đều được. Dĩ nhiên là chúng ta nên dùng những editor có chức năng hiển thị code (tức là có thể in màu những từ khóa như Notepad++…) để làm việc cho dễ.

Ví dụ:

var fs = require('fs');
var files = fs.readdirSync('.');
for (fn in files) {
    console.log(files[fn]);
}

Chúng ta tạo một file có tên ls.js với đoạn code như trên. File này mình đặt ở thư mục C:\, bạn có thể đặt ở đâu cũng được, tốt nhất là nên tạo thư mục riêng để đặt cho dễ quản lý.

Tiếp theo chúng ta chạy file đó bằng cách gõ lệnh:

C:>node ls.js
Intel
Logs
PerfLogs
Program Files
Program Files(x86)
Users
Windows

Đoạn code trên làm công việc liệt kê danh sách các file và thư mục của thư mục hiện tại, tức là thư mục chứa file ls.js, nếu bạn đã từng dùng các hệ điều hành Unix thì bạn cũng biết là lệnh ls là lệnh có chức năng tương tự có trong các hệ điều hành đó cho nên mình mới đặt là ls.js.

Chúng ta sẽ tìm hiểu chi tiết chức năng của từng dòng code ở các bài sau. Bây giờ chúng ta sẽ tìm hiểu cách truyền tham số vào khi chạy. Đoạn code trên sửa lại như sau:

var fs = require('fs');
var dir = '.';
if (process.argv[2])
    dir = process.argv[2];
var files = fs.readdirSync(dir);
for (fn in files) {
    console.log(files[fn]);
}

Khi chạy chúng ta có thể thêm tham số là đường dẫn một thư mục bất kì vào sau tên file. Ví dụ:

C:>node ls.js C:\Windows
addins
appcompat
AppPatch
AppReadiness
assembly
...

Khi chạy, đường dẫn C:\Windows sẽ được truyền vào một biến mảng toàn cục có tên là process.argv, mảng này luôn luôn chứa ít nhất là 2 phần tử, phần tử đầu tiên là đường dẫn đến file node.exe trong thư mục cài đặt Node, phần tử thứ 2 là đường dẫn đến file sẽ được dịch để chạy, ở đây là file ls.js, và các phần tử tiếp theo nếu có, ở đây là C:\Windows.

Tạo web server

Vì Node được phát minh để làm web nên hầu hết chúng ta sẽ viết web server rất nhiều trong suốt series này. Ở đây chúng ta sẽ viết một đoạn code nhỏ để kiểm tra thử:

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type' : 'text/plain'});
    res.end('Hello, World\n');
}).listen(8124, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8124');

Chúng ta tạo một file có tên app.js và viết đoạn code trên vào. Sau đó chạy:

C:>node app.js
Server running at http://127.0.0.1:8124

Đây chỉ là một đoạn code đơn giản, chúng ta sẽ tìm hiểu thêm sau. Sau khi đã chạy thì chúng ta có thể mở trình duyệt lên và trỏ đến 127.0.0.1:8124 hoặc localhost:8124 để xem chuỗi trả về của server.

capture

Bạn cũng lưu ý là đoạn code trên sẽ chạy vô thời hạn cho đến khi chúng ta ngắt chương trình (như bấm Ctrl+C trong terminal) chứ không giống như các đoạn code trước là chạy xong thì kết thúc.

Phần mềm quản lý gói – npm

Bản thân Node không có gì đặc sắc, chỉ là một trình biên dịch JavaScript với một vài thư viện nhập xuất bất đồng bộ. Lý do Node phát triển mạnh là vì nó là mã nguồn mở, đặc trưng của mã nguồn mở là cộng đồng hỗ trợ rất lớn, có rất nhiều coder ngoài viết thư viện hỗ trợ cho Node. Bản thân Node có một phần mềm quản lý các gói thư viện này đó là npm. Chúng ta có thể tải các gói thư viện về và sử dụng ngay một cách dễ dàng với npm.

Ở các phiên bản cũ thì chúng ta phải cài npm riêng khi cài Node, còn đối với các phiên bản mới thì khi chúng ta cài Node thì npm cũng đã được cài sẵn rồi.

Chúng ta sẽ thử cài gói hexy với npm, đây là một công cụ cho phép xem địa chỉ bộ nhớ của từng byte trong một file dưới dạng số hệ 16. Để cài một gói thì chúng ta chạy lệnh npm install [tùy chọn] <tên gói>:

C:>npm install -g hexy
C:\Users\PhoCode\AppData\Roaming\npm\hexy -> C:\Users\PhoCode\AppData\Roaming\npm\node_modules\hexy\bin\hexy_cmd.js
hexy@0.2.7 C:\Users\Gigabyte\AppData\Roaming\npm\node_modules\hexy

Tùy chọn -g cho biết gói này được cài đặt toàn cục (globally), tức là ai cũng có thể dùng.

Sau đó chúng ta chạy thử gói này, ví dụ:

C:>hexy ls.js
00000000: 636f 6e73 6f6c 652e 6c6f 6728 7072 6f63 console.log(proc
00000010: 6573 732e 6172 6776 5b30 5d29 3b0d 0a63 ess.argv[0]);..c
00000020: 6f6e 736f 6c65 2e6c 6f67 2870 726f 6365 onsole.log(proce
00000030: 7373 2e61 7267 765b 315d 293b 0d0a 7661 ss.argv[1]);..va
00000040: 7220 6673 203d 2072 6571 7569 7265 2827 r.fs.=.require('
00000050: 6673 2729 3b0d 0a76 6172 2064 6972 203d fs');..var.dir.=
00000060: 2027 2e27 3b0d 0a69 6620 2870 726f 6365 .'.';..if.(proce
00000070: 7373 2e61 7267 765b 325d 290d 0a20 2020 ss.argv[2]).....
00000080: 2064 6972 203d 2070 726f 6365 7373 2e61 .dir.=.process.a
00000090: 7267 765b 325d 3b0d 0a76 6172 2066 696c rgv[2];..var.fil
000000a0: 6573 203d 2066 732e 7265 6164 6469 7253 es.=.fs.readdirS
000000b0: 796e 6328 6469 7229 3b0d 0a66 6f72 2028 ync(dir);..for.(
000000c0: 666e 2069 6e20 6669 6c65 7329 207b 0d0a fn.in.files).{..
000000d0: 2020 2020 2f2f 636f 6e73 6f6c 652e 6c6f ....//console.lo
000000e0: 6728 6669 6c65 735b 666e 5d29 3b0d 0a7d g(files[fn]);..}
000000f0: 0d0a ..

Nếu máy của bạn in ra mấy dòng tương tự như trên thì gói hexy đã được cài đặt thành công.

NodeJS – Cài đặt

Trước khi đi vào tìm hiểu các thành phần của Node thì chúng ta phải cài đặt Node trên máy đã.

Node có thể chạy trên hầu hết các hệ điều hành, từ Linux, MacOS X… cho đến Windows. Ngoài ra các máy tính nhỏ cũng có thể chạy được, như các CPU trên thiết bị di động ARM, Raspberry Pi…

Chúng ta có thể cài Node thông qua các hệ thống quản lý gói có sẵn trên các hệ điều hành UNIX (như apt-get trên Ubuntu, yum trên CentOS…), thông qua trình Installer trên Windows, hoặc build lại từ Source, mặc dù việc build từ source là khá khó và không nên dùng cách này vì phí thời gian.

Cài đặt trên các hệ điều hành UNIX

Đối với các hệ điều hành UNIX thì chúng ta sẽ sử dụng phần mềm quản lý gói có sẵn trong các hệ điều hành này. Việc dùng các chương trình này có một thuận lợi là chúng ta có thể cập nhật phiên bản mới nhất mọi lúc mọi nơi chỉ với một câu lệnh, ví dụ apt-get update. Và trước khi cài Node thì chúng ta cũng nên chạy câu lệnh này trước.

Sau đó để cài Node thì chúng ta gõ lệnh sau:

Với Debian:

$ apt-get install nodejs

Với Ubuntu:

$ sudo apt-get install python-software-properties
$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update
$ sudo apt-get install nodejs npm

Vậy là xong.

Cài đặt trên Windows

Để cài đặt trên Windows thì cách tốt nhất là chúng ta lên trang chủ của Node và tải trình Installer về cài đặt tại địa chỉ https://nodejs.org/en/download/

Sau đó chúng ta tiến hành cài đặt như bình thường. Lưu ý trong quá trình cài đặt chúng ta nên chọn để trình Installer gán địa chỉ thư mục vào biến môi trường PATH. Nếu không sau này khi code sẽ rất mệt.

capture

Kiểm tra phiên bản Node

Sau khi đã cài đặt thành công. Chúng ta có thể mở trình terminal (trong Windows là Command Prompt – cmd) lên và chạy lệnh node -v hoặc node --version để xem phiên bản Node trên máy cũng như kiểm tra xem Node đã được cài đặt thành công hay chưa.

C:\User\PhoCode>node -v
v4.5.0

NodeJS – Giới thiệu

 

Node là một nền tảng phát triển các ứng dụng web back-end được viết bằng JavaScript, tức là Node không được viết để chạy trên trình duyệt, nếu bạn đã từng học JavaScript thông thường thì bạn sẽ thấy Node chạy khác với những gì bạn đã học, bạn sẽ không thấy những thứ như document.getElementById()... mặc dù thực tế vẫn có một số thư viện hỗ trợ Node viết cho trình duyệt, tuy nhiên chúng ta sẽ không quan tâm đến chúng trong series này.

Ngoài việc chạy trên JavaScript thì Node có những tính năng đi kèm sau:

  • Có trình CLI (giao diện dòng lệnh)
  • Chạy theo mô hình REPL
  • Có các hàm quản lý tiến trình
  • Có các đối tượng hỗ trợ làm việc với dữ liệu nhị phân
  • Hỗ trợ TCP và UDP
  • Hỗ trợ hân giải DNS
  • Hỗ trợ HTTP và HTTPS
  • Có thể truy cập file và thư mục

Node cho phép bạn thực hiện các giao thức mạng ở cấp độ thấp một cách dễ dàng. Chẳng hạn như Node có module HTTP cho phép xây dựng một webserver chỉ với vài dòng code, tuy nhiên vì thế mà bạn sẽ phải học nhiều thứ hơn như học về các header của một gói tin HTTP, không như PHP vốn chỉ là một module mở rộng của một webserver có sẵn (như Apache hay NginX…) – tức là PHP dễ dùng hơn Node nhưng lại không cho phép coder thực hiện các công việc ở cấp độ thấp. Tuy nhiên vì NodeJS là một framework mã nguồn mở, do đó trên mạng cũng có một số thư viện hỗ trợ viết webserver nhanh hơn và dễ hơn cho coder.

Kiến trúc của Node

Node sử dụng kiển trúc lập trình hướng sự kiện không đồng bộ, và đây là một tính năng làm cho các ứng dụng Node có thể chạy với hiệu suất cao. Chẳng hạn như đối với các ứng dụng bình thường như một chương trình viết bằng C++ thì khi chúng ta viết chương trình để đọc dữ liệu, chương trình sẽ phải dừng lại ở đoạn code đọc dữ liệu để chờ cho đến khi có dữ liệu để đọc, nếu muốn chương trình tiếp tục vừa chạy các công việc khác vừa đọc dữ liệu thì phải dùng đến đa luồng (multi-threading), tuy nhiên việc dùng đa luồng rất phức tạp và có thể làm chậm server.

Node chỉ sử dụng một luồng duy nhất, các câu lệnh nhập xuất không cần phải chờ bằng cách sử dụng Event Loop, cứ mỗi lần có sự kiện xảy ra thì chuyển dữ liệu của sự kiện đó đến các hàm xử lý tương ứng, và trong khi các hàm xử lý đang chạy thì vòng lặp sự kiện vẫn tiếp tục nhận sự kiện và chuyển đến các hàm xử lý tương ứng khác.

Ví dụ giả sử chúng ta có dòng code lấy dữ liệu từ cơ sở dữ liệu như sau:

result = query('SELECT * from db');
// xử lý result

Đối với các chương trình bình thường thì khi chạy đến dòng code trên, luồng chạy chương trình đó sẽ phải dừng lại để đợi quá trình xử lý từ cơ sở dữ liệu thực hiện xong và trả về rồi mới tiếp tục được, trong quá trình đợi đó sẽ có nhiều yêu cầu khác xảy ra và hiệu suất phần mềm sẽ giảm do lãng phí tài nguyên, để giải quyết tình trạng đó thì chúng ta có thể dùng cơ chế đa luồng để xử lý, tuy nhiên đa luồng có một nhược điểm làm tiêu tốn nhiều bộ nhớ và CPU.

Thay vì dùng đa luồng thì Node sử dụng cơ chế Event Loop để giải quyết việc này, nói một cách đơn giản thì Node sẽ đưa các câu lệnh chờ trên vào một luồng khác là Event Loop để xử lý riêng, trong khi luồng chính vẫn sẽ chạy các công việc của riêng nó, và khi nào luồng chính “rảnh” rồi thì luồng Event Loop sẽ chuyển các công việc đã thực hiện xong trở về lại luồng chính. Và chính vì Node chỉ sử dụng 2 luồng nên tài nguyên hệ thống sẽ không bị chiếm nhiều như khi dùng cơ chế đa luồng, ngoài ra việc code sử dụng Event Loop đơn giản hơn nhiều, ví dụ:

query('SELECT * from db', function(err, result) {
    if (err) throw err;
    // xử lý result
});

Trong đoạn code trên, kết quả trả về từ hàm query() thay vì được gán vào một biến thì sẽ được truyền vào một hàm khác là function(err, result){...}, và hàm này sẽ được chuyển vào luồng Event Loop và chờ cho đến khi luồng chính “rảnh” thì mới được chuyển qua.

Tên gọi

Tên chính thức là Node.js, tuy nhiên chúng ta sẽ gọi tắt là Node cho đơn giản.