[翻譯]瞭解NodeJS看這一篇就夠了

白吟靈發表於2018-02-09

原文地址:codeburst.io/the-only-no…
作者:vick_onrails
摘要:這篇文章適合對Node一無所知或瞭解不多的初學者閱讀。全面但不深入地講了包括http模組、express、mongodb和RESTful API等知識點。

如果你是前端開發工作者,那麼對你來說,基於NodeJS編寫web程式已經不是什麼新聞了。而不管是NodeJS還是web程式都非常依賴JavaScript這門語言。

首先,我們要認識到一點:Node並不是銀彈。也就是說,它不是所有專案的最佳解決方案。任何人都可以基於Node建立一個伺服器,但是這需要你對編寫web程式的語言具有一定程式的有很深入的理解。

最近,我從學習Node的過程中發現了許多樂趣,同時我也意識到我已經掌握了一定的知識,應該分享出來,並且從社群獲得反饋來提升自己。

那麼就讓我們開始吧。

Node.js出現之前

在Node.js出現之前,web應用往往基於客戶端/伺服器模式,當客戶端向伺服器請求資源時,伺服器會響應這個請求並且返回相應的資源。伺服器只會在接收到客戶端請求時才會做出響應,同時會在響應結束後關閉與客戶端的連線。

這種設計模式需要考慮到效率問題,因為每一個請求都需要處理時間和資源。因此,伺服器在每一次處理請求的資源後應該關閉這個連線,以便於響應其他請求。

如果同時有成千上萬個請求同時發往伺服器,伺服器會變成什麼樣子呢?當你問出這個問題時,你一定不想看到一個請求必須等待其他請求被響應後才能輪到他的情形,因為這段延遲實在是太長了。

想象一下,當你想要開啟FaceBook,但因為在你之前已經有上千人向伺服器發出過請求,所以你需要等待5分鐘才能看到內容。有沒有一種解決方案來同時處理成百上千個請求呢?所幸我們有執行緒這個工具。

執行緒是系統能夠並行處理多工所使用的方式。每一個發給伺服器的請求都會開啟一個新的執行緒,而每個執行緒會獲取它執行程式碼所需要的一切。

這聽上去很奇怪?讓我們來看看這個例子:

想象餐館裡只有一個廚師提供食物,當食物需求越來越多,事情也會變得越來越糟。在之前的所有訂單都被處理前,人們不得不等待很長時間。而我們能想到的方法就是增加更多的服務員來解決這個問題,對吧?這樣能夠同時應付更多的顧客。

每一個執行緒都是一個新的服務員,而顧客就是瀏覽器。我想理解這一點對你來說並不困難。

但是這種系統有一個副作用,讓請求數達到一定數量時,過多的執行緒會佔用所有系統記憶體和資源。重新回到我們的例子裡,僱傭越來越多的人來供應食物必然會提高人力成本和佔用更多的廚房空間。

當然,如果伺服器在響應完客戶端的請求後立刻切斷連線並釋放所有資源,這對我們來說自然是極好的。

多執行緒系統擅長於處理CPU密集型操作,因為這些操作需要處理大量的邏輯,而且計算這些邏輯會花費更多的時間。如果每一個請求都會被一個新的執行緒處理,那麼主執行緒可以被解放出來去處理一些重要的計算,這樣也能讓整個系統變得更快。

讓主執行緒不必忙於所有的運算操作是一種提高效率的好辦法,但是能不能在此之上更進一步呢?

NodeJS來了

想象一下我們現在已經有了一個多執行緒伺服器,執行於Ruby on rails環境。我們需要它讀取檔案並且傳送給請求這個檔案的瀏覽器。首先要知道的是Ruby並不會直接讀取檔案,而是通知檔案系統去讀取指定檔案並返回它內容。顧名思義,檔案系統就是計算機上一個專門用來存取檔案的程式。

Ruby在向檔案系統發出通知後會一直等待它完成讀取檔案的操作,而不是轉頭去處理其他任務。當檔案系統處理任務完成後,Ruby才會重新啟動去收集檔案內容並且傳送給瀏覽器。

