freeCodeCamp 旅途20 - Node 和 Express 基础

Node 和 Express

Node.js 是一个 JavaScript 运行环境,它允许开发者使用 JavaScript 来写后端(服务端)程序。Node.js 有很多内置的模块 — 小的、独立的程序 — 使其可用于服务器端编程。一些主要的核心模块包括:

  • HTTP:使其可以作为独立服务器运行。
  • 文件系统:读写文件模块
  • 路径:关于目录和文件路径的模块
  • 断言测试:主要用于断言。如果表达式不符合预期,就抛出一个错误

Express,它不是 Node.js 的内置模块,它是一个 Node.js 框架。Express 提供精简的基本 Web 应用程序功能,包括应用的路由。路由根据用户在页面的操作将用户导向到相应的页面。

Meet the Node console

myApp.js入口函数中,输入console.log("Hello World");

Start a Working Express Server

在 myApp.js 文件的前两行中,你可以看到创建一个 Express 应用对象是很简单的。这个 Express 对象有几个方法,一个基础的方法是app.listen(port)。它告诉服务器监听指定的端口,并且让这个服务处于运行状态

var express = require('express');
var app = express();
// Glitch 将端口号存储在环境变量process.env.PORT中。它的值是3000
app.listen(3000); 
 module.exports = app;

让我们在服务端输出第一个字符串!在 Express 中,路由采用这种结构:app.METHOD(PATH, HANDLER)METHOD是小写的 http 方法。PATH是服务器上的相对路径(它是一个字符串,甚至是正则表达式)HANDLER是 Express 匹配路由时调用的处理函数。

处理函数采用这种形式:function(req, res) {...},在这个处理函数的参数中req是请求对象。res是响应对象。举个例子,处理函数:

function(req, res) {
    res.send('Response String'); // 将会响应一个字符串 'Response String'
}

var express = require('express');
var app = express();
app.get('/', function(req, res){
  res.send("Hello Express");
});
app.listen(3000, function(){
  console.log("Example app listening on port 3000!")
});
module.exports = app;

Serve an HTML File

我们可以使用res.sendFile(path)方法来响应一个文件。你可以把响应一个文件的方法放到路由处理程序中:app.get('/', ...)。在后台,这个方法会根据你想发送的文件的类型,设置适当的 headers 头信息来告诉浏览器如何处理它。然后它会读取并发送文件。此方法需要文件的绝对路径。我们建议你使用 Node.js 的全局变量__dirname来计算出这个文件的绝对路径。absolutePath = __dirname + relativePath/file.ext

要发送的文件是/views/index.html。在 app 中点击 "Show Live" 按钮,你会看到一个大的 HTML 标题(以及我们稍后将使用的表单…),目前它们还没有任何样式。

var express = require("express");
var app = express();
app.get("/", function(req, res) {
  var absolutePath = __dirname + "/views/index.html";
  // res.setHeader('Content-Type', 'text/html');
  res.sendFile(absolutePath);
});
module.exports = app;

Serve Static Assets

HTML 服务器通常有一个或多个用户可以访问的目录。你可以将应用程序所需的静态资源 (样式表、脚本、图片) 放在那里。在 Express 中你可以使用中间件express.static(path)来设置此功能,它的参数就是静态资源文件的绝对路径。

一个最基本的中间件可以看做是一个函数,它拦截路由处理方法,并在里面添加了一点别的信息。使用app.use(path, middlewareFunction)方法来加载一个中间件。它的第一个参数是可选的,如果没设置第一个参数,那么应用的所有请求都会经过这个中间件处理。

var express = require("express");
var app = express();
app.get("/", function(req, res) {
  var absolutePath = __dirname + "/views/index.html";
  // res.setHeader('Content-Type', 'text/html');
  res.sendFile(absolutePath);
});
// 所有请求都能访问中间件
app.use(express.static(__dirname + "/public"))
module.exports = app;

Serve JSON on a Specific Route

HTML 服务器提供 HTML,API 服务器提供数据。REST(表现层状态转换)API 允许使用更简易的方式交换数据,从而不需要客户端知道服务器的实现细节。客户端只要知道需请求资源对应的 URL 是什么,以及需要对这个 URL 进行何种操作就够了。比如 GET 这个动作,就是从服务器上获取某些信息,它不会修改任何数据。

如今,在 web 上传输数据的首选数据格式是 JSON。简言之,JSON 是一种将 JavaScript 对象表示为字符串的简易方式,因此可以很容易地传输这些数据。

我们来创建一个简单的 API,一个路径为/json且返回数据是 JSON 格式的路由,你可以像之前那样通过app.get()方法来做,然后在路由处理部分使用res.json()方法返回 JSON 格式的数据,这个方法可以接收一个配置对象。这个方法会结束请求响应循环(request-response loop),然后返回数据。res.json()将一个有效的 JavaScript 对象转化为字符串,然后会设置适当的头信息(headers)来告诉浏览器,这是一个 JSON 数据,最后返回给客户端进行处理。

