Bài viết được dịch từ trang web Tutsplus

Lập trình hướng sự kiện thường gây bối rối cho những người mới bắt đầu, điều đó có thể khiến cho người ta gặp khó khăn khi bắt đầu học Node.js. Nhưng đừng để cho điều đó làm bạn nản lòng; trong bài viết này, tôi sẽ dạy bạn một số kiến thức cơ bản của Node.js và giải thích tại sao nó đã trở nên nổi tiếng.

Node.js có tốc độ nhanh gấp nhiều lần so với PHP
Node.js có tốc độ nhanh gấp nhiều lần so với PHP

Khóa fullstack node.js 2018 tại Techmaster

Giới thiệu

Để bắt đầu sử dụng Node.js, đầu tiên bạn phải hiểu sự khác nhau giữa Node.js và các môi trường kịch bản máy chủ truyền thống (như: PHP, Python, Ruby, v.v...).

Lập trình bất đồng bộ (Asynchronous Programming)

Node.js sử dụng một kiến trúc module để đơn giản hóa việc tạo ra các ứng dụng phức tạp.

Rất có thể bạn đã quen thuộc với lập trình bất đồng bộ; sau tất cả nó là chữ "A" trong Ajax. Mỗi function trong Node.js là bất đồng bộ. Vì vậy, mọi thứ bình thường kẹt trong thread thì giờ đây được thực thi dưới background. Đây là điều quan trọng nhất bạn nên nhớ về Node.js. Ví dụ, nếu bạn đang đọc một file trên một hệ thống file, bạn phải xác định một hàm callback sẽ được thực thi khi hoạt động đọc file đó hoàn thành.

Bạn đang tự tay làm mọi thứ!

Node.js chỉ là một môi trường - nghĩa là bạn phải tự tay làm mọi thứ. Không có một server HTTP mặc định, hoặc bất kỳ server nào cho vấn đề đó. Điều này có thể gây bối rối cho những người mới học, nhưng bù lại chúng ta sẽ xây dựng được ứng dụng web có tốc độ thực thi cao. Một script quản lý tất cả truyền thông với các client. Điều này giúp giảm đáng kể số lượng tài nguyên sử dụng bởi ứng dụng đó. Ví dụ, đây là đoạn code cho một ứng dụng đơn giản bằng Node.js:

var i, a, b, c, max;

max = 1000000000;

var d = Date.now();

for (i = 0; i < max; i++) {
    a = 1234 + 5678 + i;
    b = 1234 * 5678 + i;
    c = 1234 / 2 + i;
}

console.log(Date.now() - d); 

Và đây là đoạn code tương đương được viết bằng PHP:

$a = null;
$b = null;
$c = null;
$i = null;
$max = 1000000000;
 
$start = microtime(true);
 
for ($i = 0; $i < $max; $i++) {
    $a = 1234 + 5678 + $i;
    $b = 1234 * 5678 + $i;
    $c = 1234 / 2 + $i;
}
 
var_dump(microtime(true) - $start);

Bây giờ hãy quan sát các số liệu benchmark. Bảng danh sách sau đây chứa thời gian response, đơn vị tính là millisecond, cho hai ứng dụng đơn giản ở trên:

Số lần lặp Node.js PHP
100 2.00 0.14
10'000 3.00 10.53
1'000'000 15.00 1119.24
10'000'000 143.00 10621.46
1'000'000'000 11118.00 1036272.19

 

Tôi đã chạy hai ứng dụng ở trên từ command line vì vậy sẽ không có server nào làm trì hoãn sự thực thi của các ứng dụng. Tôi chạy mỗi phép thử 10 lần và lấy kết quả trung bình. PHP nhanh hơn đáng kể với số lượng vòng lặp nhỏ hơn, nhưng lợi thế đó nhanh chóng biến mất khi số lượng vòng lặp tăng lên. Và khi tất cả các phép thử hoàn thành, kết quả là PHP thì 93% chậm hơn Node.js!

Node.js có tốc độ rất nhanh, nhưng bạn sẽ cần học một số thứ mới để có thể sử dụng nó đúng đắn.

Các Module