這種方式很顯然會造成阻塞的情況,而NodeJS的誕生就是為了解決這個痛點。如果我們使用Node來向檔案系統發出通知,在檔案系統去讀取檔案的這段時間裡,Node會去處理其他請求。而讀取檔案的任務完成後,檔案系統會通知Node去讀取資源然後將它返回給瀏覽器。事實上,這裡的內部實現都是依賴於Node的事件迴圈。

Node的核心就是JavaScript和事件迴圈。

事件迴圈

簡單地說,事件迴圈就是一個等待事件然後在需要事件發生時去觸發它們的程式。此外還有一點很重要,就是Node和JavaScript一樣都是單執行緒的。

還記得我們舉過的餐廳例子嗎?不管顧客數量有多少,Node開的餐廳裡永遠只有一個廚師烹飪食物。

與其他語言不同,NodeJS不需要為每一個請求開啟一個新的執行緒,它會接收所有請求,然後將大部分任務委託給其他的系統。Libuv就是一個依賴於OS核心去高效處理這些任務的庫。當這些隱藏於幕後的工作者處理完委託給它們的事件後,它們會觸發繫結在這些事件上的回撥函式去通知NodeJS。

這兒我們接觸到了回撥這個概念。回撥理解起來並不困難,它是被其他函式當作引數傳遞的函式,並且在某種特定情況下會被呼叫。

NodeJS開發者們做的最多的就是編寫事件處理函式,而這些處理函式會在特定的NodeJS事件發生後被呼叫。

NodeJS雖然是單執行緒,但它比多執行緒系統要快得多。這是因為程式往往並不是只有耗時巨長的數學運算和邏輯處理,大部分時間裡它們只是寫入檔案、處理網路請求或是向控制檯和外部裝置申請許可權。這些都是NodeJS擅長處理的問題:當NodeJS在處理這些事情時,它會迅速將這些事件委託給專門的系統,轉而去處理下一個事件。

如果你繼續深入下去,你也許會意識到NodeJS並不擅長處理消耗CPU的操作。因為CPU密集型操作會佔用大量的主執行緒資源。對於單執行緒系統來說,最理想的情況就是避免這些操作來釋放主執行緒去處理別的事情。

還有一個關鍵點是在JavaScript中,只有你寫的程式碼不是併發執行的。也就是說,你的程式碼每次只能處理一件事,而其他工作者,比如檔案系統可以並行處理它們手頭的工作。

如果你還不能理解的話,可以看看下面的例子:

很久以前有一個國王,他有一千個官員。國王寫了一個任務清單讓官員去做,清單非常非常非常長。有一個宰相,根據清單將任務委託給其他所有官員。每完成一項任務他就將結果報告給國王,之後國王又會給他另一份清單。因為在官員工作的時候,國王也在忙於寫其他清單。

這個例子要講的是即使有很多官員在並行處理任務,國王每次也只能做一件事。這裡,國王就是你的程式碼,而官員就是藏於NodeJS幕後的系統工作者。所以說,除了你的程式碼,每件事都是並行發生的。

好了,讓我們繼續這段NodeJS之旅吧。

用NodeJS寫一個web應用

用NodeJS寫一個web應用相當於編寫事件回撥。讓我們來看看下面的例子:

  1. 新建並進入一個資料夾
  2. 執行npm init命令,一直回車直到你在資料夾根目錄下建立了一個package.json檔案。
  3. 新建一個名為server.js的檔案,複製並貼上下面的程式碼:
    //server.js
    const http = require('http'),
          server = http.createServer();
    
    server.on('request',(request,response)=>{
       response.writeHead(200,{'Content-Type':'text/plain'});
       response.write('Hello world');
       response.end();
    });
    
    server.listen(3000,()=>{
      console.log('Node server created at port 3000');
    });
    複製程式碼
  4. 在命令列中,輸入node server.js,你會看到下面的輸出:
    node server.js
    //Node server started at port 3000
    複製程式碼

開啟瀏覽器並且進入localhost:3000,你應該能夠看到一個Hello world資訊。

首先,我們引入了http模組。這個模組提供了處理htpp操作的介面,我們呼叫createServer()方法來建立一個伺服器。

之後,我們為request事件繫結了一個事件回撥,傳遞給on方法的第二個引數。這個回撥函式有2個引數物件,request代表接收到的請求,response代表響應的資料。

不僅僅是處理request事件,我們也可以讓Node去做其他事情。

