Node.js開發Web后台服務


一、簡介

Node.js 是一個基於Google Chrome V8 引擎的 JavaScript 運行環境。Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。Node.js 的包管理器 npm,是全球最大的開源庫生態系統。

能方便地搭建響應速度快、易於擴展的網絡應用,Node.js 使用事件驅動, 非阻塞I/O 模型而得以輕量和高效,非常適合在分布式設備上運行的數據密集型的實時應用。

官網:https://nodejs.org/en/
中文:https://cnodejs.org/http://nodejs.cn/

API:http://nodeapi.ucdok.com/#/api/

簡單說Node.js就是運行在服務器端的JavaScript,是現在流行的語言中能同時運行在前端與后台的程序語言,你可以把JavaScript想像成Java與C#。相關技術:

數據庫:MongoDB,非關系型數據庫,NoSQL(Not only SQL)

MVC框架:AngularJS

Web服務器:Express

模板引擎:jade、ejs、htmljs、swig、hogan.js

二、搭建Node.js開發環境

2.1、安裝Node.js

去官網下下載最新版本的Node.js一步一步按提示安裝即可,如果安裝失敗就手動安裝,將Node.js的安裝位置配置到環境變量的path中。

安裝完成后啟動命令行,測試:

2.2、安裝IDE開發Node.js插件

如果不使用IDE開發項目效率較低,在很多主流的集成開發環境(IDE)中都可以安裝插件支持Node.js開發,如Eclipse,這里我們以HBuilder為例:

啟動HBuilder->工具->插件安裝

安裝成功后就可以新建Node.js項目了:

這里選擇Hello World,新建好的項目如下:

hello-world-server.js文件就是一個簡單的web服務器,右鍵選擇“運行方式”->"Node Application"

控制台提示“Server running at http://127.0.0.1:1337/”在瀏覽器查看的效果如下:

三、第一個Node.js程序

在上面的示例中,我們是通過IDE完成編譯與運行的,其實手動運行也可以,比如編寫一段代碼如下:

server.js

//依賴一個http模塊,相當於java中的import,與C#中的using
var http = require('http');

//創建一個服務器對象
server = http.createServer(function (req, res) {
//設置請求成功時響應頭部的MIME為純文本
res.writeHeader(200, {"Content-Type": "text/plain"});
//向客戶端輸出字符
res.end("Hello World\n");
});
//讓服務器監聽本地8000端口開始運行
server.listen(8000,'127.0.0.1');
console.log("server is runing at 127.0.0.1:8000");

在node環境下解釋運行:

運行結果:

引入 required 模塊:我們可以使用 require 指令來載入 Node.js 模塊。
創建服務器:服務器可以監聽客戶端的請求,類似於TomCat、IIS、Apache 、Nginx 等 HTTP 服務器。
接收請求與響應請求 服務器很容易創建,客戶端可以使用瀏覽器或終端發送 HTTP 請求,服務器接收請求后返回響應數據。

第一行請求(require)Node.js 自帶的 http 模塊,並且把它賦值給 http 變量。
接下來我們調用 http 模塊提供的函數: createServer 。這個函數會返回 一個對象,這個對象有一個叫做 listen 的方法,這個方法有一個數值參數, 指定這個 HTTP 服務器監聽的端口號。

四、NPM(Node.js包管理器)

NPM是隨同NodeJS一起安裝的包管理工具,能解決NodeJS代碼部署上的很多問題,常見的使用場景有以下幾種:
a)、允許用戶從NPM服務器下載別人編寫的第三方包到本地使用。
b)、允許用戶從NPM服務器下載並安裝別人編寫的命令行程序到本地使用。
c)、允許用戶將自己編寫的包或命令行程序上傳到NPM服務器供別人使用。

官網:https://www.npmjs.com/

4.1、查看npm版本
由於新版的nodejs已經集成了npm,所以之前npm也一並安裝好了。同樣可以通過輸入 "npm -v" 來測試是否成功安裝。命令如下,出現版本提示表示安裝成功:

4.2、升級npm

如果你安裝的是舊版本的 npm,可以很容易得通過 npm 命令來升級

npm install npm -g

 

4.3、安裝模塊

npm install <Module Name> -參數

如果帶參數-g表示全局安裝,否則只是安裝到某個目錄下。

以下實例,我們使用 npm 命令安裝常用的 Node.js web框架模塊 express

4.4、卸載模塊

我們可以使用以下命令來卸載 Node.js 模塊。
npm uninstall <Module Name>

如先使用安裝指令安裝bootstrap:

npm install bootstrap

再使用卸載指令刪除模塊:

npm uninstall bootstrap

可以到 /node_modules/ 目錄下查看包是否還存在

4.5、模塊列表

使用模塊列表命令可以方便的看到當前項目中依賴的包:
npm ls

4.6、更新模塊

我們可以使用以下命令更新模塊:
npm update 模塊名稱
npm up -g 模塊名稱

4.7、搜索模塊

npm search 模塊名稱

4.8、NPM 常用命令