Node.js sử dụng một kiến trúc module để đơn giản hóa việc tạo ra các ứng dụng phức tạp. Các module hơi giống các thư viện trong ngôn ngữ C, hoặc các unit trong Pascal. Mỗi module chứa một tập các chức năng liên quan đến "chủ đề" của module đó. Ví dụ, module http chứa các function xác định liên quan đến HTTP. Node.js cung cấp một số module lõi để giúp bạn truy cập các file trên hệ thống file, tạo ra các server HTTP và TCP/UDP, và thực thi những chức năng hữu ích khác.

Việc gọi sử dụng một module rất dễ; bạn đơn giản chỉ cần gọi function require(), giống như thế này:

var http = require('http');

Node.js chỉ là một môi trường; bạn phải tự tay làm mọi thứ.

Function require() đó sẽ trả về tham chiếu đến module xác định. Trong trường hợp của đoạn code trên là một tham chiếu đến module http được lưu trữ trong biến http.

Trong đoạn code ở trên, chúng ta truyền vào tên của một module tới function require(). Điều này khiến cho Node sẽ tiến hành tìm kiếm folder có tên là node_modules nằm trong thư mục ứng dụng của bạn, và tìm kiếm module http nằm trong folder đó. Nếu Node không tìm thấy folder node_modules (hoặc module http trong đó), thì nó sẽ tìm kiếm thông qua module cache toàn cục. Bạn cũng có thể xác định một file thực sự bằng cách truyền một đường dẫn tương đối hoặc tuyệt đối, kiểu như thế này:

var myModule = require('./myModule.js');

Các module được đóng gói thành những phần code riêng biệt. Code trong một module hầu hết ở trạng thái private - nghĩa là các function và biến được định nghĩa trong chúng chỉ có thể được truy cập từ bên trong module đó mà thôi. Tuy nhiên bạn có thể phơi bày (expose) ra các function và/hoặc biến được sử dụng bên ngoài của module đó. Để làm được điều này, bạn sử dụng đối tượng exports và đưa ra các phương thức và thuộc tính của nó với đoạn code mà bạn muốn cung cấp ra ngoài. Bạn hãy xem module sau đây làm một ví dụ:

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};

Đoạn code trên tạo ra một biến PI mà chỉ có thể được truy cập bởi code trong module đó; nó không thể bị truy cập từ phía bên ngoài. Tiếp theo, hai function được tạo ra trên đối tượng exports. Những function này có thể truy cập từ bên ngoài của module đó bởi vì chúng được định nghĩa trên đối tượng exports. Kết quả là, PI hoàn toàn được bảo về khỏi sự can thiệp từ bên ngoài. Vì vậy, bạn có thể hoàn toàn yên tâm rằng các function area() và circumference() sẽ luôn luôn thực thi đúng đắn (miễn là một giá trị được cung cấp qua tham số r).

Phạm vi toàn cục (Global Scope)

Node là một môi trường JavaScript chạy trên engine Google's V8 JavaScript. Vì vậy, chúng ta nên tuân theo những chuẩn tốt nhất khi sử dụng phát triển trên client-side. Ví dụ, chúng ta nên tránh việc đặt bất cứ thứ gì vào trong một phạm vi toàn cục (global scope). Tuy nhiên, điều đó không phải khi nào cũng có thể làm được. Phạm vi toàn cục trong Node là GLOBAL (đối lập với window trong trình duyệt), và bạn có thể rất dễ tạo ra một biến toàn cục của function bởi việc quên mất từ khóa var, giống như thế này:

globalVariable = 1;
globalFunction = function () { ... };

Xin nhắc lại một lần nữa, phạm vi toàn cục (global) là nên tránh bất cứ khi nào có thể. Vì vậy hãy cẩn thận và nhớ sử dụng từ khóa var mỗi khi khai báo một biến nhé!

Cài đặt

Đương nhiên chúng ta cần cài đặt Node trước khi có thể viết và chạy một ứng dụng. Việc cài đặt rất đơn giản, nếu bạn sử dụng Windows hoặc OS X; chỉ việc lên trang web nodejs.org để tải bộ cài về cho những hệ điều hành này. Đối với Linux, sử dụng bất kỳ package manager nào. Hãy mở terminal của bạn lên và gõ:

sudo apt-get update
sudo apt-get install node

hoặc:

sudo aptitude update
sudo aptitude install node

Node.js nằm trong sid repositories; bạn có thể cần phải add chúng tới sources list của bạn:

sudo echo deb http://ftp.us.debian.org/debian/ sid main > /etc/apt/sources.list.d/sid.list

