+6

[ExpressJS] Bài 1 - Làm Quen Với ExpressJS

Như vậy là chúng ta lại bắt đầu một Sub-Series mới của Tự Học Lập Trình Web Một Cách Thật Tự Nhiên để tìm hiểu về một framework có tên là ExpressJS. Đây là một framework rất phổ biến dành cho các ứng dụng web trên nền NodeJS - và sẽ giúp đỡ chúng ta xây dựng phần mềm server thuận lợi hơn với nhiều công cụ trừu tượng có cách sử dụng đơn giản.

Trước khi bắt đầu bước vào phần nội dung chính của bài viết, chúng ta sẽ cùng nhìn lại code server đơn giản mà chúng ta đã viết trước đó. Ở đây chúng ta vẫn sẽ sử dụng lại cấu trúc thư mục như cũ với các tệp HTML, CSS, và JS giả định đã được tạo ra trước đó. Các thành phần được tạo ra bởi npm thì mình sẽ không liệt kê ở đây, tuy nhiên thì chúng ta đều đã biết là project khởi đầu đã được init xong xuôi rồi. Chúng ta chỉ việc cài đặt thêm ExpressJS để viết code thử và học kiến thức mới thôi. 😄

[nodejs-blog]
   |
   +---[static]
   |      |
   |      +---[asset]
   |      |      |
   |      |      +---style.css
   |      |      +---main.js
   |      |
   |      +---[post]
   |      |      |
   |      |      +---an-article.html
   |      |      +---another-article.html
   |      |
   |      +---index.html
   |      +---oops.html
   |
   +---route.js
   +---server.js
   +---test.js
const fsPromises = require('fs/promises');
const path = require('path');

const handleHomeRequest = function(request, response) {
   var indexHtml = path.join(__dirname, 'static', 'index.html');

   fsPromises.readFile(indexHtml)
      .then(function(data) {
         response.setHeader('content-type', textType.html);
         response.writeHead(200);
         response.end(data);
      })
      .catch(function(error) {
         console.error(error);
      });
}; // handleHomeRequest

const handlePostRequest = function(request, response) {
   var postHtml = path.join(__dirname, 'static', request.url);

   fsPromises.readFile(postHtml)
      .then(function(data) {
         response.setHeader('content-type', textType.html);
         response.writeHead(200);
         response.end(data);
      })
      .catch(function(error) {
         console.error(error);
         handleOopsRequest(request, response);
      });
}; // handlePostRequest

const handleAssetRequest = function(request, response) {
   var assetFile = path.join(__dirname, 'static', request.url);

   fsPromises.readFile(assetFile)
      .then(function(data) {
         var contentType = textType.get(request.url);
         response.setHeader('content-type', contentType);
         response.writeHead(200);
         response.end(data);
      })
      .catch(function(error) {
         console.error(error);
      });
}; // handleAssetRequest

const handleOopsRequest = function(request, response) {
   var oopsHtml = path.join(__dirname, 'static', 'oops.html');

   fsPromises.readFile(oopsHtml)
      .then(function(data) {
         response.setHeader('content-type', textType.html);
         response.writeHead(404);
         response.end(data);
      })
      .catch(function(error) {
         console.error(error);
      });
}; // handleOopsRequest

const textType = {
   html: 'text/html',
   css: 'text/css',
   js: 'text/javascript',

   get(url) {
      if (url.endsWith('.html'))
         return textType.html;
      else if (url.endsWith('.css'))
         return textType.css;
      else if (url.endsWith('.js'))
         return textType.js;
      else
         return '';
   }
}; // textType

module.exports = {
   handleHomeRequest,
   handlePostRequest,
   handleAssetRequest,
   handleOopsRequest
}; // module.exports
const http = require('http');
const path = require('path');

   /* Creating a server */

const handleRequest = function(request, response) {
   var routeJs = path.join(__dirname, 'route.js');
   var route = require(routeJs);

   if (request.url == '/')
      route.handleHomeRequest(request, response);
   else if (request.url.startsWith('/post'))
      route.handlePostRequest(request, response);
   else if (request.url.startsWith('/asset'))
      route.handleAssetRequest(request, response);
   else
      route.handleOopsRequest(request, response);
}; // handleRequest

const server = http.createServer(handleRequest);

   /* Start running server */

const port = 3000;
const hostname = '127.0.0.1';

const callback = function() {
   console.log('Server is running at...');
   console.log('http://' + hostname + ':' + port + '/');
}; // callback

server.listen(port, hostname, callback);
// xóa nội dung để làm quen với ExpressJS
<!doctype html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   
   <title>Homepage</title>
   
   <link rel="stylesheet" href="/asset/style.css">
</head>
<body>
   <h1>Homepage</h1>
   <script src="/asset/main.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Oops ! Not-Found</title>
   <link rel="stylesheet" href="/asset/style.css">
</head>
<body>
   <h1>Oops ! Not-Found</h1>
   <script src="/asset/main.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>An Article</title>
   <link rel="stylesheet" href="/asset/style.css">
</head>
<body>
   <h1>An Article</h1>
   <script src="/asset/main.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Another Article</title>
   <link rel="stylesheet" href="/asset/style.css">