除了本章介紹的部分外,NPM還提供了很多功能,package.json里也有很多其它有用的字段。
除了可以在npmjs.org/doc/查看官方文檔外,這里再介紹一些NPM常用命令。
NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。
NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。
使用npm help <command>可查看某條命令的詳細幫助,例如npm help install。
在package.json所在目錄下使用npm install . -g可先在本地安裝當前命令行程序,可用於發布前的本地測試。
使用npm update <package>可以把當前目錄下node_modules子目錄里邊的對應模塊更新至最新版本。
使用npm update <package> -g可以把全局安裝的對應命令行程序更新至最新版。
使用npm cache clear可以清空NPM本地緩存,用於對付使用相同版本號發布新版本代碼的人。
使用npm unpublish <package>@<version>可以撤銷發布自己發布過的某個版本代碼。

4.9、更換NPM 鏡像

因為npm的服務器在國外,在網絡狀態不好的情況下引入一個模塊會因為網絡延遲而失敗,可以更換成國內速度更快的鏡像服務器,這里以使用淘寶 NPM 鏡像(http://npm.taobao.org/)為例:

npm install -g cnpm --registry=https://registry.npm.taobao.org

這樣就可以使用 cnpm 命令來安裝模塊了:
$ cnpm install [name]

這是一個完整 npmjs.org 鏡像,你可以用此代替官方版本(只讀),同步頻率目前為 10分鍾 一次以保證盡量與官方服務同步。

如是安裝失敗,可以試試:

alias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"

# Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=https://registry.npm.taobao.org \
  --cache=$HOME/.npm/.cache/cnpm \
  --disturl=https://npm.taobao.org/dist \
  --userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc

4.10、package.json

4.10.1、概述

每個項目的根目錄下面,一般都有一個package.json文件,定義了這個項目所需要的各種模塊,以及項目的配置信息(比如名稱、版本、許可證等元數據)。npm install命令根據這個配置文件,自動下載所需的模塊,也就是配置項目所需的運行和開發環境。

下面是一個最簡單的package.json文件,只定義兩項元數據:項目名稱和項目版本。

{
  "name" : "xxx",
  "version" : "0.0.0",
}

package.json文件就是一個JSON對象,該對象的每一個成員就是當前項目的一項設置。比如name就是項目名稱,version是版本(遵守“大版本.次要版本.小版本”的格式)。

下面是一個更完整的package.json文件。

{
    "name": "Hello World",
    "version": "0.0.1",
    "author": "張三",
    "description": "第一個node.js程序",
    "keywords":["node.js","javascript"],
    "repository": {
        "type": "git",
        "url": "https://path/to/url"
    },
    "license":"MIT",
    "engines": {"node": "0.10.x"},
    "bugs":{"url":"http://path/to/bug","email":"bug@example.com"},
    "contributors":[{"name":"李四","email":"lisi@example.com"}],
    "scripts": {
        "start": "node index.js"
    },
    "dependencies": {
        "express": "latest",
        "mongoose": "~3.8.3",
        "handlebars-runtime": "~1.0.12",
        "express3-handlebars": "~0.5.0",
        "MD5": "~1.2.0"
    },
    "devDependencies": {
        "bower": "~1.2.8",
        "grunt": "~0.4.1",
        "grunt-contrib-concat": "~0.3.0",
        "grunt-contrib-jshint": "~0.7.2",
        "grunt-contrib-uglify": "~0.2.7",
        "grunt-contrib-clean": "~0.5.0",
        "browserify": "2.36.1",
        "grunt-browserify": "~1.3.0",
    }
}

下面詳細解釋package.json文件的各個字段。

成熟示例:

{
  "_args": [
    [
      {
        "raw": "md5@^2.2.1",
        "scope": null,
        "escapedName": "md5",
        "name": "md5",
        "rawSpec": "^2.2.1",
        "spec": ">=2.2.1 <3.0.0",
        "type": "range"
      },
      "E:\\NF\\vue\\demos\\demo02"
    ]
  ],
  "_from": "md5@>=2.2.1 <3.0.0",
  "_id": "md5@2.2.1",
  "_inCache": true,
  "_location": "/md5",
  "_nodeVersion": "4.4.5",
  "_npmOperationalInternal": {
    "host": "packages-16-east.internal.npmjs.com",
    "tmp": "tmp/md5-2.2.1.tgz_1472679629604_0.48944878415204585"
  },
  "_npmUser": {
    "name": "pvorb",
    "email": "paul@vorba.ch"
  },
  "_npmVersion": "3.9.3",
  "_phantomChildren": {},
  "_requested": {
    "raw": "md5@^2.2.1",
    "scope": null,
    "escapedName": "md5",
    "name": "md5",
    "rawSpec": "^2.2.1",
    "spec": ">=2.2.1 <3.0.0",
    "type": "range"
  },
  "_requiredBy": [
    "#USER",
    "/"
  ],
  "_resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
  "_shasum": "53ab38d5fe3c8891ba465329ea23fac0540126f9",
  "_shrinkwrap": null,
  "_spec": "md5@^2.2.1",
  "_where": "E:\\NF\\vue\\demos\\demo02",
  "author": {
    "name": "Paul Vorbach",
    "email": "paul@vorba.ch",
    "url": "http://paul.vorba.ch"
  },
  "bugs": {
    "url": "https://github.com/pvorb/node-md5/issues"
  },
  "contributors": [
    {
      "name": "salba"
    }
  ],
  "dependencies": {
    "charenc": "~0.0.1",
    "crypt": "~0.0.1",
    "is-buffer": "~1.1.1"
  },
  "description": "js function for hashing messages with MD5",
  "devDependencies": {
    "mocha": "~2.3.4"
  },
  "directories": {},
  "dist": {
    "shasum": "53ab38d5fe3c8891ba465329ea23fac0540126f9",
    "tarball": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz"
  },
  "gitHead": "5536a25dbe856b25d9963fd47da5eb4e1bca4250",
  "homepage": "https://github.com/pvorb/node-md5#readme",
  "license": "BSD-3-Clause",
  "main": "md5.js",
  "maintainers": [
    {
      "name": "coolaj86",
      "email": "coolaj86@gmail.com"
    },
    {
      "name": "pvorb",
      "email": "paul@vorba.ch"
    }
  ],
  "name": "md5",
  "optionalDependencies": {},
  "readme": "ERROR: No README data found!",
  "repository": {
    "type": "git",
    "url": "git://github.com/pvorb/node-md5.git"
  },
  "scripts": {
    "test": "mocha"
  },
  "tags": [
    "md5",
    "hash",
    "encryption",
    "message digest"
  ],
  "version": "2.2.1"
}
View Code
{
  "_args": [
    [
      {
        "raw": "charenc@~0.0.1",
        "scope": null,
        "escapedName": "charenc",
        "name": "charenc",
        "rawSpec": "~0.0.1",
        "spec": ">=0.0.1 <0.1.0",
        "type": "range"
      },
      "E:\\NF\\vue\\demos\\demo02\\node_modules\\md5"
    ]
  ],
  "_from": "charenc@>=0.0.1 <0.1.0",
  "_id": "charenc@0.0.2",
  "_inCache": true,
  "_location": "/charenc",
  "_nodeVersion": "4.4.5",
  "_npmOperationalInternal": {
    "host": "packages-12-west.internal.npmjs.com",
    "tmp": "tmp/charenc-0.0.2.tgz_1482450158427_0.9801697849761695"
  },
  "_npmUser": {
    "name": "pvorb",
    "email": "paul@vorba.ch"
  },
  "_npmVersion": "3.9.3",
  "_phantomChildren": {},
  "_requested": {
    "raw": "charenc@~0.0.1",
    "scope": null,
    "escapedName": "charenc",
    "name": "charenc",
    "rawSpec": "~0.0.1",
    "spec": ">=0.0.1 <0.1.0",
    "type": "range"
  },
  "_requiredBy": [
    "/md5"
  ],
  "_resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
  "_shasum": "c0a1d2f3a7092e03774bfa83f14c0fc5790a8667",
  "_shrinkwrap": null,
  "_spec": "charenc@~0.0.1",
  "_where": "E:\\NF\\vue\\demos\\demo02\\node_modules\\md5",
  "author": {
    "name": "Paul Vorbach",
    "email": "paul@vorb.de",
    "url": "http://vorb.de"
  },
  "bugs": {
    "url": "https://github.com/pvorb/node-charenc/issues"
  },
  "dependencies": {},
  "description": "character encoding utilities",
  "devDependencies": {},
  "directories": {},
  "dist": {
    "shasum": "c0a1d2f3a7092e03774bfa83f14c0fc5790a8667",
    "tarball": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz"
  },
  "engines": {
    "node": "*"
  },
  "gitHead": "01d66efb429d0cb242b2dd5af2ce338554fd3e54",
  "homepage": "https://github.com/pvorb/node-charenc#readme",
  "license": "BSD-3-Clause",
  "main": "charenc.js",
  "maintainers": [
    {
      "name": "pvorb",
      "email": "paul@vorb.de"
    }
  ],
  "name": "charenc",
  "optionalDependencies": {},
  "readme": "ERROR: No README data found!",
  "repository": {
    "type": "git",
    "url": "git://github.com/pvorb/node-charenc.git"
  },
  "scripts": {},
  "tags": [
    "utf8",
    "binary",
    "byte",
    "string"
  ],
  "version": "0.0.2"
}
View Code

 

4.10.2、scripts字段

scripts指定了運行腳本命令的npm命令行縮寫,比如start指定了運行npm run start時,所要執行的命令。

下面的設置指定了npm run preinstallnpm run postinstallnpm run startnpm run test時,所要執行的命令。

"scripts": {
    "preinstall": "echo here it comes!",
    "postinstall": "echo there it goes!",
    "start": "node index.js",
    "test": "tap test/*.js"
}

4.10.3、dependencies字段,devDependencies字段

dependencies字段指定了項目運行所依賴的模塊,devDependencies指定項目開發所需要的模塊。

它們都指向一個對象。該對象的各個成員,分別由模塊名和對應的版本要求組成,表示依賴的模塊及其版本范圍。

{
  "devDependencies": {
    "browserify": "~13.0.0",
    "karma-browserify": "~5.0.1"
  }
}

對應的版本可以加上各種限定,主要有以下幾種:

  • 指定版本:比如1.2.2,遵循“大版本.次要版本.小版本”的格式規定,安裝時只安裝指定版本。
  • 波浪號(tilde)+指定版本:比如~1.2.2,表示安裝1.2.x的最新版本(不低於1.2.2),但是不安裝1.3.x,也就是說安裝時不改變大版本號和次要版本號。
  • 插入號(caret)+指定版本:比如?1.2.2,表示安裝1.x.x的最新版本(不低於1.2.2),但是不安裝2.x.x,也就是說安裝時不改變大版本號。需要注意的是,如果大版本號為0,則插入號的行為與波浪號相同,這是因為此時處於開發階段,即使是次要版本號變動,也可能帶來程序的不兼容。
  • latest:安裝最新版本。

package.json文件可以手工編寫,也可以使用npm init命令自動生成。

$ npm init

這個命令采用互動方式,要求用戶回答一些問題,然后在當前目錄生成一個基本的package.json文件。所有問題之中,只有項目名稱(name)和項目版本(version)是必填的,其他都是選填的。

有了package.json文件,直接使用npm install命令,就會在當前目錄中安裝所需要的模塊。

$ npm install

如果一個模塊不在package.json文件之中,可以單獨安裝這個模塊,並使用相應的參數,將其寫入package.json文件之中。

$ npm install express --save
$ npm install express --save-dev

上面代碼表示單獨安裝express模塊,--save參數表示將該模塊寫入dependencies屬性,--save-dev表示將該模塊寫入devDependencies屬性。

示例:

 package.json

{
  "name": "demo001",
  "version": "1.1.2",
  "description": "vueclidemo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "keywords": [
    "vue",
    "vue-cli"
  ],
  "author": "zhangguo",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://git.dev.tencent.com/zhangguo5/vuedemo001.git"
  },
  "dependencies": {
    "md5": "^2.2.1"
  },
  "devDependencies": {
    "lodash": "^4.17.15"
  }
}