Nhưng nên biết rằng việc cài đặt sid packages trên những system cũ hơn có thể làm hỏng system của bạn. Hãy cẩn thận, và nhớ gỡ bỏ /etc/apt/sources.list.d/sid.list sau khi bạn kết thúc công việc cài đặt Node.

Cài đặt những module mới

Node.js có một trình quản lý gói (package manager), được gọi là Node Package Manager (NPM). Nó tự động được cài cùng Node.js, và bạn sử dụng NPM để cài đặt những module mới. Để cài đặt một module, mở terminal/ command line của bạn lên, điều hướng tới folder mong muốn và chạy lệnh sau đây:

npm install module_name

Không quan trọng việc bạn đang chạy hệ điều hành nào; dòng lệnh phía trên sẽ cài đặt module bạn xác định tại ví trí module_name.

Ứng dụng Hello World

Tất nhiên, script Node.js đầu tiên của chúng ta sẽ in ra dòng chữ 'Hello World!' tới console. Tạo ra một file, đặt tên nó là hello.js, và gõ vào dòng code sau:

console.log('Hello World!');

Bây giờ hãy chạy script đó. Mở terminal/command line lên, điều hướng tới folder chứa file hello.js, và chạy dòng lệnh sau:

node hello.js

Bạn sẽ nhìn thấy dòng chữ 'Hello World!' hiển thị trên console.

HTTP Server

Tiếp tục chuyển sang một ứng dụng nâng cao hơn; nó không phức tạp như bạn nghĩ. Hãy bắt đầu với đoạn code sau. Bạn đọc thêm phần comment và phần giải thích phía dưới đoạn code:

// Include http module.
var http = require("http");

// Create the server. Function passed as parameter is called on every request made.
// request variable holds all request parameters
// response variable allows you to do anything with response sent to the client.
http.createServer(function (request, response) {
    // Attach listener on end event.
    // This event is called when client sent all data and is waiting for response.
    request.on("end", function () {
        // Write headers to the response.
        // 200 is HTTP status code (this one means success)
        // Second parameter holds header fields in object
        // We are sending plain text, so Content-Type should be text/plain
        response.writeHead(200, {
            'Content-Type': 'text/plain'
        });
        // Send data and end response.
        response.end('Hello HTTP!');
    });
// Listen on the 8080 port.
}).listen(8080);

Đoạn code trên rất đơn giản. Bạn có thể gửi nhiều dữ liệu hơn tới client bằng cách sử dụng phương thức response.write(), nhưng bạn phải gọi nó trước khi gọi response.end(). Lưu đoạn code trên vào file http.js và gõ dòng lệnh sau vào trong console của bạn:

node http.js

Mở trình duyệt của bạn lên và gõ vào địa chỉ http://localhost:8080. Bạn sẽ nhìn thấy dòng chữ "Hello HTTP!" trên trang đó.

Xử lý các tham số trên URL (Handling URL Parameters)

Như tôi đã đề cập ở trên, chúng ta phải tự tay làm mọi thứ trong Node, bao gồm việc phân tích các tham số request. Tuy nhiên việc này khá đơn giản. Bạn hãy xem đoạn code dưới đây:

// Include http module, 
var http = require("http"), 
// And url module, which is very helpful in parsing request parameters. 
    url = require("url"); 

// Create the server. 
http.createServer(function (request, response) { 
    // Attach listener on end event. 
    request.on('end', function () { 
        // Parse the request for arguments and store them in _get variable. 
        // This function parses the url from request and returns object representation. 
        var _get = url.parse(request.url, true).query; 
        // Write headers to the response. 
        response.writeHead(200, { 
            'Content-Type': 'text/plain' 
        }); 
        // Send data and end response. 
        response.end('Here is your data: ' + _get['data']); 
    }); 
// Listen on the 8080 port. 
}).listen(8080);

Đoạn code trên sử dụng phương thức parse() của module url, một module lõi trong Node.js, để chuyển URL của request thành một đối tượng. Đối tượng trả về có một thuộc tính là query, nó truy xuất các tham số của URL đó. Bạn lưu file này với tên là get.js và chạy nó bằng dòng lệnh sau:

node get.js

Sau đó điều hướng tới đường dẫn http://localhost:8080/?data=put_some_text_here trên trình duyệt của bạn. Tất nhiên là việc thay đổi giá trị của tham số data sẽ không làm đoạn script bị lỗi.

