1. 如何引入express?
cnpm install express --save
其中--save可以保存到依賴項中。
接着 var express = require("express"); 即可。這里express只是一個模塊。
注意: 有時候我們會看到有人使用 npm i express --save 的方式來安裝,這樣也是可行的,因為 npm i 就是 npm install 的簡寫形式。
2. 什么是並且如何使用express-generator?
這是一個生成express的生成器,通過它,我們可以快速構建一個express架構,而無需自己繁瑣的一項一項構建。
cnpn install express-generator -g (管理員方式打開命令窗口)
注意:如果是在當前目錄安裝就不需要使用管理員方式,但是如果全局安裝,就一定要使用管理員方式。因為這里創建了一個生成器,所以就像構造函數一樣可以去創建實例,那么express在命令行中就是相當於一個可執行文件, 如果不是全局安裝,就必須要在express-generator的文件目錄下才能執行 ,非常繁瑣,但是如果全局安裝,我們在任何目錄下都可以執行該命令。 如:
express myapp
就可以創建一個express架構。
這里默認使用的模板引擎是jade,如果希望使用ejs模板引擎,可以是 npm -e myapp, 其中的-e就代表使用ejs模板引擎。
如下:
注意:即通過express-generator我們再執行 express <項目名稱> 可以快速構建一個架構。 其中myapp就是這樣一個文件,包含了package.json(此文件中的依賴項中包含了各種express所需的包),app.js(即入口文件)、pulic即其下面的一些文件夾用於存放相應的文件 ,以及路由等等。 如果不使用 express-generator ,我們就得自己一個一個的創建,這是相當麻煩的 。 另外,如果有不符合我們項目的地方,我們直接修改即可。 創建的同時提示首先 cd myapp (即進入myapp文件夾)然后 npm install(安裝package.json中的依賴項),完成依賴項的安裝(即package.json文件中的依賴項安裝)。我們可以看到package.json中的依賴項如下:
{ "name": "myapp", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.17.1", "cookie-parser": "~1.4.3", "debug": "~2.6.3", "express": "~4.15.2", "jade": "~1.11.0", "morgan": "~1.8.1", "serve-favicon": "~2.4.2" } }
其中body-parser在post請求時必須使用;cookie-parser在處理cookie時必須使用; debug模塊用於調試,類似於console.log; express就更不用說了;jade是模板引擎模塊,這對於服務器端語言還是非常重要的。morgan是一個日志模塊,用於在后台打印出req請求等等,方便我們在后台查看,這與debug模塊非常相似,但是debug模塊是用於取代console.log的,而morgan主要是用於查看請求的。serve-favicon不太懂,后面學習。
cd myapp
npm install
注意: 在安裝過程中,會提示jade已經更名為pug,也就是說兩者是一回事。
在安裝完了所有的依賴項之后,我們就可以使用下面的命令來啟動這個應用了:
set DEBUG=myapp:* & npm start
這里 set DEBUG=myapp:* & npm start 就可以來啟動了,而前者是說啟動debug模塊,打印一些debug日志方便我們管理后台。 注意:和*之間有空格和沒有空格是不同的。 這里使用的沒有空格。
另外,如果不希望使用debug模塊,像下面這樣就可以啟動了。
npm start
在瀏覽器中進入localhost:3000, 如下所示:
通過 Express 應用生成器創建的應用一般都有如下目錄結構:
(補充):其中routes文件是怎么是使用的呢?
其實不用routes文件當然也是可以的,但是在實際開發中, 路由文件動輒成百上千,如果全部放在 app.js 中, 不難想象app.js將會多么臃腫, 所以我們需要對於不同路徑的路由放在不同的文件下。
對於 express-generator 生成的模板, app.js 的內容如下:
var express = require('express'); var app = express(); var indexRouter = require('./routes/index'); var userRouter = require('./routes/users'); app.use('/', indexRouter); app.use('/users', userRouter); app.listen(3000);
然后進入routes下的index我們可以看到:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res) { res.send('hello, express'); }); module.exports = router;
其中router為express.Router() 的一個實例,這是非常重要的。
對於routes下的users也是一樣的:
var express = require('express'); var router = express.Router(); router.get('/:name', function(req, res) { res.send('hello, ' + req.params.name); }); module.exports = router;
這樣就可以很好的管理路由了,當然,不難看出,實際項目中,我們並不是真的只要這兩個路由,而是根據你的項目,可能除了 index、 users, 還有其他的文件,只要保證一個路徑對應一個路由文件即可,這樣便可很好地管理路由了。
重要的是需要在 app.js 中使用 app.use() 掛載到不同的路徑上。
易錯點: 在使用 app.use() 時,第一個參數是一個相對的路徑,然后使用第二個參數,即routes下的文件時, router.get("/"),這又是一個路徑,最終表現在url上是兩者的綜合路徑(疊加路徑),比如app.use('/reg', index); 其中在index下的路由文件中設置的是 router.get('/reg', function (req, res) {}) , 那么最終表現出來的就是localhost:8888/reg/reg 這樣的路由,這樣才能正確訪問, 否則就會出錯。
(補)debug模塊的使用。(參考教程)
在上面的例子中,我們使用 set DEBUG=myapp:* & npm start, 其中用到了debug模塊, 實際上debug模塊是怎么使用的呢?
nodejs的調試有很多,這里主要介紹debug模塊調試,首先npm init 、npm install debug --save, 新建app.js文件,其內容如下:
var debug = require("debug")("mydebug:http"), work = require("./work"), http = require("http"); http.createServer(function (req, res) { debug(req.method + " " + req.url); res.end("hello \n"); }).listen(3000, function () { debug("listening"); });
然后建立work.js,內容如下:
var debug = require("debug")("mydebug:work"); setInterval(function () { debug("doing some work @ %s - %s", new Date().toString(), "with supervisor"); }, 2000);
(注意:如果要debug, 就必須要在當前目錄下存在 npm-debug.log 日志文件)
可以看到,這兩個模塊中我都使用了 debug 模塊。 運行 set DEBUG=mydebug:* & node app.js ,如下:
即這里的debug語句就相當於console.log(),然后對於不同的debug,會顯示不同的顯色, 而mydebug是我設置的debug名稱,也可以是其他的。
開啟時使用的是 set DEBUG=mydebug:* & node app , 也就是說我們運行了所有的(*)debug模塊,如果我們只想運行work模塊,而不運行http模塊,可以像下面這樣:
set DEBUG=mydebug:work & node app
我們可以將&理解為並且的意思。(為什么不是&&呢?)
(補)npm start的使用原理
這里實際上是 npm run start 的簡寫。 參考 http://javascript.ruanyifeng.com/nodejs/packagejson.html
這里的npm start啟動的是bin目錄下的www,也就是說使用 express-generator 的默認的入口是 bin 下的 www, 而不是app.js ,目前很多項目都是如此, 我們可以在 package.json中進行設置,如 express-generator 中的設置如下:
{ "name": "myapp", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.17.1", "cookie-parser": "~1.4.3", "debug": "~2.6.3", "express": "~4.15.2", "jade": "~1.11.0", "morgan": "~1.8.1", "serve-favicon": "~2.4.2" } }
其中的 scripts 中設置了start(入口文件)為 node ./bin/www ,即當啟動項目時,實際上輸入的是 node ./bin/www ,只是這樣設置的好處在於更加方便管理。容易理解。
比如我們創建一個文件,npm init , 創建 app.js,內容如下:

var http = require("http"); http.createServer(function (req, res) { res.writeHead(200, {"Content-Type": "text/plain; charset=utf8"}); res.write("hello"); res.end(); }).listen(8888, function () { console.log("Server is running at port 127.0.0.1:8888"); });
然后node app即可啟動這個項目,但是app就在這當然比較好啟動,可如果入口文件藏的很深呢? 比如./bin/www就深了一層,每次 node ./bin/www可能會比較麻煩,所以在 package.json 中的scripts下添加了 start 選項,對於這個項目我們也可以添加,如下:
{ "name": "starttest", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node app.js" }, "author": "", "license": "ISC" }
即添加start項,每次npm start 就相當於 node app.js,這樣的效果是一樣的。
我們可以使用 npm run start 和 npm run test 都是可以執行的。 參考阮一峰教程。
(補:)package.json中的dependencies是什么? 作用如何? 和devDependencies的區別是什么?
即項目依賴項,用於告訴我們這個項目依賴了哪些模塊,但這不是主要作用, 主要作用是當我們將項目上傳到服務器時,可以不用上傳 node_modules ,而只用上傳基礎文件即可, 服務器端可以直接在根目錄下 npm install ,然后就可以安裝package.json中所有的依賴模塊了。
dev,顧名思義,是開發的意思, 而dependencies是指生產中所需要的依賴項, devDependencies 可能包括一些生產中不需要而僅在開發中需要的調試模塊。
3. 路由見了很多,到底什么是路由? 句柄又是什么? 路由中有哪些常用的響應方法?
簡單的理解,路由就是 路徑 + 一個http方法 + 一些句柄。 下面就是一個路由文件:
// 對網站首頁的訪問返回 "Hello World!" 字樣 app.get('/', function (req, res) { res.send('Hello World!'); }); // 網站首頁接受 POST 請求 app.post('/', function (req, res) { res.send('Got a POST request'); }); // /user 節點接受 PUT 請求 app.put('/user', function (req, res) { res.send('Got a PUT request at /user'); }); // /user 節點接受 DELETE 請求 app.delete('/user', function (req, res) { res.send('Got a DELETE request at /user');
其中app是一個express實例, 同時包含了路徑(/ 、user)和http方法(get、post、delete、put)。
有時候我們還會見到app.all() ,這是指不論是什么方法,只要路徑對了,就會執行后面的句柄。
那么什么是句柄呢? 其實句柄就是指其中的語句, 執行的函數。。。
而路由中一定是有響應方法的,比如之前一直使用的 res.send() 這樣可以把其中的內容返回給頁面,另外,還有下面的一些方法:
其中 res.end() 也比較常用,表示發送結束了。 res.download() 方法接受一個參數是文件的相對路由, 一旦滿足路由,就會下載文件。 res.json() 即接受一個json字符串。如下:
var express = require("express"); var fs = require("fs"); var app = express(); app.get("/", function (req, res) { res.json('{"name": "John Zhu"}'); res.end(); }); app.get("/login", function (req, res) { res.download("./test.txt"); }); app.listen(3000, function () { console.log("Server is running at localhost:3000"); });
另外,對於相同的路徑,根據不同的請求方法給出不同的句柄,我們應該怎么實現呢? 可以是下面這樣:
app.get("/", function (req, res) { res.send("get"); }); app.post("/", function (req, res) { res.send("post"); }); app.delete("/", function (req, res) { res.send("delete"); }); app.put("/", function (req, res) { res.send("put"); });
但是這樣顯然代碼是冗余的,並且容易造成拼寫錯誤,如果使用 app.route() 使用鏈式定義會更好,如下:
app.route("/") .get(function (req, res) { res.send("get"); }) .post(function (req, res) { res.send("post"); }) .delete(function (req, res) { res.send("delete"); }) .put(function (req, res) { res.send("put"); });
這樣更容易查看並且不容易出錯。
4. express中靜態文件是什么?
錯! 靜態文件是node中的概念,而不僅僅是express中的。 它的作用就是托管靜態文件。 什么是靜態文件呢? 比如我們看到一個網站上(如網易)的一個圖片,然后復制圖片地址,如http://img3.cache.netease.com/photo/0001/2017-04-21/CII56AJH19BR0001.jpg ,打開這個連接,發現這就是一個圖片, 而這,就是靜態文件。 如我們再引入圖片、css、js等時,這些文件都是靜態文件,由此可知,靜態文件的重要性。
在使用express-generator生成應用的時候,我們就可以在那個架構中看到 public 文件,這個文件就是靜態文件,其中包含了images、javascripts、stylesheets。 使用如下:
app.use(express.static("public"));
重要聲明: 其中app是一個express實例,而use就代表使用一個中間件。即使用express.static()中間件。其中一定是express而不是app,這是需要格外注意的地方。
5. 剛剛也提到了中間件這個概念,那么到底什么是中間件? 怎么理解中間件中的next()方法?
在知乎上有這么一個回答,就照搬過來吧~
毫無疑問,這里的中間件的定義是便於我們理解的,我們看看官網上是怎么說的吧~
Express 是一個自身功能極簡,完全是由路由和中間件構成一個的 web 開發框架:從本質上來說,一個 Express 應用就是在調用各種中間件。
中間件(Middleware) 是一個函數,它可以訪問請求對象(request object (
req
)), 響應對象(response object (res
)), 和 web 應用中處於請求-響應循環流程中的中間件,一般被命名為next
的變量。
在express中可以使用下面的幾種中間件:
應用級中間件:即我們使用的app.use() 和 app.get()之類的中間件。 通過這個我們不難理解,express的確完全是使用中間件搭建起來的。 因為app.METHOD()在服務器端語言node中使用的很多。
路由級中間件:即app.Router()的中間件
錯誤處理中間件:即函數的參數必須要四個,分別是 error、req、res和next。 error就是用來處理錯誤的。
內置中間件:express中唯一內置的中間件就是 express.static()了。
第三方中間件:如cookie-parser 這樣的中間件就是第三方中間件。 還有body-parser也是的。
6. 如何在Express中使用模板引擎?
說明:模板引擎有很多,比如 jade (下面主要說的)、 ejs (也是非常常用的)等等很多。
兩個步驟就可以讓express來渲染模板文件:
第一: 添加放置模板的目錄views, 然后app.set('views', './views');
第二: 添加模板引擎即views engine, 然后app.set('view engine', 'jade');
當然前提條件是有相應的模板引擎安裝包:
npm install jade --save
之前我們使用 express-generator 的時候就可以發現目錄下已經有了 views 目錄, 同時 package.json 中也是有依賴項jade 的, 利用之我們可以看到在app.js中它是這樣設置的。
// view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade');
當然這是一樣的, set函數接受兩個參數,第一個是要設置的東西,這里是模板文件(views), 第二個參數是一個路徑, 不難理解,path.join() 就是為了將兩個path組合到一起 。 當然,前提是引入path模塊。
對於第二句使用jade模板引擎都是一樣的。
然后我們再 views 下面生成以及模板文件, 如generator生成的文件就是 index.jade、layout.jade、 error.jade 這三個模板引擎。其中layout.jade如下所示:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content
這個模板引擎的特有語法, 更加簡潔。 在body下有一個block content, 這時就要根據不同的狀態使用 index.jade 和 error.jade了, 如index.jade內容如下:
extends layout block content h1= title p Welcome to #{title} #{title}
很容易看出來它是對 layout.jade 的擴展, 其定義了block content的內容, 其中的title就是可以替換的變量,后面會講到。而error.jade的內容如下:
extends layout block content h1= message h2= error.status pre #{error.stack}
同樣,這也是對於layout.jade 的擴展。
在哪里控制這個渲染的呢? 顯然這些都與路由有關,所以在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;
即使用了res.render()渲染index這個模板引擎(在擴展layout的基礎上),並給這個模板引擎傳遞參數title: 'Express', 這是以對象的形式傳遞的所以說我們還可以傳遞更多的參數。
更一般的理解: 一旦get方法訪問了主頁,就會將index.jade模板引擎渲染為html頁面返回給用戶。
開頭就說了,模板引擎有很多,ejs 就是其中一種, 因為它在使用起來非常簡單, 並且與 express 集成良好, 所以我們選用 ejs . (ejs官方文檔)
在 express-generator 中自動生成的是jade, 所以如果不實用jade而是使用 ejs 的話,我們就要單獨安裝了,如下所示:
npm install ejs --save
然后,我們需要在 app.js 中添加下面的代碼:
app.set('views', path.join(__dirname, 'views')); app.set('views engine', 'ejs');
app.set('view engine', 'ejs');
也就是說使用views作為放置模板的目錄。 使用ejs作為模板引擎。
注意: 設置路徑時是 views ,因為模板不止一個,但是在設置模板引擎時,一定是 view engine ,而不能加s, 否則就會報錯。
另外,在app.js中不需要 require("ejs") ,如果require()了也不會報錯。
接下來開始設置模板, 在 views 下面添加 user.ejs,其中內容如下:
<!DOCTYPE html> <html> <head> <style type="text/css"> body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;} </style> </head> <body> <h1><%= name.toUpperCase() %></h1> <p>hello, <%= name %></p> </body> </html>
這就是ejs的格式, 使用 <% 變量 %>將變量包裹起來。可以看到,在模板中我使用了 name.toUpperCase() 這樣的js語句。
修改 routes下的 user.js 內容如下:
var express = require('express'); var router = express.Router(); router.get('/:name', function(req, res) { res.render('users', { name: req.params.name }); }); module.exports = router;
這樣就可以成功渲染一個html頁面了。即通過 res.render() 來渲染 ejs 模板, res.render() 的第一個參數是模板的名字,這里的user會匹配 views/user.ejs , 第二個參數是傳給模板的數據,這里傳入了name,那么在模板中就可以使用 name 這個變量了, 所以res.render() 的作用就是將模板和數據結合成HTML,同時在響應頭中設置 {"Content-Type: text/html"} 告訴瀏覽器我渲染的是一個html頁面,而不是文本。
補充說明: ejs有下面幾種常用的標簽:
- <% code %> 運行js代碼,不輸出
- <%= code %> 顯示轉義后的html內容
- <%- code %> 顯示原始html內容
下面的例子解釋了 <% code %>的用法:
DATA:
supplies: ['mop', 'broom', 'duster']
EJS TEMPLATE:
<ul> <% for(var i=0; i<supplies.length; i++) {%> <li><%= supplies[i] %></li> <% } %> </ul>
RESULT:
<ul> <li>mop</li> <li>broom</li> <li>duster</li> </ul>
ejs --- include
我們使用模板通常並不是一個頁面對應一個模板,這樣模板的優勢就失去了。而是把模板拆成可以復用的模板片段組合使用 (這正是我想要的)。
比如我在views下新建了 header.ejs 和 footer.ejs, 並修改了 user.ejs,如下所示:
views/header.ejs
<!DOCTYPE html> <html> <head> <style type="text/css"> body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;} </style> </head> <body>
views/footer.ejs
</body> </html>
views/user.ejs
<%- include('header') %> <h1><%= name.toUpperCase() %></h1> <p>hello, <%= name %></p> <%- include('footer') %>
即我們通過 incluede 方式引入了footer和header,這樣,如果文件多了,利用率會更高一些。 注意: 其中 <%- ... %> 表示使用原始數據,即原來是啥就是啥。
說明: 拆分模板組件的兩個好處
- 模板可以復用,減少重復代碼
- 主模板更加清晰。
7.怎么理解錯誤處理? 如何進行錯誤處理?
錯誤處理? 即請求發生錯誤,或者是響應錯誤時,給出一定的處理方案: 如給用戶提示錯誤信息等等。。。
錯誤處理也是一種中間件,之前我們就說過,express框架就是使用一大推中間件堆積起來的,錯誤處理中間件是一個函數, 必須有四個參數,分別是 err req res next, 值得注意的是,我們必須要在其他中間件定義完了之后,然后在定義錯誤處理中間件。 如下:
var bodyParser = require('body-parser'); var methodOverride = require('method-override'); app.use(bodyParser()); app.use(methodOverride()); app.use(function(err, req, res, next) { // 業務邏輯 });
我們可以自己測驗一下,如下所示:
var express = require("express"); var app = express(); app.get('/', function (req, res) { // 這個函數沒有定義會發生錯誤。 god(); }); app.get('/login', function (req, res) { res.send("登錄成功!"); }); app.use(function (err, req, res, next) { res.send("錯誤發生:" + err); }); app.listen(3000, function () { console.log("server is running at localhost:3000..."); });
在這里, 我們在‘/’定義了一個 get 請求,然后執行一個沒有定義的函數,那么這一定會出錯,然后不過不使用 app.use(function (err, req, res, next) {...}) 那么錯誤界面就會很亂甚至導致后台崩潰,但是如果我們使用了異常處理中間件, 就會發現, 可以通過它捕獲到請求。 值得注意的是: 異常處理中間件一定要放在最后(listen之前), 這樣就可以成功的異常處理了。
如下:
錯誤發生:ReferenceError: god is not defined
當然我們也可以打印出 錯誤棧(error stack), 即
res.send("錯誤發生:" + err.stack);
效果如下:
8. nodejs作為后台語言,怎么集成數據庫?
的確,后台語言大半時間也是為了和后台打交道的,所以數據庫的連接和使用格外重要。 要為nodejs連接數據庫,只需要添加相應的驅動即可。下面是一些常用的數據庫node模塊。
這里主要講MySQL 和 MongoDB,如下所示:
MySQL --- 首先安裝mysql模塊:
npm install mysql
然后連接數據庫:
var mysql = require('mysql'); var connection = mysql.createConnection({ host : 'localhost', user : 'dbuser', password : 's3kreee7' }); connection.connect(); connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) { if (err) throw err; console.log('The solution is: ', rows[0].solution); }); connection.end();
MongoDB --- 首先安裝mongoskin模塊:
npm install mongoskin
然后連接數據庫:
var db = require('mongoskin').db('localhost:27017/animals'); db.collection('mamals').find().toArray(function(err, result) { if (err) throw err; console.log(result); });
8. app.get("/logim/:name", function () {}); 是什么意思? 那里的:的作用是什么?
其中:name是占位符的意思,如:
app.get("/login/:name", function (req, res) { res.send(req.params.name); });
如果我們輸入 localhost:8888/login/hhh ,那么瀏覽器上就會顯示hhh,因為我們可以通過 req.params.name 讀取到這個占位符。
推薦github項目: https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md
結束