index.js

var md5=require("md5");
var _=require("lodash");
function Cat(){
    console.log(md5("Hello Cat!"));
}

Cat();

var users = [
    { 'user': 'fred',   'age': 48 },
    { 'user': 'barney', 'age': 36 },
    { 'user': 'fred',   'age': 40 },
    { 'user': 'barney', 'age': 34 }
  ];

 var result=_.sortBy(users,['user','age']);
 console.log(result);

運行結果:

4.10.4、peerDependencies

有時,你的項目和所依賴的模塊,都會同時依賴另一個模塊,但是所依賴的版本不一樣。比如,你的項目依賴A模塊和B模塊的1.0版,而A模塊本身又依賴B模塊的2.0版。

大多數情況下,這不構成問題,B模塊的兩個版本可以並存,同時運行。但是,有一種情況,會出現問題,就是這種依賴關系將暴露給用戶。

最典型的場景就是插件,比如A模塊是B模塊的插件。用戶安裝的B模塊是1.0版本,但是A插件只能和2.0版本的B模塊一起使用。這時,用戶要是將1.0版本的B的實例傳給A,就會出現問題。因此,需要一種機制,在模板安裝的時候提醒用戶,如果A和B一起安裝,那么B必須是2.0模塊。

peerDependencies字段,就是用來供插件指定其所需要的主工具的版本。