Đọc và ghi file

Để quản lý các file trong Node, chúng ta sử dụng module fs (một module lõi). Chúng ta đọc và ghi file sử dụng các phương thức fs.readFile() và fs.writeFile() tương ứng. Tôi sẽ giải thích về các đối số sau phần code ở dưới đây:

// Include http module,
var http = require("http"),
// And mysql module you've just installed.
    fs = require("fs");

// Create the http server.
http.createServer(function (request, response) {
    // Attach listener on end event.
    request.on("end", function () {
        // Read the file.
        fs.readFile("test.txt", 'utf-8', function (error, data) {
            // Write headers.
            response.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            // Increment the number obtained from file.
            data = parseInt(data) + 1;
            // Write incremented number to file.
            fs.writeFile('test.txt', data);
            // End response with some nice message.
            response.end('This page was refreshed ' + data + ' times!');
        });
    });
// Listen on the 8080 port.
}).listen(8080);

Node.js có một trình quản lý gói được gọi là Node Package Manager (NPM). Nó được cài đặt tự động cùng với Node.js

Lưu đoạn code trên lại trong file tên là files.js. Trước khi bạn chạy đoạn script này, hãy tạo ra một file có tên là test.txt trong cùng thư mục chứa file files.js.

Đoạn code trên minh họa cách sử dụng các phương thức fs.readFile() và fs.writeFile(). Mỗi lần server nhận một request, đoạn script đó đọc một con số từ file, tăng giá trị số lên một đơn vị, và ghi con số mới xuống file. Phương thức fs.readFile() chấp nhận 3 đối số: tên của file cần đọc, kiểu mã hóa mong muốn, và một callback function.

Việc ghi tới một file, ít nhất là trong trường hợp này, còn đơn giản hơn nhiều. Chúng ta không cần đợi bất kỳ kết quả nào, mặc dù bạn nên thêm phần kiểm tra lỗi trong ứng dụng thực tế. Phương thức fs.writeFile() chấp nhận tên file và data như là các đối số. Nó cũng chấp nhận các đối số thứ 3 và 4 (cả hai đều là tùy chọn) để xác định kiểu mã hóa và hàm callback, tương ứng.

Bây giờ hãy chạy đoạn script trên bằng dòng lệnh sau:

node files.js