一个有效的对象通常是这种结构:{key: data}。数据可以是数字、字符串、嵌套对象或数组。也可以是变量或者函数返回值,在这种情况下,会以它们的执行结果为基准,再转成字符串。

当 GET 请求路由/json时,将对象{"message": "Hello json"}作为 JSON 格式返回给客户端。然后在浏览器里输入完整的 URL,比如your-app-url/json,就可以在屏幕上看到这个消息了。

var express = require("express");
var app = express();
app.get("/", function(req, res) {
  var absolutePath = __dirname + "/views/index.html";
  res.sendFile(absolutePath);
});
app.use(express.static(__dirname + "/public"))
app.get("/json", function(req, res){
  var obj = {message: "Hello json"};
  res.json(obj);
})
module.exports = app;

Use the .env File

.env文件是一个隐藏文件,用于将环境变量传给应用程序。这是一个私密文件,除了你之外没人可以访问它,它可以用来存储你想保持私有或者隐藏的数据。举个例子,可以存储第三方服务 API 密钥或者数据库 URI。你也可以使用它来存储配置选项。通过设置配置选项,你可以改变应用程序的行为,而无需重写一些代码。

在应用程序中可以通过process.env.VAR_NAME访问到环境变量。process.env是 Node 程序中的一个全局对象,可以给这个变量传字符串。按照惯例,变量名都是大写的,单词之间用下划线隔开。.env是一个 shell 文件,因此不需要用给变量名和值加引号。还有一点需要注意,当你给变量赋值时,等号周围不能有空格,举个例子:VAR_NAME=value。通常来讲,每一个变量会单独定义在新的一行。

让我们添加一个环境变量作为配置选项。在.env文件中保存变量MESSAGE_STYLE=uppercase。它的作用是,告诉上一次挑战中的路由处理程序,当我们 GET 方法请求 /JSON 时,如果process.env.MESSAGE_STYLE的值为uppercase,那么返回的对象则应该是{"message": "HELLO JSON"}.

var express = require("express");
var app = express();
app.get("/", function(req, res) {
  var absolutePath = __dirname + "/views/index.html";
  res.sendFile(absolutePath);
});
app.use(express.static(__dirname + "/public"))
app.get("/json", function(req, res){ 
  if(process.env.MESSAGE_STYLE.indexOf("uppercase") > -1){
     res.json({message: "Hello JSON"})
  }
});
module.exports = app;

Implement a Root-Level Request Logger Middleware

中间件是一个接收 3 个参数的函数:请求对象、响应对象和在应用请求响应循环中的下一个函数。这些函数执行一些可能对应用程序产生副作用的代码,通常还会在请求对象或者响应对象里添加一些信息。当满足某些条件时,它们也可以结束发送响应的循环。如果它们没有发送响应,那么当它们完成时就会开始执行堆栈中的下一个函数。这将触发调用第 3 个参数next()

function(req, res, next) {
  console.log("我是一个中间件...");
  next();
}

要在根层级安装中间件函数,我们可以使用app.use(<mware-function>)方法。在这种情况下,该函数将对所有请求执行,但是你还是可以设置成更具体的条件来执行。举个例子,如果你希望某个函数只针对 POST 请求执行,可以使用app.post(<mware-function>)方法。所有 http 动作都有类似的方法,比如 GET、DELETE、PUT 等等。

构建一个简单的日志记录器。对于每个请求,它应该在控制台中记录一个采用以下格式的字符串:method path - ip。一个简单的日志看起来就像这样:GET /json - ::ffff:127.0.0.1。注意methodpath有一个空格,并且pathip中间的破折号两边都有空格。

在请求对象中,可以使用req.methodreq.pathreq.ip获取请求方法(http 动词)、路由相对路径和请求者的 IP 信息。记住,当你完成时,要调用next()方法,否则你的服务器将一直处于挂起状态。

提示: Express 按照函数在代码中出现的顺序来评估函数。中间件也是如此。如果你想让中间件函数适用于所有路由,那么应该在路由之前配置好中间件。

var express = require("express");
var app = express();
app.use(express.static(__dirname + "/public"));
app.use("/", function(req, res, next){
  console.log(req.method + " " + req.path + " - " + req.ip);
  next();
});
app.get("/", function(req, res){
  res.sendfile(__dirname+'/views/index.html');
});
module.exports = app;

Chain Middleware to Create a Time Server

使用app.METHOD(path, middlewareFunction)可以将中间件挂载到指定的路由。中间件也可以在路由定义中链接。

app.get('/user', function(req, res, next) {
  req.user = getTheUserSync(); // Hypothetical synchronous operation
  next();
}, function(req, res) {
  res.send(req.user);
})