{
  "name": "chai-as-promised",
  "peerDependencies": {
    "chai": "1.x"
  }
}

上面代碼指定,安裝chai-as-promised模塊時,主程序chai必須一起安裝,而且chai的版本必須是1.x。如果你的項目指定的依賴是chai的2.0版本,就會報錯。

注意,從npm 3.0版開始,peerDependencies不再會默認安裝了。

4.10.5、bin字段

bin項用來指定各個內部命令對應的可執行文件的位置。

"bin": {
  "someTool": "./bin/someTool.js"
}

上面代碼指定,someTool 命令對應的可執行文件為 bin 子目錄下的 someTool.js。Npm會尋找這個文件,在node_modules/.bin/目錄下建立符號鏈接。在上面的例子中,someTool.js會建立符號鏈接npm_modules/.bin/someTool。由於node_modules/.bin/目錄會在運行時加入系統的PATH變量,因此在運行npm時,就可以不帶路徑,直接通過命令來調用這些腳本。

因此,像下面這樣的寫法可以采用簡寫。

scripts: {  
  start: './node_modules/someTool/someTool.js build'
}

// 簡寫為

scripts: {  
  start: 'someTool build'
}

所有node_modules/.bin/目錄下的命令,都可以用npm run [命令]的格式運行。在命令行下,鍵入npm run,然后按tab鍵,就會顯示所有可以使用的命令。

4.10.6、main字段

main字段指定了加載的入口文件,require('moduleName')就會加載這個文件。這個字段的默認值是模塊根目錄下面的index.js

4.10.7、config 字段

config字段用於添加命令行的環境變量。

下面是一個package.json文件。

{
  "name" : "foo",
  "config" : { "port" : "8080" },
  "scripts" : { "start" : "node server.js" }
}

然后,在server.js腳本就可以引用config字段的值。

http
  .createServer(...)
  .listen(process.env.npm_package_config_port)

用戶執行npm run start命令時,這個腳本就可以得到值。

$ npm run start

用戶可以改變這個值。

$ npm config set foo:port 80

4.10.8、browser字段

browser指定該模板供瀏覽器使用的版本。Browserify這樣的瀏覽器打包工具,通過它就知道該打包那個文件。

"browser": {
  "tipso": "./node_modules/tipso/src/tipso.js"
},

4.10.9、engines 字段

engines字段指明了該模塊運行的平台,比如 Node 的某個版本或者瀏覽器。

{ "engines" : { "node" : ">=0.10.3 <0.12" } }

該字段也可以指定適用的npm版本。

{ "engines" : { "npm" : "~1.0.20" } }

4.10.10、man字段

man用來指定當前模塊的man文檔的位置。

"man" :[ "./doc/calc.1" ]