//server.js
const http = require('http'),
server = http.createServer((request,response)=>{
    response.writeHead(200,{'Content-Type':'text/plain'});
    response.write('Hello world');
    response.end();
});
server.listen(3000,()=>{
    console.log('Node server created at port 3000');
});
複製程式碼

在當面的程式碼裡,我們傳給createServer()一個回撥函式,Node把它繫結在request事件上。這樣我們只需要關心request和response物件了。

我們使用response.writeHead()來設定返回報文頭部欄位,比如狀態碼和內容型別。而response.write()是對web頁面進行寫入操作。最後使用response.end()來結束這個響應。

最後,我們告知伺服器去監聽3000埠,這樣我們可以在本地開發時檢視我們web應用的一個demo。listen這個方法要求第二個引數是一個回撥函式,伺服器一啟動,這個回撥函式就會被執行。

習慣回撥

Node是一個單執行緒事件驅動的執行環境,也就是說,在Node裡,任何事都是對事件的響應。

前文的例子可以改寫成下面這樣:

//server.js
const http = require('http'),
      
makeServer = function (request,response){
   response.writeHead(200,{'Content-Type':'text/plain'});
   response.write('Hello world');
   response.end();
},
      
server = http.createServer(makeServer);

server.listen(3000,()=>{
  console.log('Node server created at port 3000');
複製程式碼

makeServer是一個回撥函式,由於JavaScript把函式當作一等公民,所以他們可以被傳給任何變數或是函式。如果你還不瞭解JavaScript,你應該花點時間去了解什麼是事件驅動程式。

當你開始編寫一些重要的JavaScript程式碼時,你可能會遇到“回撥地獄”。你的程式碼變得難以閱讀因為大量的函式交織在一起,錯綜複雜。這時你想要找到一種更先進、有效的方法來取代回撥。看看Promise吧,Eric Elliott 寫了一篇文章來講解什麼是Promise,這是一個好的入門教程。

NodeJS路由

一個伺服器會儲存大量的檔案。當瀏覽器傳送請求時,會告知伺服器他們需要的檔案,而伺服器會將相應的檔案返回給客戶端。這就叫做路由。

在NodeJS中,我們需要手動定義自己的路由。這並不麻煩,看看下面這個基本的例子:

//server.js
const http = require('http'),
      url = require('url'),
 
makeServer = function (request,response){
   let path = url.parse(request.url).pathname;
   console.log(path);
   if(path === '/'){
      response.writeHead(200,{'Content-Type':'text/plain'});
      response.write('Hello world');
   }
   else if(path === '/about'){
     response.writeHead(200,{'Content-Type':'text/plain'});
     response.write('About page');
   }
   else if(path === '/blog'){
     response.writeHead(200,{'Content-Type':'text/plain'});
     response.write('Blog page');
   }
   else{
     response.writeHead(404,{'Content-Type':'text/plain'});
     response.write('Error page');
   }
   response.end();
 },
server = http.createServer(makeServer);
server.listen(3000,()=>{
 console.log('Node server created at port 3000');
});
複製程式碼

貼上這段程式碼,輸入node server.js命令來執行。在瀏覽器中開啟localhost:3000localhost:3000/abou,然後在試試開啟localhost:3000/somethingelse,是不是跳轉到了我們的錯誤頁面?

雖然這樣滿足了我們啟動伺服器的基本要求,但是要為伺服器上每一個網頁都寫一遍程式碼實在是太瘋狂了。事實上沒有人會這麼做,這個例子只是讓你瞭解路由是怎麼工作的。

如果你有注意到,我們引入了url這個模組,它能讓我們處理url更加方便。

為parse()方法傳入一個url字串引數,這個方法會將url拆分成protocolhostpathquerystring等部分。如果你不太瞭解這些單詞,可以看看下面這張圖:

url

所以當我們執行url.parse(request.url).pathname語句時,我們得到一個url路徑名,或者是url本身。這些都是我們用來進行路由請求的必要條件。不過這件事還有個更簡單的方法。

使用Express進行路由

如果你之前做過功課,你一定聽說過Express。這是一個用來構建web應用以及API的NodeJS框架,它也可以用來編寫NodeJS應用。接著往下看,你會明白為什麼我說它讓一切變得更簡單。

在你的終端或是命令列中,進入電腦的根目錄,輸入npm install express --save來安裝Express模組包。要在專案中使用Express,我們需要引入它。

const express = require('express');
複製程式碼

歡呼吧,生活將變得更美好。

現在,讓我們用express進行基本的路由。

//server.js
const express = require('express'),
      server = express();

server.set('port', process.env.PORT || 3000);

//Basic routes
server.get('/', (request,response)=>{
   response.send('Home page');
});

server.get('/about',(request,response)=>{
   response.send('About page');
});

//Express error handling middleware
server.use((request,response)=>{
   response.type('text/plain');
   response.status(505);
   response.send('Error page');
});

//Binding to a port
server.listen(3000, ()=>{
  console.log('Express server started at port 3000');
});
複製程式碼

譯者注:這裡不是很理解為什麼程式碼中錯誤狀態碼是505。

現在的程式碼是不是看上去更加清晰了?我相信你很容易就能理解它。

首先,當我們引入express模組後,得到的是一個函式。呼叫這個函式後就可以開始啟動我們的伺服器了。

接下來,我們用server.set()來設定監聽埠。而process.env.PORT是程式執行時的環境所設定的。如果沒有這個設定,我們預設它的值是3000.

然後,觀察上面的程式碼,你會發現Express裡的路由都遵循一個格式:

server.VERB('route',callback);
複製程式碼

這裡的VERB可以是GET、POST等動作,而pathname是跟在域名後的字串。同時,callback是我們希望接收到一個請求後觸發的函式。

最後我們再呼叫server.listen(),還記得它的作用吧?

以上就是Node程式裡的路由,下面我們來挖掘一下Node如何呼叫資料庫。

NodeJS裡的資料庫

很多人喜歡用JavaScript來做所有事。剛好有一些資料庫滿足這個需求,比如MongoDB、CouchDB等待。這些資料庫都是NoSQL資料庫。

一個NoSQL資料庫以鍵值對的形式作為資料結構,它以文件為基礎,資料都不以表格形式儲存。

我們來可以看看MongoDB這個NoSQL資料庫。如果你使用過MySQL、SQLserver等關係型資料庫,你應該熟悉資料庫、表格、行和列等概念。 MongoDB與他們相比並沒有特別大的區別,不過還是來比較一下吧。

譯者注:這兒應該有個表格顯示MongoDB與MySQL的區別,但是原文裡沒有顯示。

為了讓資料更加有組織性,在向MongoDB插入資料之前,我們可以使用Mongoose來檢查資料型別和為文件新增驗證規則。它看上去就像Mongo與Node之間的中介人。

由於本文篇幅較長,為了保證每一節都儘可能的簡短,請你先閱讀官方的MongoDB安裝教程

此外,Chris Sevilleja寫了一篇Easily Develop Node.js and MongoDB Apps with Mongoose,我認為這是一篇適合入門的基礎教程。

使用Node和Express編寫RESTful API

API是應用程式向別的程式傳送資料的通道。你有沒有登陸過某些需要你使用facebook賬號登入的網頁?facebook將某些函式公開給這些網站使用,這些就是API。

一個RESTful API應該不以伺服器/客戶端的狀態改變而改變。通過使用一個REST介面,不同的客戶端,即使它們的狀態各不相同,但是在訪問相同的REST終端時,應該做出同一種動作,並且接收到相同的資料。

API終端是API裡返回資料的一個函式。

編寫一個RESTful API涉及到使用JSON或是XML格式傳輸資料。讓我們在NodeJS裡試試吧。我們接下來會寫一個API,它會在客戶端通過AJAX發起請求後返回一個假的JSON資料。這不是一個理想的API,但是能幫助我們理解在Node環境中它是怎麼工作的。

  1. 建立一個叫node-api的資料夾;
  2. 通過命令列進入這個資料夾,輸入npm init。這會建立一個收集依賴的檔案;
  3. 輸入npm install --save express來安裝express;
  4. 在根目錄新建3個檔案:server.jsindex.htmlusers.js
  5. 複製下面的程式碼到相應的檔案:
//users.js
module.exports.users = [
 {
  name: 'Mark',
  age : 19,
  occupation: 'Lawyer',
  married : true,
  children : ['John','Edson','ruby']
 },
  
 {
  name: 'Richard',
  age : 27,
  occupation: 'Pilot',
  married : false,
  children : ['Abel']
 },
  
 {
  name: 'Levine',
  age : 34,
  occupation: 'Singer',
  married : false,
  children : ['John','Promise']
 },
  
 {
  name: 'Endurance',
  age : 45,
  occupation: 'Business man',
  married : true,
  children : ['Mary']
 },
]
複製程式碼

這是我們傳給別的應用的資料,我們匯出這份資料讓所有程式都可以使用。也就是說,我們將users這個陣列儲存在modules.exports物件中。

//server.js
const express = require('express'),
      server = express(),
      users = require('./users');

//setting the port.
server.set('port', process.env.PORT || 3000);

//Adding routes
server.get('/',(request,response)=>{
 response.sendFile(__dirname + '/index.html');
});

server.get('/users',(request,response)=>{
 response.json(users);
});

//Binding to localhost://3000
server.listen(3000,()=>{
 console.log('Express server started at port 3000');
});
複製程式碼

我們執行require('express')語句然後使用express()建立了一個服務變數。如果你仔細看,你還會發現我們引入了別的東西,那就是users.js。還記得我們把資料放在哪了嗎?要想程式工作,它是必不可少的。

express有許多方法幫助我們給瀏覽器傳輸特定型別的內容。response.sendFile()會查詢檔案並且傳送給伺服器。我們使用__dirname來獲取伺服器執行的根目錄路徑,然後我們把字串index.js加在路徑後面保證我們能夠定位到正確的檔案。

response.json()向網頁傳送JSON格式內容。我們把要分享的users陣列傳給它當引數。剩下的程式碼我想你在之前的文章中已經很熟悉了。

//index.html

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <title>Home page</title>
</head>
<body>
 <button>Get data</button>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
 
  <script type="text/javascript">
  
    const btn = document.querySelector('button');
    btn.addEventListener('click',getData);
    function getData(e){
        $.ajax({
        url : '/users',
        method : 'GET',
        success : function(data){
           console.log(data);
        },
      
        error: function(err){
          console.log('Failed');
        }
   });
  } 
 </script>
</body>
</html>
複製程式碼

在資料夾根目錄中執行node server.js,現在開啟你的瀏覽器訪問localhost:3000,按下按鈕並且開啟你的瀏覽器控制檯。

瀏覽器控制檯

btn.addEventListent('click',getData);這行程式碼裡,getData通過AJAX發出一個GET請求,它使用了$.ajax({properties})函式來設定urlsuccesserror等引數。

在實際生產環境中,你要做的不僅僅是讀取JSON檔案。你可能還想對資料進行增刪改查等操作。express框架會將這些操作與特定的http動詞繫結,比如POST、GET、PUT和DELETE等關鍵字。

要想深入瞭解使用express如何編寫API,你可以去閱讀Chris Sevilleja寫的Build a RESTful API with Express 4

使用socket進行網路連線

計算機網路是計算機之間分享接收資料的連線。要在NodeJS中進行連網操作,我們需要引入net模組。

const net = require('net');
複製程式碼

在TCP中必須有兩個終端,一個終端與指定埠繫結,而另一個則需要訪問這個指定埠。

如果你還有疑惑,可以看看這個例子:

以你的手機為例,一旦你買了一張sim卡,你就和sim的電話號碼繫結。當你的朋友想要打電話給你時,他們必須撥打這個號碼。這樣你就相當於一個TCP終端,而你的朋友是另一個終端。

現在你明白了吧?

為了更好地吸收這部分知識,我們來寫一個程式,它能夠監聽檔案並且當檔案被更改後會通知連線到它的客戶端。

  1. 建立一個資料夾,命名為node-network
  2. 建立3個檔案:filewatcher.jssubject.txtclient.js。把下面的程式碼複製進filewatcher.js
    //filewatcher.js
    
    const net = require('net'),
       fs = require('fs'),
       filename = process.argv[2],
          
    server = net.createServer((connection)=>{
     console.log('Subscriber connected');
     connection.write(`watching ${filename} for changes`);
      
    let watcher = fs.watch(filename,(err,data)=>{
      connection.write(`${filename} has changed`);
     });
      
    connection.on('close',()=>{
      console.log('Subscriber disconnected');
      watcher.close();
     });
      
    });
    server.listen(3000,()=>console.log('listening for subscribers'));
    複製程式碼
  3. 接下來我們提供一個被監聽的檔案,在subject.txt寫下下面一段話:
    Hello world, I'm gonna change
    複製程式碼
  4. 然後,新建一個客戶端。下面的程式碼複製到client.js
    const net = require('net');
    let client = net.connect({port:3000});
    client.on('data',(data)=>{
     console.log(data.toString());
    });
    複製程式碼
  5. 最後,我們還需要兩個終端。第一個終端裡我們執行filename.js,後面跟著我們要監聽的檔名。
    //subject.txt會儲存在filename變數中
    node filewatcher.js subject.txt
    //監聽訂閱者
    複製程式碼

在另一個終端,也就是客戶端,我們執行client.jsnode client.js 現在,修改subject.txt,然後看看客戶端的命令列,注意到多出了一條額外資訊:

//subject.txt has changed.
複製程式碼

網路的一個主要的特徵就是許多客戶端都可以同時接入這個網路。開啟另一個命令列視窗,輸入node client.js來啟動另一個客戶端,然後再修改subject.txt檔案。看看輸出了什麼?

我們做了什麼?

如果你沒有理解,不要擔心,讓我們重新過一遍。

我們的filewatcher.js做了三件事:

  1. net.createServer()建立一個伺服器並向許多客戶端傳送資訊。
  2. 通知伺服器有客戶端連線,並且告知客戶端有一個檔案被監聽。
  3. 最後,使用wactherbianl監聽檔案,並且當客戶端埠連線時關閉它。

再來看一次filewatcher.js

//filewatcher.js

const net = require('net'),
   fs = require('fs'),
   filename = process.argv[2],
      
server = net.createServer((connection)=>{
 console.log('Subscriber connected');
 connection.write(`watching ${filename} for changes`);
  
let watcher = fs.watch(filename,(err,data)=>{
  connection.write(`${filename} has changed`);
 });
  
connection.on('close',()=>{
  console.log('Subscriber disconnected');
  watcher.close();
 });
  
});
server.listen(3000,()=>console.log('listening for subscribers'));
複製程式碼

我們引入兩個模組:fs和net來讀寫檔案和執行網路連線。你有注意到process.argv[2]嗎?process是一個全域性變數,提供NodeJS程式碼執行的重要資訊。argv[]是一個引數陣列,當我們獲取argv[2]時,希望得到執行程式碼的第三個引數。還記得在命令列中,我們曾輸入檔名作為第三個引數嗎?

node filewatcher.js subject.txt
複製程式碼

此外,我們還看到一些非常熟悉的程式碼,比如net.createServer(),這個函式會接收一個回撥函式,它在客戶端連線到埠時觸發。這個回撥函式只接收一個用來與客戶端互動的物件引數。

connection.write()方法向任何連線到3000埠的客戶端傳送資料。這樣,我們的connetion物件開始工作,通知客戶端有一個檔案正在被監聽。

wactcher包含一個方法,它會在檔案被修改後傳送資訊給客戶端。而且在客戶端斷開連線後,觸發了close事件,然後事件處理函式會向伺服器傳送資訊讓它關閉watcher停止監聽。

//client.js
const net = require('net'),
      client = net.connect({port:3000});
client.on('data',(data)=>{
  console.log(data.toString());
});
複製程式碼

client.js很簡單,我們引入net模組並且呼叫connect方法去訪問3000埠,然後監聽每一個data事件並列印出資料。

當我們的filewatcher.js每執行一次connection.write(),我們的客戶端就會觸發一次data事件。

以上只是網路如何工作的一點皮毛。主要就是一個端點廣播資訊時會觸發所有連線到這個端點的客戶端上的data事件。

如果你想要了解更多Node的網路知識,可以看看官方NodeJS的文件:net模組。你也許還需要閱讀Building a Tcp service using Node

作者總結

好了,這就是我要講的全部。如果你想要使用NodeJS來編寫web應用程式,你要知道的不僅僅是編寫一個伺服器和使用express進行路由。

下面是我推薦的一些書:
NodeJs the right way
Web Development With Node and Express

如果你還有什麼見解,可以在下面發表評論。

譯者注:這個翻譯專案才開始,以後會翻譯越來越多的作品。我會努力堅持的。
專案地址:github.com/WhiteYin/tr…

相關文章