此方法可用于将服务操作拆分为较小的单元。这样可以让应用拥有更好的结构,以便于在不同的位置上复用代码。此方法还可用于对数据执行某些验证。在每一个中间件堆栈中,你都可以阻止当前链的执行,并将控制权传递给专门设计用于处理错误的函数。或者你可以将控制权传递给下一个匹配的路径,以处理特殊情况。

app.get("/now", function(req, res, next){
  req.time = new Date().toString();
  next();
}, function(req, res){
  res.send({time: req.time});
});

Get Route Parameter Input from the Client

在构建 API 时,我们要让用户告诉我们他们想从服务中获取什么。举个例子,如果客户请求数据库中存储的用户信息,他们需要一种方法让我们知道他们对哪个用户感兴趣。实现这个需求的的方式就是使用路由参数。路由参数是由斜杠 (/) 分隔的 URL 命名段。每一小段能捕获与其位置匹配的 URL 部分的值。捕获的值能够在req.params对象中找到。

route_path: '/user/:userId/book/:bookId'
actual_request_URL: '/user/546/book/6754'
req.params: {userId: '546', bookId: '6754'}

app.get("/:word/echo", function(req, res, next){
  req.word = req.params.word;
  next();
}, function(req, res, next){
  res.send({echo: req.word});
  next();
});

Get Query Parameter Input from the Client

从客户端获取输入的另一种常见方式是使用查询字符串对路由路径中的数据进行编码。查询字符串使用标记 (?) 分隔,并且包含一对field=value。每一对键值使用符号 (&) 分隔。Express 能够从查询字符串中分析这些数据,并且把它放到req.query对象中。有些字符不能在出现在 URL 中,它们在发送前必须以不同的格式进行编码。如果你使用来自 JavaScript 的 API,你可以使用特定的方法来编码/解码这些字符。

route_path: '/library'
actual_request_URL: '/library?userId=546&bookId=6754'
req.query: {userId: '546', bookId: '6754'}

app.route('/name')
  .get(function(req, res) {
    res.json(req.query);
  })
  .post(function(req, res) {
    res.json(req.query);
  });

Use body-parser to Parse POST Requests

除了 GET 还有另一个常见的 http 动词,它是 POST。POST 是使用 HTML 表单发送客户端数据的默认方法。在 REST 规范中,POST 常用于发送数据,以便在数据库中创建新项目(新用户或新博客文章)。

在这些类型的请求中,数据不会出现在 URL 中,而是隐藏在请求正文中。这也是 HTML 请求的一部分,被称为负载。因为 HTML 是基于文本的,你看不到数据,这并不意味着它们是加密的。HTTP POST 请求的原始内容如下所示:

POST /path/subpath HTTP/1.0
From: john@example.com
User-Agent: someBrowser/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
name=John+Doe&age=25

正文被编码成了查询字符串。这是 HTML 表单使用的默认格式。使用 Ajax,我们还可以使用 JSON 来处理具有更复杂结构的数据。还有另一种类型的编码:multipart/form-data。它用来上传二进制文件。

要解析来自 POST 请求的数据,你必须安装一个包:body-parser。这个包允许你使用一套可以解码不同格式数据的中间件,在这里查看文档。

var app = require('express')();
var bodyParser = require('body-parser');
var multer = require('multer'); // v1.0.5
var upload = multer(); // for parsing multipart/form-data

// 通常中间件必须挂载在所有需要它的路由之前

// for parsing application/json
app.use(bodyParser.json()); 
// for parsing application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true })); 
// extended=false 告诉解析器使用经典编码,值只能是字符串或者数组
app.use(bodyParser.urlencoded({extended: false})); 

app.post('/profile', upload.array(), function (req, res, next) {
  console.log(req.body);
  res.json(req.body);
});

Get Data from POST Requests

如果 body-parser 正确配置好了,你就可以在req.body对象中找到请求的参数。来看看一个常规的请求 /library 例子:

route: POST '/library'
urlencoded_body: userId=546&bookId=6754
req.body: {userId: '546', bookId: '6754'}

提示: 除了 GET 和 POST,还有其他几种 http 方法。按照惯例,http 动词之间有对应关系,它们分别对应你在服务端执行的某种操作,传统的对应关系:

  • POST (有时候是 PUT) - 使用请求发送信息,以创建新资源,
  • GET - 读取已存在的资源,不用修改它,
  • PUT 或者 PATCH (有时候是 POST) - 发送数据,以更新资源,
  • DELETE => 删除一个资源。

还有一些其他方法,常用于与服务进行交互。除了 GET 之外,上面列出的所有方法都可以负载数据(换言之,数据都能在请求体中找到)。也可以使用 body-parser 来正常工作。

app.route('/name')
  .get(function(req, res) {
    res.json(req.body);
  })
  .post(function(req, res) {
    res.json(req.body);
  });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容