4.10.11、preferGlobal字段

preferGlobal的值是布爾值,表示當用戶不將該模塊安裝為全局模塊時(即不用–global參數),要不要顯示警告,表示該模塊的本意就是安裝為全局模塊。

4.10. 12、style字段

style指定供瀏覽器使用時,樣式文件所在的位置。樣式文件打包工具parcelify,通過它知道樣式文件的打包位置。

"style": [
  "./node_modules/tipso/src/tipso.css"
]

五、Express

Express 是一個簡潔而靈活的 node.js Web應用框架, 提供了一系列強大特性幫助你創建各種 Web 應用,和豐富的 HTTP 工具。
使用 Express 可以快速地搭建一個完整功能的網站。使用Node.js作為AngularJS開發Web服務器的最佳方式是使用Express模塊。

Express官網: http://expressjs.com/

Express4.x API:http://expressjs.com/zh-cn/4x/api.html

5.2、Express框架核心特性

可以設置中間件來響應 HTTP 請求。

定義了路由表用於執行不同的 HTTP 請求動作。

可以通過向模板傳遞參數來動態渲染 HTML 頁面。

豐富的 HTTP 快捷方法和任意排列組合的 Connect 中間件,讓你創建健壯、友好的 API 變得既快速又簡單。

Express 不對 Node.js 已有的特性進行二次抽象,我們只是在它之上擴展了 Web 應用所需的基本功能。

5.3、安裝 Express

安裝 Express 並將其保存到依賴列表中:

npm install express --save

以上命令全局安裝express。也可安裝時指定安裝中間件。

body-parser - node.js 中間件,用於處理 JSON, Raw, Text 和 URL 編碼的數據。

cookie-parser - 這就是一個解析Cookie的工具。通過req.cookies可以取到傳過來的cookie,並把它們轉成對象。

multer - node.js 中間件,用於處理 enctype="multipart/form-data"(設置表單的MIME編碼)的表單數據。

$ npm install body-parser --save
$ npm install cookie-parser --save
$ npm install multer --save

默認這些模塊都已經添加。

5.4、第一個Express框架實例

接下來我們使用 Express 框架來輸出 "Hello World"。
以下實例中我們引入了 express 模塊,並在客戶端發起請求后,響應 "Hello World" 字符串。

創建一個目錄,如Project,進入命令行:

使用npm install express 導入express模塊。

在目錄下創建hello.js文件,如下所示:

//引入express模塊
var express = require('express');
//創建一個app對象,類似一個web 應用(網站)
var app = express();
//接受指定路徑的請求,指定回調函數
app.get('/', function (req, res){
res.send('Hello World');
});
//創建一個web服務器,可以認為就是web服務器對象
//監聽8081端口,當監聽成功時回調
var server = app.listen(8081, function () {
   var host = server.address().address;  //地址
   var port = server.address().port;  //端口
    console.log("應用實例,訪問地址為 http://%s:%s", host, port);
});
})

使用node執行js:

運行結果:

5.5、使用Nodeclipse開發Express項目

如果直接使用記事本效率會不高,nodeclipse插件可以方便的創建一個Express項目,步驟如下:

創建好的項目如下:

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 index = require('./routes/index');
var users = require('./routes/users');

var app = express();

//指定視圖引擎為ejs
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('/', index);
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 handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

bin\www是web服務器:

#!/usr/bin/env node

/**
 * 依賴模塊,導入
 */

var app = require('../app');
var debug = require('debug')('nodejsexpress:server');
var http = require('http');

/**
 * 從上下文環境中獲得監聽端口,如果空則3000
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * 創建Web服務器
 */

var server = http.createServer(app);

/**
 * 開始監聽
 */

server.listen(port);
server.on('error', onError);  //指定發生錯誤時的事件
server.on('listening', onListening);  //當監聽成功時的回調