</head>
<body>
   <h1>Another Article</h1>
   <script src="/asset/main.js"></script>
</body>
</html>
console.log('Client-side JavaScript');
h1 {
   font-size: 90px;
   line-height: 1.5;
   text-align: center;
}

Cài đặt và sử dụng cơ bản

Giống với các package khác mà chúng ta đã thử cài đặt từ npm, việc tiến hành cài đặt ExpressJS cho project hiện hành được thực hiện bằng một dòng lệnh -

npm install express --save

Và như vậy là chúng ta đã có thể viết code để sử dụng thử ExpressJS giống như lần đầu tiên khởi tạo một server trên nền NodeJS. Mà thực ra thì không phải là chúng ta viết, là copy/paste về từ trang chủ của ExpressJS thôi. 😄

const express = require('express');

   /* Creating server */
   
const app = express();

   /* Adding a route */

const path = '/';

app.get(path, function(request, response) {
   response.send('Hello World!');
});

   /* Start running server */

const port = 3000;

app.listen(port, function() {
   console.log('Server is running at...');
   console.log(`http://127.0.0.1:${port}`);
});

Bắt đầu chạy thử code Hello World được cung cấp bởi ExpressJS.com.

npm test

Tuyệt.. mọi thứ đã hoạt động tốt giống như ExpressJS quảng cáo. 😄

Đối với code khởi tạo server đơn giản thì hiển nhiên chúng ta chưa thể nhìn thấy được nhiều sự khác biệt giữa code Hello World không có ExpressJS và code Hello World mà chúng ta vừa mới copy/paste từ trang chủ của ExpressJS. Ở đây chúng ta chỉ nhận ra một chi tiết khác biệt nho nhỏ - đó là đoạn app.get() chắc chắn là có ý nghĩa tương đương với đoạn code mà chúng ta truyền hàm xử lý yêu cầu handleRequest vào phương thức http.createServer trong code server cũ.

Tuy nhiên ở đây app.get() nhận vào thêm một tham số path chỉ đường dẫn nhận diện từ yêu cầu được gửi tới. Và hàm xử lý yêu cầu được truyền vào sẽ chỉ được thực thi khi yêu cầu gửi tới phù hợp với path mà chúng ta truyền vào phương thức app.get(). Như vậy là chúng ta không phải khởi đầu với một hàm handleRequest tiếp nhận tất cả các kiểu reuqest truyền tới và sau đó lại phải thực hiện phân tích để chia tác vụ về các hàm xử lý phụ. Nếu vậy có lẽ ExpressJS sẽ cho phép chúng ta tạo ra thêm những chu trình xử lý route khác với cách thức tương tự như cái route dành cho trang chủ trong code ví dụ vừa rồi.

const express = require('express');

   /* Creating server */

const app = express();

   /* Adding routes */

app.get('/', function(request, response) {
   response.send('Bạn vừa yêu cầu xem Trang Chủ');
});

app.get('/post', function(request, response) {
   response.send('Bạn vừa yêu cầu xem Bài Viết');
});

   /* Start running server */

app.listen(3000, function() {
   console.log('Server is running at...');
   console.log('http://127.0.0.1:3000');
});

Thật tuyệt... Như vậy là chúng ta không cần phải thực hiện thao tác phân tích đường dẫn yêu cầu ở một hàm tiếp nhận request tổng bộ rồi ủy thác công việc tới hàm xử lý phù hợp ở cấp thấp hơn. Tất cả những gì chúng ta cần làm đó là truyền các cặp đường dẫn/hàm xử lý tương ứng vào các câu lệnh app.get(path, handler).

Tuy nhiên đó vẫn chưa phải là tất cả những gì mà ExpressJS có thể giúp đỡ chúng ta thay đổi code server đơn giản mà chúng ta đã có trước đó. Bây giờ chúng ta sẽ thử gửi trả lại các tệp HTML tĩnh tương ứng với các yêu cầu.

const express = require('express');
const path = require('path');

   /* Creating server */

const app = express();

   /* Adding routes */

const staticFolder = path.join(__dirname, 'static');

app.get('/', function(request, response) {
   var indexHtml = path.join(staticFolder, 'index.html');
   response.sendFile(indexHtml);
});

app.get('*', function(request, response) {
   var staticFile = path.join(staticFolder, request.originalUrl);
   
   response.sendFile(staticFile, function(error) {
      var oopsHtml = path.join(staticFolder, 'oops.html')
      
      if (error instanceof Error)
         response.status(404).sendFile(oopsHtml)
      else
         { /* do nothing */; }
   }); // response
});

   /* Start running server */

app.listen(3000, function() {
   console.log('Server is running at...');
   console.log('http://127.0.0.1:3000');
});

Thao tác gửi trả một tệp tĩnh cũng là một trong số những thao tác rất phổ biến và vì vậy ExpressJS cũng đã giúp chúng ta đơn giản hóa mọi thao tác xử lý chi tiết. Chúng ta đã không cần phải viết đoạn code nhờ File System truy xuất tới tệp cần gửi trả để đọc nội dung. 😄