Mở nó trong trình duyệt tại địa chỉ (http://localhost:8080) và nhấn refresh một vài lần. Lúc này bạn có thể nghĩ rằng có một lỗi trong đoạn code đó bởi vì nó dường như tăng một lần 2 đơn vị. Đây không phải là một lỗi. Mỗi lần mà bạn request URL này, thì có tới 2 request được gửi tới server. Request đầu tiên thì được tạo tự động bởi trình duyệt, nó request favicon.ico, và dĩ nhiên, cái request thứ hai là cho URL (http://localhost:8080).

Mặc dù về mặt kỹ thuật đây không phải là một lỗi, nhưng đó là kết quả mà chúng ta không mong muốn. Chúng ta có thể sửa nó dễ dàng bằng cách kiểm tra request URL. Đây là đoạn code được sửa lại:

// Include http module,
var http = require("http"),
// And mysql module you've just installed.
    fs = require("fs");

// Create the http server.
http.createServer(function (request, response) {
    // Attach listener on end event.
    request.on('end', function () {
        // Check if user requests /
        if (request.url == '/') {
            // Read the file.
            fs.readFile('test.txt', 'utf-8', function (error, data) {
                // Write headers.
                response.writeHead(200, {
                    'Content-Type': 'text/plain'
                });
                // Increment the number obtained from file.
                data = parseInt(data) + 1;
                // Write incremented number to file.
                fs.writeFile('test.txt', data);
                // End response with some nice message.
                response.end('This page was refreshed ' + data + ' times!');
            });
        } else {
            // Indicate that requested file was not found.
            response.writeHead(404);
            // And end request without sending any data.
            response.end();
        }
    });
// Listen on the 8080 port.
}).listen(8080); 

Bạn hãy chạy thử lại đoạn code trên; nó sẽ hoạt động như mong muốn.

Truy cập cơ sở dữ liệu MySQL

Hầu hết các công nghệ server-side truyền thống có một phương tiện built-in để kết nối và truy vấn cơ sở dữ liệu. Với Node.js, bạn phải cài đặt một thư viện để thực hiện công việc đó. Đối với bài tutorial này, tôi đã chọn sử dụng một thư viện ổn định và khá dễ sử dụng là node_mysql. Tên đầy đủ của module này là mysql@2.0.0-alpha2 (mọi thứ phía sau chữ @ là con số phiên bản). Mở console của bạn lên, điều hướng tới thư mục nơi mà bạn đã lưu trữ những script của mình, và chạy dòng lệnh sau:

npm install mysql@2.0.0-alpha2

Lệnh trên sẽ tải về và cài đặt module mysql, và nó cũng tạo ra folder tên là node_modules trong thư mục hiện tại. Bây giờ hãy xem làm cách nào mà chúng ta có thể sử dụng nó trong code của mình; xem đoạn code ví dụ sau:

// Include http module, 
var http = require('http'), 
// And mysql module you've just installed. 
    mysql = require("mysql"); 
     
// Create the connection. 
// Data is default to new mysql installation and should be changed according to your configuration. 
var connection = mysql.createConnection({ 
    user: "root", 
    password: "", 
    database: "db_name"
}); 

// Create the http server. 
http.createServer(function (request, response) { 
    // Attach listener on end event. 
    request.on('end', function () { 
        // Query the database. 
        connection.query('SELECT * FROM your_table;', function (error, rows, fields) { 
            response.writeHead(200, { 
                'Content-Type': 'x-application/json' 
            }); 
            // Send data as JSON string. 
            // Rows variable holds the result of the query. 
            response.end(JSON.stringify(rows)); 
        }); 
    }); 
// Listen on the 8080 port. 
}).listen(8080);

Việc truy vấn cơ sở dữ liệu bằng thư viện này rất dễ; chỉ đơn giản nhập vào chuỗi truy vấn và một hàm callback. Trong một ứng dụng thực tế, bạn nên kiểm tra xem liệu có lỗi xảy ra hay không (tham số error sẽ không là undefined nếu có lỗi xuất hiện) và gửi mã response phụ thuộc vào thành công hay thất bại của truy vấn đó. Ngoài ra cũng chú ý rằng chúng ta thiết lập Content-Type thành x-application/json, đây là kiểu MIME hợp lệ cho JSON. Tham số rows chứa kết quả của truy vấn đó, và chúng ta đơn giản convert dữ liệu trong rows thành một cấu trúc JSON bằng cách sử dụng phương thức JSON.stringify().

Lưu file này thành tên mysql.js, và chạy nó (nếu bạn đã cài đặt MySQL) bằng dòng lệnh sau:

node mysql.js

Điều hướng tới địa chỉ http://localhost:8080 trên trình duyệt của bạn, và bạn sẽ thấy một cửa sổ hiện lện nhắc bạn tải về file có định dạng JSON.

Kết luận

Mỗi function trong Node.js là bất đồng bộ (asynchronous).

Node.js yêu cầu thêm nhiều công việc phụ trợ, nhưng kết quả đạt được là một ứng dụng tốc độ nhanh và mạnh mẽ thì cũng đáng đồng tiền bát gạo. Nếu bạn không muốn làm mọi thứ ở những mức thấp nhất, thì bạn luôn có thể chọn lấy một vài framework, như là >Express, để khiến công việc phát triển ứng dụng trở nên đơn giản hơn.

Node.js là một công nghệ đầy hứa hẹn và là một lựa chọn hoàn hảo cho các ứng dụng có mức tải cao (high load). Nó đã được chứng minh bởi các tổ chức lớn như Microsoft, eBay, và Yahoo. Nếu bạn chưa chắc chắn về việc hosting ứng dụng hoặc website của mình, bạn luôn có thể sử dụng một VPS giá rẻ hoặc rất nhiều dịch vụ cloud-based như Microsoft Azure và Amazon EC2. Cả hai dịch vụ này đều cung cấp những môi trường có khả năng mở rộng với một mức giá chấp nhận được.

Đừng quên để lại bình luận nếu bạn có bất kỳ câu hỏi nào nhé!

Tham khảo:

Để xây dựng web site chuyên nghiệp trên Node.js tham khảo khoá học "Node.js xây dựng web site tốc độ cao"  và Khóa Fullstack node.js 2018"Arrowjs.io Core - CMS"