/**
 * 規范化端口
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 *錯誤事件監聽
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  //錯誤處理
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);  //結束程序
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * 當用戶訪問服務器成功時的回調
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

routers/index.js路由,有點類似控制器或Servlet:

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

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

module.exports = router;

views/index.ejs首頁視圖:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

在www上右鍵選擇“運行方式”->“Node Application”運行結果:

5.5.1、request對象

Request 對象 - request 對象表示 HTTP 請求,包含了請求查詢字符串,參數,內容,HTTP 頭部等屬性。常見屬性有:

req.app:當callback為外部文件時,用req.app訪問express的實例
req.baseUrl:獲取路由當前安裝的URL路徑
req.body / req.cookies:獲得「請求主體」/ Cookies
req.fresh / req.stale:判斷請求是否還「新鮮」
req.hostname / req.ip:獲取主機名和IP地址
req.originalUrl:獲取原始請求URL
req.params:獲取路由的parameters
req.path:獲取請求路徑
req.protocol:獲取協議類型
req.query:獲取URL的查詢參數串
req.route:獲取當前匹配的路由
req.subdomains:獲取子域名
req.accepts():檢查可接受的請求的文檔類型
req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一個可接受字符編碼
req.get():獲取指定的HTTP請求頭
req.is():判斷請求頭Content-Type的MIME類型

5.5.2、response對象

Response 對象 - response 對象表示 HTTP 響應,即在接收到請求時向客戶端發送的 HTTP 響應數據。常見屬性有:

res.app:同req.app一樣
res.append():追加指定HTTP頭
res.set()在res.append()后將重置之前設置的頭
res.cookie(name,value [,option]):設置Cookie
opition: domain / expires / httpOnly / maxAge / path / secure / signed
res.clearCookie():清除Cookie
res.download():傳送指定路徑的文件
res.get():返回指定的HTTP頭
res.json():傳送JSON響應
res.jsonp():傳送JSONP響應
res.location():只設置響應的Location HTTP頭,不設置狀態碼或者close response
res.redirect():設置響應的Location HTTP頭,並且設置狀態碼302
res.render(view,[locals],callback):渲染一個view,同時向callback傳遞渲染后的字符串,如果在渲染過程中有錯誤發生next(err)將會被自動調用。callback將會被傳入一個可能發生的錯誤以及渲染后的頁面,這樣就不會自動輸出了。
res.send():傳送HTTP響應
res.sendFile(path [,options] [,fn]):傳送指定路徑的文件 -會自動根據文件extension設定Content-Type
res.set():設置HTTP頭,傳入object可以一次設置多個頭
res.status():設置HTTP狀態碼
res.type():設置Content-Type的MIME類型

 5.5.3、express獲取參數有三種方法

req.query 適合 http://localhost:3000/form?num=8888
req.body 適合http://localhost:3000/form,Post請求中的參數
req.params 適合獲取form后的num:http://localhost:3000/form/num

(一)、GET

var num = req.query.num;
res.send("你獲取的get數據為:" + num); 

(二)、POST

解析post數據需要用到body-parser

npm install body-parser --save

app.js

var express = require('express');
var app = express();
//引入body-parser
var bodyParser = require('body-parser');
app.use(express.static('public'));

//需要use的
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({
extended: true
})); // for parsing application/x-www-form-urlencoded

//獲取數據
app.post('/form', function(req, res) {
var num = req.body.num;
res.send("你獲取的post數據為:" + num);
});

//設置監聽端口
app.listen(3000);

public/test.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div id="box"></div>
<form action="/form" method="post">
<input type="text" name="num" value="tinyphp">
<input type="submit" value="提交">
</form>

</body>
</html>

(三)、獲取路徑參數

app.js

 

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

//獲取數據
app.get('/form/:num', function(req, res) {
var num = req.params.num;
res.send("你獲取到form/后的參數:" + num);
});

//設置監聽端口
app.listen(3000);

5.6、ejs基礎

ejs是一個Express Web應用的模板引擎,在NodeJS開發中可以選擇的模板引擎可能是所有Web應用開發中范圍最廣的,如jade、ejs、htmljs、swig、hogan.js,但ejs是最容易上手的,與jsp,asp,php的原始模板引擎風格很像。

官網:http://www.embeddedjs.com/

添加一個product.js路由:

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

/* 產品 */
router.get('/', function(req, res, next) {
    
  var products=[];
  products.push({name:"ZTE U880",price:899.8});
  products.push({name:"HuWei 榮耀8",price:1899.8});
  products.push({name:"iPhone 7 Plus 128G",price:5899.8});
  
  //將product視圖與指定的對象渲染后輸出到客戶端
  res.render('product', { title: '天狗商城', pdts:products});
});

module.exports = router;

在views目錄下添加product.ejs視圖,這里是一個簡單的MVC:

<!DOCTYPE html>
<html>

    <head>
        <title>
            <%= title %>
        </title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
    </head>

    <body>
        <h1><%= title %> - 產品列表</h1>
        <table border="1" width="80%">
            <tr>
                <th>序號</th>
                <th>名稱</th>
                <th>價格</th>
            </tr>
            <%pdts.forEach(function(pdt,index){%>
            <tr>
                <td>
                    <%=index+1%>
                </td>
                <td>
                    <%=pdt.name%>
                </td>
                <td>
                    <%=pdt.price%>
                </td>
            </tr>
            <%});%>
        </table>

        <ul>
            <% for(var i=0; i<pdts.length; i++) {%>
            <li>
                <%=pdts[i].name%>
            </li>
            <% } %>
    </body>

</html>

修改app,注冊定義好的模塊product:

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

var app = express();

//指定視圖引擎為ejs
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('/', index);
app.use('/users', users);
app.use('/pdt', pdts);

運行結果:

5.7、lodash

這是一個具有一致接口、模塊化、高性能等特性的 JavaScript 工具庫。可以非常方便的操作json。

官網:http://lodashjs.com/

安裝:

npm i -g npm

npm i --save lodash

安裝時先用cd切換到當前項目下。

如果瀏覽器使用可以直接引入:

<script src="lodash.js"></script>

添加lodash依賴:

依賴成功后會在package.json中添加引用:

后台Node.js使用,可以引入模塊:

//導入lodash模塊
var _= require('lodash');

var products=[];
products.push({name:"ZTE U880",price:899.8});
products.push({name:"HuWei 榮耀8",price:1899.8});
products.push({name:"iPhone 7 Plus 128G",price:5899.8});

//1、取出第一個元素
var obj1=_.first(products);
console.log(obj1.name);  //ZTE U880

//2、取出最后一個元素
var obj2=_.last(products);
console.log(obj2.name);  //iPhone 7 Plus 128G

//3、指定查找條件返回符合條件的索引
var obj3=_.findIndex(products,function(obj){
    return obj.price>=1000&&obj.name.indexOf("7")>0;
});
console.log(obj3);  //2