Nếu như jQuery ở phía client-side là một nhà thông thái trong nhóm tác vụ làm việc với cấu trúc văn bản HTML và CSS; Thì ExpressJS ở đây lại đặc biệt am hiểu về những thao tác điều hướng route, và phản rồi response khi làm việc với các yêu cầu request gửi tới từ đâu đó.

Ở đây chúng ta đã sử dụng path ở lần gắn hàm xử lý sự kiện thứ hai là *, nó có nghĩa là hàm xử lý truyền vào ở đây sẽ được áp dụng cho tất cả các yêu cầu gửi tới. Ký hiệu này được sử dụng với ý nghĩa tương tự ở rất nhiều công cụ lập trình khác mà chúng ta đã học; Ví dụ như bộ chọn * của CSS cũng là để chọn tất cả các phần tử HTML có mặt trong trang web đơn; Hoặc trong các biểu thức thường thị RexExp giúp làm việc với các chuỗi, thì * cũng có ý nghĩa là bất kỳ kí tự nào. Vậy chúng ta cứ ghi nhớ * có nghĩa là bất kỳ cái gì cũng phù hợp. 😄

Làm quen với tài liệu

Sau khi chúng ta đã hiểu sơ lược về những giá trị mà ExpressJS đem lại, việc tiếp tục tìm hiểu về framework này chắc chắn vẫn sẽ là xuất phát từ tài liệu chính thức trên trang chủ của framework - ExpressJS.com.

Bộ tài liệu chính thức mà ExpressJS cung cấp cho chúng ta rất gọn gàng với 5 chỉ mục chính ở phía bên trái được đặt trong một danh sách đóng/mở dạng accordion giúp chúng ta luôn luôn duy trì được cái nhìn tổng quan về các công cụ được cung cấp. Và điểm khởi đầu của framework là hàm express().

cosnt express = require('express');
cosnt app = express();

ExpressJS gọi phần mềm server của chúng ta là app (application) - hay ứng dụng - để biểu thị một phần mềm hoạt động qua tương tác mạng network nói chung. Và như vậy chúng ta có chỉ mục tiếp theo cần mở xem là Application.

const express = require('express');
const app = express();

app.get('/', function(req, res) {
  res.send('hello world');
});

app.listen(3000);

Bên cạnh đó thì object express cũng có một phương thức nổi bật so với số còn lại đó là express.Router() và chúng ta cũng thấy có một chỉ mục tương ứng cùng tên Router - tạm dịch là trình định tuyến - điều hướng yêu cầu nhận được tới đâu đó để xử lý. Các router sẽ giúp chúng ta tách rời tác vụ phân tích và chuyển hướng yêu cầu khỏi phần thân chương trình chính ở tệp khởi tạo ứng dụng app trong trường hợp bạn có dự định xây dựng một thứ gì đó đồ sộ. 😄

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

router.get('/', function(req, res) {
   res.send('hello world');
});

module.exports = { router };
const path = require('path');
const express = require('express');
const app = express();

const routerJs = path.join(__dirname, 'router.js');
const { router } = require(routerJs);

app.use('/', router);
app.listen(3000);

Hai chỉ mục còn lại là RequestResponse thì chúng ta cũng có thể đoán ra được là liên quan tới 2 tham số đầu tiên (req, res) mà các hàm xử lý nhận được. Như vậy là chúng ta có thể thấy tổng quan những công cụ của ExpressJS được liệt kê trong trong tài liệu này sẽ giúp chúng ta đơn giản hóa các tác vụ phân tích và điều hướng yêu cầu tới những chu trình xử lý riêng. Như vậy thì chúng ta sẽ có thể tập trung tốt hơn vào việc viết code xử lý yêu cầu cụ thể và không tốn quá nhiều năng lượng vào thao tác phân tích địa chỉ yêu cầu và khởi tạo logic điều hướng.

Thế cái tác vụ tự động tạo ra mấy trang web đơn từ nguồn dữ liệu đơn giản sẽ được hỗ trợ ở chỗ nào?

À.. mình quên khuấy mất. Cái này mới là cái quan trọng nhất đối với nhu cầu tạo blog đơn giản của chúng ta hiện tại; Chứ cái tác vụ phân tích với điều hướng yêu cầu thì cũng chưa hẳn là quan trọng lắm. 😄

Phương thức resonse.render(template, data) sẽ giúp chúng ta sử dụng các biểu mẫu template mô tả code HTML ở dạng chưa có dữ liệu thực tế - gắn với dữ liệu data truy vấn được bởi logic cung cấp trong hàm xử lý sự kiện và tạo ra code HTML hoàn chỉnh để gửi trả cho trình duyệt web.

Và như vậy là chúng ta sẽ cần tìm hiểu cách thức để tạo ra cái template mà chúng ta vừa nói đến, và... hoàn thiện trang blog đơn giản mà chúng ta đang xây dựng thôi. 😄

Hẹn gặp lại bạn trong bài viết tiếp theo.

[ExpressJS] Bài 2 - Thiết Lập & Sử Dụng View Engine


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.