//4、指定查找條件返回查找到的對象
var obj4=_.find(products,function(obj){
    return obj.price>=1000&&obj.name.indexOf("7")>0;
});
console.log(obj4);  //{ name: 'iPhone 7 Plus 128G', price: 5899.8 }

//5、排序
var obj5=_.orderBy(products,["price","name"],["desc","asc"]);
console.log(obj5); 

//[ { name: 'iPhone 7 Plus 128G', price: 5899.8 },
//{ name: 'HuWei 榮耀8', price: 1899.8 },
//{ name: 'ZTE U880', price: 899.8 } ]

//6、查找價格為1899.8的產品的key
var obj6=_.findKey(products,{price:1899.8});
console.log(obj6);   //1

API的使用非常簡單,但需要注意版本,可以現查現用,API地址:https://lodash.com/docs/4.17.2 

5.8、參數

5.8.1、URL中的參數占位

Checks route params (req.params), ex: /user/:id

127.0.0.1:3000/index,這種情況下,我們為了得到index,我們可以通過使用req.params得到,通過這種方法我們就可以很好的處理Node中的路由處理問題,同時利用這點可以非常方便的實現MVC模式;

//獲得產品根據Id
router.get('/:id/:category',function(request,res,next){
    res.send(request.params.id+","+request.params.category);
});

運行結果:

5.8.2、URL中的QueryString

Checks query string params (req.query), ex: ?id=12

127.0.0.1:3000/index?id=12,這種情況下,這種方式是獲取客戶端get方式傳遞過來的值,通過使用req.query.id就可以獲得,類似於PHP的get方法;

router.get('/:id',function(request,res,next){
    res.send("name:"+request.query.name);
});

運行結果:

5.8.3、HTTP正文中的參數

 在post請求中獲得表單中的數據。

Checks urlencoded body params (req.body), ex: id=

127.0.0.1:300/index,然后post了一個id=2的值,這種方式是獲取客戶端post過來的數據,可以通過req.body.id獲取,類似於PHP的post方法;

頁面:

<!DOCTYPE html>
<html>

    <head>
        <title>
            <%= title %>
        </title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
    </head>

    <body>
        <h1><%= title %> - 產品列表</h1>
        <table border="1" width="80%">
            <tr>
                <th>序號</th>
                <th>名稱</th>
                <th>價格</th>
            </tr>
            <%pdts.forEach(function(pdt,index){%>
            <tr>
                <td>
                    <%=index+1%>
                </td>
                <td>
                    <%=pdt.name%>
                </td>
                <td>
                    <%=pdt.price%>
                </td>
            </tr>
            <%});%>
        </table>

        <ul>
            <% for(var i=0; i<pdts.length; i++) {%>
            <li>
                <%=pdts[i].name%>
            </li>
            <% } %>
        </ul>
<p>
            <%if(typeof msg!="undefined"){%>
                <%=msg%>
            <%}%>
</p>
        <form action="pdt/add" method="post">
            <p>
                名稱:<input name="name" />
            </p>
            <p>
                價格:<input name="price" />
            </p>
            <button>添加</button>
        </form>
        
        
        
        
    </body>

</html>
View Code

代碼:

router.post('/add',function(request,res,next){
    var entity={name:request.body.name,price:request.body.price};
    products.push(entity);
      //將product視圖與指定的對象渲染后輸出到客戶端
      res.render('product', { title: '天狗商城', pdts:products,msg:"添加成功"});
});

結果:

5.9、JSON

如果需要Node.js向外提供返回JSON的接口,Express也是非常方便的,可以使用原來在瀏覽器中使用到的JSON對象,這是一個瀏覽器內置對象在服務可以直接使用:

將對象序列化成字符:

            //對象
            var rose={"name":"Rose","weight":"65"};
            //序列化成字符串
            var str=JSON.stringify(rose);
            alert(str);

結果:

反序列化,將字符轉換成對象:

            //將字符串轉換成JavaScript對象
            var markStr='{"name":"mark","weight":"188"}';
            var mark=JSON.parse(markStr);
            alert(mark.name+","+mark.weight);

結果:

Express已經封裝了一個json方法,直接調用該方法就可以序列化對象:

/* 產品 */
router.get('/rest', function(req, res, next) {
  res.json(products);
});

運行結果:

 

六、RESTful(表述性狀態轉移)

REST是英文Representational State Transfer的縮寫,中文稱之為“表述性狀態轉移”
基於HTTP協議
是另一種服務架構
傳遞是JSON、POX(Plain Old XML)而不是SOAP格式的數據
充分利用HTTP謂詞(Verb)
側重數據的傳輸,業務邏輯交給客戶端自行處理

REST是一種分布式服務架構的風格約束,像Java、.Net(WCF、WebAPI)都有對該約束的實現,使URL變得更加有意義,更加簡潔明了,如:

http://www.zhangguo.com/products/1 get請求 表示獲得所有產品的第1個

http://www.zhangguo.com/products/product post請求 表示添加一個產品

http://www.zhangguo.com/products/1/price get請求 表示獲得第1個產品的價格

http://www.zhangguo.com/products/1 delete請求 刪除編號為1的產品

REST設計需要遵循的原則
網絡上的所有事物都被抽象為資源(resource);
每個資源對應一個唯一的資源標識符(resource identifier);
通過通用的連接器接口(generic connector interface)對資源進行操作;
對資源的各種操作不會改變資源標識符;
所有的操作都是無狀態的(stateless)

謂詞
GET
表示查詢操作,相當於Retrieve、Select操作
POST
表示插入操作,相當於Create,Insert操作
PUT
表示修改操作,相當於Update操作
DELETE
表示刪除操作,相當於Delete操作

其它還有:

NodeJS+Express可以很容易的實現REST

application/x-www-form-urlencoded

multipart/form-data

application/json

res.setHeader('Content-Type', 'application/json;charset=utf-8');  

示例代碼cars.js:

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

var cars=[];
cars.push({id:201701,name:"BMW",price:190,speed:"210km/h",color:"白色"});
cars.push({id:201702,name:"BYD",price:25,speed:"160km/h",color:"紅色"});
cars.push({id:201703,name:"Benz",price:300,speed:"215km/h",color:"藍色"});
cars.push({id:201704,name:"Honda",price:190,speed:"170km/h",color:"黑色"});
cars.push({id:201705,name:"QQ",price:130,speed:"210km/h",color:"白色"});

/* Get */
/*獲得所有汽車*/
/*url /cars/*/
router.get('/', function(req, res, next) {
    res.json(cars);
});

/*Get*/
/*獲得汽車通過id*/
/*url:/cars/:id  */
router.get('/:id', function(req, res, next) {
     //從路徑中映射參數,轉換成數字
      var id=parseInt(req.params.id);
      var car=_.find(cars,{id:id});
      res.json(car);
});

/*Post*/
/*添加汽車*/
/*url:/cars/car  */
router.post('/car', function(req, res, next) {
      var car=req.body;  //從請求正文中獲得json對象
      car.id=_.last(cars).id+1;  //將編號修改為最后一輛車的編號+1
      cars.push(car);  //將汽車對象添加到集合中
      res.json(car);  //將添加成功的車以json的形式返回
});

/*Put*/
/*修改汽車*/
/*url:/cars/car  */
router.put('/car', function(req, res, next) {
      var car=req.body;  //從請求正文中獲得json對象
      console.log(req.body);
      var index=_.findIndex(cars,{id:parseInt(car.id)});  //根據id獲得車在集合中的下標
      
      cars[index]=car;  //替換原對象
      //res.json(car);  //將修改后的車以json的形式返回
      res.send({status:"success", message:"更新成功!"});  
});

/*Delete*/
/*刪除汽車*/
/*url:/cars/:id  */
router.delete('/id/:id', function(req, res, next) {
      //獲得url中的編號參數
      var id=parseInt(req.params.id);
      var index=_.findIndex(cars,{id:id});  //根據id獲得車在集合中的下標
      cars.splice(index,1);   //在cars數組中刪除下標從index開始的1條數據
      res.send({status:"success", message:"刪除成功!"});  
});

module.exports = router;

示例代碼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 index = require('./routes/index');
var users = require('./routes/users');
var pdts = require('./routes/product');
var task = require('./routes/task');
var cars = require('./routes/cars');

var app = express();

//指定視圖引擎為ejs
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('/', index);
app.use('/users', users);
app.use('/pdt', pdts);
app.use("/task",task);
app.use("/cars",cars);

// 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 handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

/* Get */
/*獲得所有汽車*/
/*url /cars/*/

/*Get*/
/*獲得汽車通過id*/
/*url:/cars/:id */

/*Post*/
/*添加汽車*/
/*url:/cars/car */

 

參數中的json格式一定要使用標准格式,注意引號,注意Content-Type,默認的Content-Type類型是:application/x-www-form-urlencoded

/*Put*/
/*修改汽車*/
/*url:/cars/car */

/*Delete*/
/*刪除汽車*/
/*url:/cars/:id */

node.js跨域

修改app.js文件攔截所有的請求,修改頭部

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.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "content-type");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.header("X-Powered-By", ' 3.2.1');
    res.header("Content-Type", "application/json;charset=utf-8");
    if(req.method == "OPTIONS") {
        res.send("200");
    } else {
        next();
    }
});

結果:

七、示例下載

git:https://coding.net/u/zhangguo5/p/NodeJS001/git

git:https://coding.net/u/zhangguo5/p/NodeJSExpress/git

八、作業

8.1、請安裝好node.js環境,測試版本,在控制台寫一個方法用於計算1-100間的所有能被3整除的數,並調用。

8.2、請將8.1中的方法單獨存放到一個math.js文件中,同時在math.html頁面與node的控制台中調用

8.3、在開發工具IDE中集成node.js開發環境,創建一個node.js項目,向控制台輸出“Hello World!”

8.4、使用記事本在c:\根目錄下寫一個server.js文件實現一個最簡單的web服務器,請求時響應當前系統時間。

8.5、將8.4的功能在IDE中完成,請注意端口號不能被占用,如果提示占用錯誤可以修改端口號為1025-65535之間

8.6、完成一個圖書管理的功能,圖書包含(編號,名稱,作者,圖片,價格),實現:

a)、非AJAX的CRUD,使用Node.js+Express+ejs的動態技術。

b)、AJAX的CRUD,使用Node.js+Express+jQuery+HTML技術實現。當然也可以使用Vue2+axios。

c)、使用RestFul風格的服務完成第個作業,get,post,delete,put請。

九、視頻

https://www.bilibili.com/video/av17977069/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM