ExpressJS - 快速指南



ExpressJS - 概述

ExpressJS 是一个 Web 应用程序框架,它提供了一个简单的 API 来构建网站、Web 应用和后端。使用 ExpressJS,您无需担心底层协议、流程等。

什么是 Express?

Express 提供了一个最小的接口来构建我们的应用程序。它为我们提供了构建应用程序所需的工具。它很灵活,因为在 npm 上有许多可用的模块,可以直接插入 Express 中。

Express 由 TJ Holowaychuk 开发,并由 Node.js 基金会和众多开源贡献者维护。

为什么选择 Express?

与 Rails 和 Django 等竞争对手不同,它们构建应用程序的方式有自己的主张,Express 没有“最佳方式”来做某事。它非常灵活且可扩展。

Pug

Pug(以前称为 Jade)是一种简洁的语言,用于编写 HTML 模板。它 -

  • 生成 HTML
  • 支持动态代码
  • 支持可重用性(DRY)

它是 Express 最常用的模板语言之一。

MongoDB 和 Mongoose

MongoDB 是一个开源的文档数据库,旨在简化开发和扩展。此数据库也用于存储数据。

Mongoose 是一个 node.js 的客户端 API,它使我们能够轻松地从 Express 应用程序访问我们的数据库。

ExpressJS - 环境

在本章中,我们将学习如何开始开发和使用 Express 框架。首先,您应该安装 Node 和 npm(节点包管理器)。如果您尚未安装这些,请访问 Node 设置 在本地系统上安装 Node。通过在终端中运行以下命令确认 Node 和 npm 是否已安装。

node --version
npm --version

您应该得到类似于以下的输出。

v5.0.0
3.5.2

现在我们已经设置了 Node 和 npm,让我们了解一下 npm 是什么以及如何使用它。

Node 包管理器 (npm)

npm 是 Node 的包管理器。npm 注册表是 JavaScript 社区中 Node.js、前端 Web 应用、移动应用、机器人、路由器以及无数其他需求的开源代码包的公共集合。npm 允许我们访问所有这些包并在本地安装它们。您可以在 npmJS 上浏览 npm 上可用的包列表。

如何使用 npm?

有两种方法可以使用 npm 安装包:全局安装和本地安装。

  • 全局安装 - 此方法通常用于安装开发工具和基于 CLI 的包。要全局安装包,请使用以下代码。

npm install -g <package-name>
  • 本地安装 - 此方法通常用于安装框架和库。本地安装的包只能在其安装的目录中使用。要本地安装包,请使用与上面相同的命令,但不带 -g 标志。

npm install <package-name>

每当我们使用 npm 创建项目时,都需要提供一个 package.json 文件,其中包含我们项目的所有详细信息。npm 使我们能够轻松设置此文件。让我们设置我们的开发项目。

步骤 1 - 启动您的终端/cmd,创建一个名为 hello-world 的新文件夹并 cd(创建目录)到其中 -

npm init info

步骤 2 - 现在要使用 npm 创建 package.json 文件,请使用以下代码。

npm init

它会要求您提供以下信息。

npm init info

只需一直按 Enter 键,并在“作者姓名”字段中输入您的姓名。

步骤 3 - 现在我们已经设置了 package.json 文件,我们将进一步安装 Express。要安装 Express 并将其添加到我们的 package.json 文件中,请使用以下命令 -

npm install --save express

要确认 Express 是否已正确安装,请运行以下代码。

ls node_modules #(dir node_modules for windows)

提示 - --save 标志可以替换为 -S 标志。此标志确保 Express 作为依赖项添加到我们的 package.json 文件中。这有一个优点,下次我们需要安装项目的所有依赖项时,我们只需运行命令 npm install,它就会在此文件中找到依赖项并为我们安装它们。

这就是我们使用 Express 框架开始开发所需的一切。为了使我们的开发过程更容易,我们将从 npm 安装一个工具,nodemon。此工具会在我们修改任何文件后立即重新启动我们的服务器,否则我们需要在每次文件修改后手动重新启动服务器。要安装 nodemon,请使用以下命令 -

npm install -g nodemon

您现在可以开始使用 Express 了。

ExpressJS - Hello World

我们已经设置了开发环境,现在是时候开始使用 Express 开发我们的第一个应用程序了。创建一个名为 index.js 的新文件,并在其中输入以下内容。

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

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

app.listen(3000);

保存文件,转到您的终端并输入以下内容。

nodemon index.js

这将启动服务器。要测试此应用程序,请打开您的浏览器并转到 https://127.0.0.1:3000,将显示一条消息,如下面的屏幕截图所示。

Hello world

应用程序如何工作?

第一行将 Express 导入我们的文件,我们可以通过变量 Express 访问它。我们用它来创建一个应用程序并将其分配给 var app。

app.get(route, callback)

此函数告诉在调用给定路由的 get 请求时该做什么。回调函数有两个参数,request(req)response(res)。请求 对象 (req) 表示 HTTP 请求,并具有请求查询字符串、参数、主体、HTTP 标头等的属性。同样,响应对象表示 Express 应用程序在收到 HTTP 请求时发送的 HTTP 响应。

res.send()

此函数以对象作为输入,并将其发送到请求的客户端。这里我们发送字符串 "Hello World!"

app.listen(port, [host], [backlog], [callback]])

此函数绑定并在指定的主机和端口上侦听连接。端口是这里唯一必需的参数。

序号 参数和说明
1

端口

服务器应接受传入请求的端口号。

2

主机

域名的名称。当您将应用程序部署到云端时,需要设置它。

3

积压

排队等待连接的最大数量。默认值为 511。

4

回调

服务器开始侦听请求时调用的异步函数。

ExpressJS - 路由

Web 框架在不同的路由中提供资源,例如 HTML 页面、脚本、图像等。

以下函数用于在 Express 应用程序中定义路由 -

app.method(path, handler)

此 METHOD 可以应用于任何一个 HTTP 动词 - get、set、put、delete。还存在另一种方法,它独立于请求类型执行。

路径是请求将运行的路由。

处理程序是在相关路由上找到匹配的请求类型时执行的回调函数。例如,

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

app.get('/hello', function(req, res){
   res.send("Hello World!");
});

app.listen(3000);

如果我们运行应用程序并转到 localhost:3000/hello,服务器将在路由 "/hello" 上接收 get 请求,我们的 Express 应用程序将执行附加到此路由的 回调 函数并将 "Hello World!" 作为响应发送。

Hello

我们也可以在同一路由上有多种不同的方法。例如,

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

app.get('/hello', function(req, res){
   res.send("Hello World!");
});

app.post('/hello', function(req, res){
   res.send("You just called the post method at '/hello'!\n");
});

app.listen(3000);

要测试此请求,请打开您的终端并使用 cURL 执行以下请求 -

curl -X POST "https://127.0.0.1:3000/hello"

Curl request

Express 提供了一种特殊方法 all,用于使用相同函数处理特定路由上的所有类型的 http 方法。要使用此方法,请尝试以下操作。

app.all('/test', function(req, res){
   res.send("HTTP method doesn't have any effect on this route!");
});

此方法通常用于定义中间件,我们将在中间件章节中讨论。

路由器

像上面那样定义路由维护起来非常麻烦。为了将路由与我们的主要 index.js 文件分离,我们将使用 Express.Router。创建一个名为 things.js 的新文件,并在其中输入以下内容。

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

router.get('/', function(req, res){
   res.send('GET route on things.');
});
router.post('/', function(req, res){
   res.send('POST route on things.');
});

//export this router to use in our index.js
module.exports = router;

现在要在我们的 index.js 中使用此路由器,请在 app.listen 函数调用之前输入以下内容。

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

var things = require('./things.js');

//both index.js and things.js should be in same directory
app.use('/things', things);

app.listen(3000);

在路由 '/things' 上的 app.use 函数调用将 things 路由器附加到此路由。现在,我们的应用程序在 '/things' 上接收到的任何请求都将由我们的 things.js 路由器处理。'/' 路由在 things.js 中实际上是 '/things' 的子路由。访问 localhost:3000/things/,您将看到以下输出。

Router Things

路由器在分离关注点并将相关代码段放在一起方面非常有用。它们有助于构建可维护的代码。您应该在一个文件中定义与实体相关的路由,并使用上述方法将其包含在您的 index.js 文件中。

ExpressJS - HTTP 方法

HTTP 方法在请求中提供,并指定客户端请求的操作。下表列出了最常用的 HTTP 方法 -

序号 方法和说明
1

GET

GET 方法请求指定资源的表示形式。使用 GET 的请求应该只检索数据,并且不应产生其他影响。

2

POST

POST 方法请求服务器接受请求中包含的数据作为 URI 标识的资源的新对象/实体。

3

PUT

PUT 方法请求服务器接受请求中包含的数据作为对 URI 标识的现有对象的修改。如果它不存在,则 PUT 方法应该创建一个。

4

DELETE

DELETE 方法请求服务器删除指定的资源。

这些是最常用的 HTTP 方法。要了解更多关于这些方法的信息,请访问 https://tutorialspoint.com/http/http_methods.htm

ExpressJS - URL 构建

我们现在可以定义路由了,但这些路由是静态的或固定的。要使用动态路由,我们**应该**提供不同类型的路由。使用动态路由允许我们传递参数并基于这些参数进行处理。

这是一个动态路由的示例:

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

app.get('/:id', function(req, res){
   res.send('The id you specified is ' + req.params.id);
});
app.listen(3000);

要测试它,请访问https://127.0.0.1:3000/123。将显示以下响应。

URL Building 1

您可以将 URL 中的“123”替换为任何其他内容,并且更改将反映在响应中。上面一个更复杂的示例是:

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

app.get('/things/:name/:id', function(req, res) {
   res.send('id: ' + req.params.id + ' and name: ' + req.params.name);
});
app.listen(3000);

要测试以上代码,请访问https://127.0.0.1:3000/things/tutorialspoint/12345

URL Building 2

您可以使用req.params对象访问您在 url 中传递的所有参数。请注意,以上两个是不同的路径。它们永远不会重叠。此外,如果您希望在获取'/things'时执行代码,则需要单独定义它。

模式匹配路由

您还可以使用regex来限制 URL 参数匹配。假设您需要id为 5 位长数字。您可以使用以下路由定义:

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

app.get('/things/:id([0-9]{5})', function(req, res){
   res.send('id: ' + req.params.id);
});

app.listen(3000);

请注意,这**仅**匹配具有 5 位长id的请求。您可以使用更复杂的正则表达式来匹配/验证您的路由。如果您的任何路由都不匹配请求,您将收到“Cannot GET <your-request-route>”消息作为响应。可以使用以下简单路由将此消息替换为 404 未找到页面:

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

//Other routes here
app.get('*', function(req, res){
   res.send('Sorry, this is an invalid URL.');
});
app.listen(3000);

重要提示:这应该放在所有路由之后,因为 Express 从index.js文件的开头到结尾匹配路由,包括您需要的外部路由器。

例如,如果我们定义与上面相同的路由,则在使用有效 URL 请求时,将显示以下输出:

Correct regex

而对于错误的 URL 请求,将显示以下输出。

Invalid regex(404)

ExpressJS - 中间件

中间件函数是可以访问请求对象 (req)响应对象 (res)和应用程序请求-响应周期中下一个中间件函数的函数。这些函数用于修改reqres对象,以执行解析请求主体、添加响应标头等任务。

这是一个中间件函数在行动中的简单示例:

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

//Simple request time logger
app.use(function(req, res, next){
   console.log("A new request received at " + Date.now());
   
   //This function call is very important. It tells that more processing is
   //required for the current request and is in the next middleware
   function route handler.
   next();
});

app.listen(3000);

上述中间件在服务器上的每个请求都会被调用。因此,在每次请求之后,我们将在控制台中看到以下消息:

A new request received at 1467267512545

要将其限制到特定路由(及其所有子路由),请将该路由作为app.use()的第一个参数提供。例如,

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

//Middleware function to log request protocol
app.use('/things', function(req, res, next){
   console.log("A request for things received at " + Date.now());
   next();
});

// Route handler that sends the response
app.get('/things', function(req, res){
   res.send('Things');
});

app.listen(3000);

现在,每当您请求'/things'的任何子路由时,它才会记录时间。

中间件调用顺序

关于 Express 中的中间件,最重要的事情之一是它们在文件中写入/包含的顺序;还需要考虑它们执行的顺序,前提是路由也匹配。

例如,在以下代码片段中,第一个函数首先执行,然后是路由处理程序,然后是结束函数。此示例总结了如何在路由处理程序之前和之后使用中间件;以及如何将路由处理程序本身用作中间件。

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

//First middleware before response is sent
app.use(function(req, res, next){
   console.log("Start");
   next();
});

//Route handler
app.get('/', function(req, res, next){
   res.send("Middle");
   next();
});

app.use('/', function(req, res){
   console.log('End');
});

app.listen(3000);

当我们运行此代码后访问'/'时,我们收到响应为Middle,并在控制台中:

Start
End

下图总结了我们关于中间件的学习内容:

Middleware

现在我们已经介绍了如何创建自己的中间件,让我们讨论一些最常用的社区创建的中间件。

第三方中间件

Express 的第三方中间件列表可在 此处找到。以下是一些最常用的中间件;我们还将学习如何使用/挂载这些中间件:

body-parser

这用于解析带有附加有效负载的请求的主体。要挂载 body-parser,我们需要使用npm install --save body-parser 进行安装,并将其挂载,在您的 index.js 中包含以下行:

var bodyParser = require('body-parser');

//To parse URL encoded data
app.use(bodyParser.urlencoded({ extended: false }))

//To parse json data
app.use(bodyParser.json())

要查看 body-parser 的所有可用选项,请访问其 github 页面。

cookie-parser

它解析Cookie标头并将 req.cookies 填充为一个以 cookie 名称为键的对象。要挂载 cookie-parser,我们需要使用 npm install --save cookie-parser 进行安装,并将其挂载,在您的 index.js 中包含以下行:

var cookieParser = require('cookie-parser');
app.use(cookieParser())

express-session

它使用给定的选项创建一个会话中间件。我们将在会话部分讨论其用法。

ExpressJS 中还有许多其他第三方中间件。但是,我们在这里只讨论了一些重要的中间件。

ExpressJS - 模板引擎

Pug 是 Express 的模板引擎。模板引擎用于消除服务器代码中 HTML 的混乱,避免大量连接字符串到现有的 HTML 模板。Pug 是一个非常强大的模板引擎,具有多种功能,包括过滤器、包含、继承、插值等。这方面的内容很多。

要在 Express 中使用 Pug,我们需要安装它,

npm install --save pug

现在 Pug 已安装,将其设置为应用程序的模板引擎。您**无需**“require”它。将以下代码添加到您的index.js文件中。

app.set('view engine', 'pug');
app.set('views','./views');

现在创建一个名为 views 的新目录。在其中创建一个名为first_view.pug的文件,并在其中输入以下数据。

doctype html
html
   head
      title = "Hello Pug"
   body
      p.greetings#people Hello World!

要运行此页面,请将以下路由添加到您的应用程序中:

app.get('/first_template', function(req, res){
   res.render('first_view');
});

您将获得以下输出:Hello World! Pug 将这个非常简单的标记转换为 html。我们不需要跟踪关闭标签,不需要使用 class 和 id 关键字,而是使用 '.' 和'#'来定义它们。上面的代码首先转换为:

<!DOCTYPE html>
<html>
   <head>
      <title>Hello Pug</title>
   </head>
   
   <body>
      <p class = "greetings" id = "people">Hello World!</p>
   </body>
</html>

Pug 能够做的不仅仅是简化 HTML 标记。

Pug 的重要特性

现在让我们探索一些 Pug 的重要特性。

简单标签

标签根据缩进嵌套。就像在上面的例子中,<title>缩进在<head>标签内,所以它在里面。但是<body>标签在相同的缩进级别上,所以它是<head>标签的同级标签。

我们不需要关闭标签,一旦 Pug 遇到相同或外部缩进级别的下一个标签,它就会为我们关闭标签。

要在标签内放置文本,我们有三种方法:

  • 空格分隔

h1 Welcome to Pug
  • 管道文本

div
   | To insert multiline text, 
   | You can use the pipe operator.
  • 文本块

div.
   But that gets tedious if you have a lot of text.
   You can use "." at the end of tag to denote block of text.
   To put tags inside this block, simply enter tag in a new line and 
   indent it accordingly.

注释

Pug 使用与JavaScript(//)相同的语法来创建注释。这些注释被转换为 html 注释(<!--comment-->)。例如,

//This is a Pug comment

此注释将转换为以下内容。

<!--This is a Pug comment-->

属性

要定义属性,我们使用逗号分隔的属性列表,括在括号中。Class 和 ID 属性具有特殊的表示形式。以下代码行涵盖了为给定的 html 标签定义属性、类和 id。

div.container.column.main#division(width = "100", height = "100")

此代码行将转换为以下内容:

<div class = "container column main" id = "division" width = "100" height = "100"></div>

将值传递给模板

当我们渲染 Pug 模板时,我们实际上可以从路由处理程序传递一个值,然后我们可以在模板中使用它。创建一个新的路由处理程序,如下所示。

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

app.get('/dynamic_view', function(req, res){
   res.render('dynamic', {
      name: "TutorialsPoint", 
      url:"https://tutorialspoint.com"
   });
});

app.listen(3000);

并在 views 目录中创建一个新的视图文件,名为dynamic.pug,包含以下代码:

html
   head
      title=name
   body
      h1=name
      a(href = url) URL

在浏览器中打开 localhost:3000/dynamic_view;您应该获得以下输出:

Variables in template

我们也可以在文本中使用这些传递的变量。要在标签文本之间插入传递的变量,我们使用#{variableName}语法。例如,在上面的示例中,如果我们想放置 Greetings from TutorialsPoint,那么我们可以执行以下操作。

html
   head
      title = name
   body
      h1 Greetings from #{name}
      a(href = url) URL

这种使用值的方法称为插值。以上代码将显示以下输出:

Templating Inter

条件语句

我们也可以使用条件语句和循环结构。

考虑以下内容:

如果用户已登录,页面应显示“Hi, User”,否则显示“Login/Sign Up”链接。为了实现这一点,我们可以定义一个简单的模板,如下所示:

html
   head
      title Simple template
   body
      if(user)
         h1 Hi, #{user.name}
      else
         a(href = "/sign_up") Sign Up

当我们使用我们的路由渲染它时,我们可以像在以下程序中一样传递一个对象:

res.render('/dynamic',{
   user: {name: "Ayush", age: "20"}
});

您将收到一条消息:Hi, Ayush。但是,如果我们不传递任何对象或传递一个没有 user 键的对象,那么我们将获得一个注册链接。

包含和组件

Pug 提供了一种非常直观的方式来创建网页组件。例如,如果您看到一个新闻网站,带有徽标和类别的页眉始终是固定的。与其将它复制到我们创建的每个视图中,我们可以使用include功能。以下示例显示了我们如何使用此功能:

创建 3 个具有以下代码的视图:

HEADER.PUG

div.header.
   I'm the header for this website.

CONTENT.PUG

html
   head
      title Simple template
   body
      include ./header.pug
      h3 I'm the main content
      include ./footer.pug

FOOTER.PUG

div.footer.
   I'm the footer for this website.

为此创建一个路由,如下所示:

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

app.get('/components', function(req, res){
    res.render('content');
});

app.listen(3000);

转到 localhost:3000/components,您将收到以下输出:

templating components

include也可用于包含纯文本、css 和 JavaScript。

Pug 还有许多其他功能。但这些超出了本教程的范围。您可以在 Pug 进一步探索 Pug。

ExpressJS - 提供静态文件

静态文件是客户端从服务器下载的文件,保持原样。创建一个新目录public。默认情况下,Express 不允许您提供静态文件。您需要使用以下内置中间件启用它。

app.use(express.static('public'));

注意:Express 相对静态目录查找文件,因此静态目录的名称不是 URL 的一部分。

请注意,根路由现在设置为您的 public 目录,因此您加载的所有静态文件都将以 public 作为根。要测试这是否正常工作,请在您的新public目录中添加任何图像文件,并将其名称更改为“testimage.jpg”。在您的视图中,创建一个新的视图并包含此文件,如下所示:

html
   head
   body
      h3 Testing static file serving:
      img(src = "/testimage.jpg", alt = "Testing Image

您应该获得以下输出:

Static Files Example

多个静态目录

我们还可以使用以下程序设置多个静态资产目录:

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

app.use(express.static('public'));
app.use(express.static('images'));

app.listen(3000);

虚拟路径前缀

我们还可以为提供静态文件提供路径前缀。例如,如果您想提供一个像'/static'这样的路径前缀,则需要在您的index.js文件中包含以下代码:

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

app.use('/static', express.static('public'));

app.listen(3000);

现在,每当您需要包含一个文件时,例如驻留在您的 public 目录中的名为 main.js 的脚本文件,请使用以下脚本标签:

<script src = "/static/main.js" />

当提供多个目录作为静态文件时,此技术非常有用。这些前缀可以帮助区分多个目录。

ExpressJS - 表单数据

表单是网页不可或缺的一部分。我们访问的几乎每个网站都提供表单,以便为我们提交或获取一些信息。要开始使用表单,我们首先需要安装body-parser(用于解析 JSON 和 url 编码数据)和 multer(用于解析 multipart/form 数据)中间件。

要安装body-parsermulter,请转到您的终端并使用以下命令:

npm install --save body-parser multer

将您的index.js文件内容替换为以下代码:

var express = require('express');
var bodyParser = require('body-parser');
var multer = require('multer');
var upload = multer();
var app = express();

app.get('/', function(req, res){
   res.render('form');
});

app.set('view engine', 'pug');
app.set('views', './views');

// for parsing application/json
app.use(bodyParser.json()); 

// for parsing application/xwww-
app.use(bodyParser.urlencoded({ extended: true })); 
//form-urlencoded

// for parsing multipart/form-data
app.use(upload.array()); 
app.use(express.static('public'));

app.post('/', function(req, res){
   console.log(req.body);
   res.send("recieved your request!");
});
app.listen(3000);

导入 body parser 和 multer 后,我们将使用body-parser解析 json 和 x-www-form-urlencoded 头请求,而使用multer解析 multipart/form-data。

让我们创建一个 html 表单来测试一下。创建一个名为form.pug的新视图,内容如下:

html
html
   head
      title Form Tester
   body
      form(action = "/", method = "POST")
         div
            label(for = "say") Say:
            input(name = "say" value = "Hi")
         br
         div
            label(for = "to") To:
            input(name = "to" value = "Express forms")
         br
         button(type = "submit") Send my greetings

使用以下命令运行您的服务器。

nodemon index.js

现在访问 localhost:3000/ 并根据需要填写表单并提交。将显示以下响应:

Response to form submission

查看您的控制台;它将向您显示请求主体作为 JavaScript 对象,如下面的屏幕截图所示:

Console output for form

req.body对象包含您解析的请求主体。要使用该对象中的字段,只需像使用普通 JS 对象一样使用它们即可。

这是最推荐的发送请求的方式。还有许多其他方法,但这里与之无关,因为我们的 Express 应用程序将以相同的方式处理所有这些请求。要详细了解不同的请求方式,请查看页面。

ExpressJS - 数据库

我们不断收到请求,但最终没有将它们存储在任何地方。我们需要一个数据库来存储数据。为此,我们将使用名为MongoDB的 NoSQL 数据库。

要安装并了解 Mongo,请遵循此链接。

为了将 Mongo 与 Express 一起使用,我们需要一个 Node 的客户端 API。我们有多种选择,但对于本教程,我们将坚持使用mongoose。Mongoose 用于 MongoDB 的 Node 中的文档建模。对于文档建模,我们创建一个模型(类似于面向文档编程中的),然后我们使用此模型生成文档(就像我们在 OOP 中创建类的文档一样)。我们所有的处理都将在这些“文档”上完成,然后最终,我们将这些文档写入我们的数据库。

设置 Mongoose

现在您已经安装了 Mongo,让我们安装 Mongoose,就像我们安装其他 Node 包一样:

npm install --save mongoose

在开始使用 mongoose 之前,我们必须使用 Mongo shell 创建一个数据库。要创建一个新的数据库,请打开您的终端并输入“mongo”。将启动一个 Mongo shell,输入以下代码:

use my_db

将为您创建一个新的数据库。每当您打开 mongo shell 时,它都会默认使用“test”数据库,您需要使用与上面相同的命令切换到您的数据库。

要使用 Mongoose,我们将在index.js文件中引入它,然后连接到在mongodb://127.0.0.1上运行的mongodb服务。

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1/my_db');

现在我们的应用程序已连接到我们的数据库,让我们创建一个新的模型。此模型将充当我们数据库中的集合。要创建一个新的模型,请在定义任何路由之前使用以下代码:

var personSchema = mongoose.Schema({
   name: String,
   age: Number,
   nationality: String
});
var Person = mongoose.model("Person", personSchema);

以上代码定义了人的模式,并用于创建 Mongoose 模型Person

保存文档

现在,我们将创建一个新的 html 表单;此表单将帮助您获取一个人的详细信息并将其保存到我们的数据库中。要创建表单,请在 views 目录中创建一个名为person.pug的新视图文件,内容如下:

html
head
   title Person
   body
      form(action = "/person", method = "POST")
      div
         label(for = "name") Name: 
         input(name = "name")
      br
      div
         label(for = "age") Age: 
         input(name = "age")
      br
      div
         label(for = "nationality") Nationality: 
         input(name = "nationality")
      br
      button(type = "submit") Create new person

还在index.js中添加一个新的 get 路由以渲染此文档:

app.get('/person', function(req, res){
   res.render('person');
});

转到“localhost:3000/person”以检查表单是否显示了正确的输出。请注意,这仅仅是 UI,它还没有工作。以下屏幕截图显示了表单的显示方式:

Mongoose Create

我们现在将在'/person'处定义一个 post 路由处理程序来处理此请求

app.post('/person', function(req, res){
   var personInfo = req.body; //Get the parsed information
   
   if(!personInfo.name || !personInfo.age || !personInfo.nationality){
      res.render('show_message', {
         message: "Sorry, you provided worng info", type: "error"});
   } else {
      var newPerson = new Person({
         name: personInfo.name,
         age: personInfo.age,
         nationality: personInfo.nationality
      });
		
      newPerson.save(function(err, Person){
         if(err)
            res.render('show_message', {message: "Database error", type: "error"});
         else
            res.render('show_message', {
               message: "New person added", type: "success", person: personInfo});
      });
   }
});

在上面的代码中,如果我们收到任何空字段或未收到任何字段,我们将发送错误响应。但是,如果我们收到一个格式良好的文档,那么我们将从 Person 模型创建一个newPerson文档并使用newPerson.save()函数将其保存到我们的数据库中。这在 Mongoose 中定义,并接受回调作为参数。此回调有两个参数 - 错误和响应。这些参数将渲染show_message视图。

要显示此路由的响应,我们还需要创建一个show_message视图。使用以下代码创建一个新视图:

html
   head
      title Person
   body
      if(type == "error")
         h3(style = "color:red") #{message}
      else
         h3 New person, 
            name: #{person.name}, 
            age: #{person.age} and 
            nationality: #{person.nationality} added!

成功提交表单(show_message.pug)后,我们将收到以下响应:

Mongoose Response

现在我们有一个创建人员的界面。

检索文档

Mongoose 提供了许多用于检索文档的函数,我们将重点介绍其中的 3 个。所有这些函数都将回调作为最后一个参数,并且就像 save 函数一样,它们的 arguments 是错误和响应。这三个函数如下:

Model.find(conditions, callback)

此函数查找与 conditions 对象中的字段匹配的所有文档。在 Mongo 中使用的相同运算符也适用于 mongoose。例如,

Person.find(function(err, response){
   console.log(response);
});

这将从人员集合中获取所有文档。

Person.find({name: "Ayush", age: 20}, 
   function(err, response){
      console.log(response);
});

这将获取名称字段为“Ayush”且年龄为 20 的所有文档。

我们还可以提供所需的投影,即我们所需的字段。例如,如果我们只需要国籍为“Indian”的人的姓名,则使用:

Person.find({nationality: "Indian"}, "name", function(err, response){
   console.log(response);
});

Model.findOne(conditions, callback)

此函数始终获取一个最相关的单个文档。它与Model.find()具有完全相同的 arguments。

Model.findById(id, callback)

此函数将_id(由 mongo 定义)作为第一个 arguments,一个可选的投影字符串和一个回调来处理响应。例如,

Person.findById("507f1f77bcf86cd799439011", function(err, response){
   console.log(response);
});

现在让我们创建一个路由来查看所有人员记录:

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

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1/my_db');

var personSchema = mongoose.Schema({
   name: String,
   age: Number,
   nationality: String
});

var Person = mongoose.model("Person", personSchema);

app.get('/people', function(req, res){
   Person.find(function(err, response){
      res.json(response);
   });
});

app.listen(3000);

更新文档

Mongoose 提供了 3 个用于更新文档的函数。这些函数如下所述:

Model.update(condition, updates, callback)

此函数以条件和更新对象作为输入,并将更改应用于集合中与条件匹配的所有文档。例如,以下代码将更新所有 Person 文档中的国籍“American”:

Person.update({age: 25}, {nationality: "American"}, function(err, response){
   console.log(response);
});

Model.findOneAndUpdate(condition, updates, callback)

它根据查询查找一个文档,并根据第二个 arguments 更新该文档。它还将回调作为最后一个 arguments。让我们执行以下示例以了解该函数

Person.findOneAndUpdate({name: "Ayush"}, {age: 40}, function(err, response) {
   console.log(response);
});

Model.findByIdAndUpdate(id, updates, callback)

此函数更新由其 id 标识的单个文档。例如,

Person.findByIdAndUpdate("507f1f77bcf86cd799439011", {name: "James"}, 
   function(err, response){
      console.log(response);
});

现在让我们创建一个路由来更新人员。这将是一个PUT路由,其中 id 作为参数,详细信息在有效负载中。

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

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1/my_db');

var personSchema = mongoose.Schema({
   name: String,
   age: Number,
   nationality: String
});

var Person = mongoose.model("Person", personSchema);

app.put('/people/:id', function(req, res){
   Person.findByIdAndUpdate(req.params.id, req.body, function(err, response){
      if(err) res.json({message: "Error in updating person with id " + req.params.id});
      res.json(response);
   });
});

app.listen(3000);

要测试此路由,请在您的终端中输入以下内容(将 id 替换为您创建的人员中的 id):

curl -X PUT --data "name = James&age = 20&nationality = American
"https://127.0.0.1:3000/people/507f1f77bcf86cd799439011

这将使用上述详细信息更新与路由中提供的 id 关联的文档。

删除文档

我们已经介绍了创建、读取更新,现在我们将了解如何使用 Mongoose 来删除文档。我们这里有 3 个函数,与更新完全一样。

Model.remove(condition, [callback])

此函数以条件对象作为输入,并删除与条件匹配的所有文档。例如,如果我们需要删除所有 20 岁的用户,请使用以下语法:

Person.remove({age:20});

Model.findOneAndRemove(condition, [callback])

此函数根据条件对象删除单个最相关的文档。让我们执行以下代码以了解相同的内容。

Person.findOneAndRemove({name: "Ayush"});

Model.findByIdAndRemove(id, [callback])

此函数删除由其 id 标识的单个文档。例如,

Person.findByIdAndRemove("507f1f77bcf86cd799439011");

现在让我们创建一个路由来从我们的数据库中删除人员。

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

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1/my_db');

var personSchema = mongoose.Schema({
   name: String,
   age: Number,
   nationality: String
});

var Person = mongoose.model("Person", personSchema);

app.delete('/people/:id', function(req, res){
   Person.findByIdAndRemove(req.params.id, function(err, response){
      if(err) res.json({message: "Error in deleting record id " + req.params.id});
      else res.json({message: "Person with id " + req.params.id + " removed."});
   });
});

app.listen(3000);

要检查输出,请使用以下 curl 命令:

curl -X DELETE https://127.0.0.1:3000/people/507f1f77bcf86cd799439011

这将删除具有给定 id 的人员,并生成以下消息:

{message: "Person with id 507f1f77bcf86cd799439011 removed."}

这总结了我们如何使用 MongoDB、Mongoose 和 Express 创建简单的 CRUD 应用程序。要进一步探索 Mongoose,请阅读API 文档。

ExpressJS - Cookies

Cookie 是简单的、小型的文件/数据,随服务器请求发送到客户端并在客户端存储。每次用户重新加载网站时,此 Cookie 都会随请求一起发送。这有助于我们跟踪用户的操作。

以下是 HTTP Cookie 的众多用途:

  • 会话管理
  • 个性化(推荐系统)
  • 用户跟踪

要将 Cookie 与 Express 一起使用,我们需要 cookie-parser 中间件。要安装它,请使用以下代码:

npm install --save cookie-parser

现在要将 Cookie 与 Express 一起使用,我们将需要cookie-parser。cookie-parser 是一个中间件,它解析附加到客户端请求对象的 Cookie。要使用它,我们将在index.js文件中引入它;这可以像我们使用其他中间件一样使用。这里,我们将使用以下代码。

var cookieParser = require('cookie-parser');
app.use(cookieParser());

cookie-parser 解析 Cookie 标头,并使用 Cookie 名称作为键填充req.cookies对象。要设置新的 Cookie,让我们在您的 Express 应用程序中定义一个新的路由,例如:

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

app.get('/', function(req, res){
   res.cookie('name', 'express').send('cookie set'); //Sets name = express
});

app.listen(3000);

要检查您的 Cookie 是否已设置,只需转到您的浏览器,启动控制台并输入:

console.log(document.cookie);

您将获得类似于以下的输出(您可能设置了更多 Cookie,可能是由于浏览器中的扩展程序):

"name = express"

浏览器每次查询服务器时也会发送回 Cookie。要在服务器控制台中查看服务器的 Cookie,请在路由中添加以下代码到该路由。

console.log('Cookies: ', req.cookies);

下次您向此路由发送请求时,您将收到以下输出。

Cookies: { name: 'express' }

添加具有过期时间的 Cookie

您可以添加过期的 Cookie。要添加过期的 Cookie,只需传递一个对象,该对象的“expire”属性设置为您希望它过期的时刻。例如,

//Expires after 360000 ms from the time it is set.
res.cookie(name, 'value', {expire: 360000 + Date.now()}); 

设置过期时间的另一种方法是使用'maxAge'属性。使用此属性,我们可以提供相对时间而不是绝对时间。以下为此方法的示例。

//This cookie also expires after 360000 ms from the time it is set.
res.cookie(name, 'value', {maxAge: 360000});

删除现有 Cookie

要删除 Cookie,请使用 clearCookie 函数。例如,如果您需要清除名为foo的 Cookie,请使用以下代码。

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

app.get('/clear_cookie_foo', function(req, res){
   res.clearCookie('foo');
   res.send('cookie foo cleared');
});

app.listen(3000);

在下一章中,我们将了解如何使用 Cookie 管理会话。

ExpressJS - Session

HTTP 协议是无状态的;为了将一个请求与任何其他请求关联起来,你需要一种方法在 HTTP 请求之间存储用户数据。Cookie 和 URL 参数都是传输客户端和服务器之间数据的合适方法。但它们都可以在客户端读取。会话解决了这个问题。你为客户端分配一个 ID,它使用该 ID 发出所有后续请求。与客户端相关的信息存储在服务器上,并与该 ID 链接。

我们需要 Express-session,所以使用以下代码安装它。

npm install --save express-session

我们将 sessioncookie-parser 中间件部署到位。在本例中,我们将使用默认存储来存储会话,即 MemoryStore。切勿在生产环境中使用此存储。会话中间件为我们处理所有事情,例如创建会话、设置会话 Cookie 以及在 req 对象中创建会话对象。

每当我们再次从同一客户端发出请求时,我们都会将他们的会话信息存储在我们这里(前提是服务器没有重启)。我们可以向会话对象添加更多属性。在以下示例中,我们将为客户端创建一个页面访问计数器。

var express = require('express');
var cookieParser = require('cookie-parser');
var session = require('express-session');

var app = express();

app.use(cookieParser());
app.use(session({secret: "Shh, its a secret!"}));

app.get('/', function(req, res){
   if(req.session.page_views){
      req.session.page_views++;
      res.send("You visited this page " + req.session.page_views + " times");
   } else {
      req.session.page_views = 1;
      res.send("Welcome to this page for the first time!");
   }
});
app.listen(3000);

上面代码的作用是,当用户访问网站时,它会为用户创建一个新会话并为其分配一个 Cookie。下次用户访问时,会检查 Cookie 并相应地更新 page_view 会话变量。

现在,如果你运行应用程序并访问 localhost:3000,将会显示以下输出。

First visit

如果你重新访问页面,页面计数器会增加。以下屏幕截图中的页面刷新了 42 次。

First visit

ExpressJS - 认证

身份验证是一个过程,在这个过程中,提供的凭据与本地操作系统或身份验证服务器上授权用户的信息数据库中的文件进行比较。如果凭据匹配,则过程完成,并且用户被授予访问权限。

为了创建身份验证系统,我们需要创建一个注册页面和一个用户密码存储。以下代码为我们创建了一个帐户并将其存储在内存中。这仅仅是为了演示的目的;建议始终使用持久性存储(数据库或文件)来存储用户信息。

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var multer = require('multer');
var upload = multer(); 
var session = require('express-session');
var cookieParser = require('cookie-parser');

app.set('view engine', 'pug');
app.set('views','./views');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true })); 
app.use(upload.array());
app.use(cookieParser());
app.use(session({secret: "Your secret key"}));

var Users = [];

app.get('/signup', function(req, res){
   res.render('signup');
});

app.post('/signup', function(req, res){
   if(!req.body.id || !req.body.password){
      res.status("400");
      res.send("Invalid details!");
   } else {
      Users.filter(function(user){
         if(user.id === req.body.id){
            res.render('signup', {
               message: "User Already Exists! Login or choose another user id"});
         }
      });
      var newUser = {id: req.body.id, password: req.body.password};
      Users.push(newUser);
      req.session.user = newUser;
      res.redirect('/protected_page');
   }
});

app.listen(3000);

现在,对于注册表单,创建一个名为 signup.jade 的新视图。

SIGNUP.JADE

html
   head
      title Signup
   body
      if(message)
         h4 #{message}
         form(action = "/signup" method = "POST")
         input(name = "id" type = "text" required placeholder = "User ID")
         input(name = "password" type = "password" required placeholder = "Password")
         button(type = "Submit") Sign me up!

通过访问 localhost:3000/signup 检查此页面是否加载。

Signup form

我们已为这两个字段设置了 required 属性,因此启用了 HTML5 的浏览器在提供 ID 和密码之前不会让我们提交此表单。如果有人尝试使用 curl 请求在没有用户 ID 或密码的情况下进行注册,则会显示错误。在 views 中创建一个名为 protected_page.pug 的新文件,内容如下:

html
   head
      title Protected page
   body
      div Hey #{id}, How are you doing today?
      div Want to log out?
      div Logout

此页面仅在用户刚刚注册或登录时可见。现在让我们定义其路由以及登录和注销的路由:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var multer = require('multer');
var upload = multer(); 
var session = require('express-session');
var cookieParser = require('cookie-parser');

app.set('view engine', 'pug');
app.set('views','./views');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true })); 
app.use(upload.array());
app.use(cookieParser());
app.use(session({secret: "Your secret key"}));

var Users = [];

app.get('/signup', function(req, res){
   res.render('signup');
});

app.post('/signup', function(req, res){
   if(!req.body.id || !req.body.password){
      res.status("400");
      res.send("Invalid details!");
   } else {
      Users.filter(function(user){
         if(user.id === req.body.id){
            res.render('signup', {
               message: "User Already Exists! Login or choose another user id"});
         }
      });
      var newUser = {id: req.body.id, password: req.body.password};
      Users.push(newUser);
      req.session.user = newUser;
      res.redirect('/protected_page');
   }
});
function checkSignIn(req, res){
   if(req.session.user){
      next();     //If session exists, proceed to page
   } else {
      var err = new Error("Not logged in!");
      console.log(req.session.user);
      next(err);  //Error, trying to access unauthorized page!
   }
}
app.get('/protected_page', checkSignIn, function(req, res){
   res.render('protected_page', {id: req.session.user.id})
});

app.get('/login', function(req, res){
   res.render('login');
});

app.post('/login', function(req, res){
   console.log(Users);
   if(!req.body.id || !req.body.password){
      res.render('login', {message: "Please enter both id and password"});
   } else {
      Users.filter(function(user){
         if(user.id === req.body.id && user.password === req.body.password){
            req.session.user = user;
            res.redirect('/protected_page');
         }
      });
      res.render('login', {message: "Invalid credentials!"});
   }
});

app.get('/logout', function(req, res){
   req.session.destroy(function(){
      console.log("user logged out.")
   });
   res.redirect('/login');
});

app.use('/protected_page', function(err, req, res, next){
console.log(err);
   //User should be authenticated! Redirect him to log in.
   res.redirect('/login');
});

app.listen(3000);

我们创建了一个中间件函数 checkSignIn 来检查用户是否已登录。protected_page 使用此函数。要注销用户,我们销毁会话。

现在让我们创建登录页面。将视图命名为 login.pug 并输入以下内容:

html
   head
      title Signup
   body
      if(message)
         h4 #{message}
         form(action = "/login" method = "POST")
         input(name = "id" type = "text" required placeholder = "User ID")
         input(name = "password" type = "password" required placeholder = "Password")
         button(type = "Submit") Log in

我们简单的身份验证应用程序现在已经完成了;现在让我们测试一下应用程序。使用 nodemon index.js 运行应用程序,然后继续访问 localhost:3000/signup。

输入用户名和密码,然后点击注册。如果详细信息有效/唯一,您将被重定向到 protected_page

Protected page

现在退出应用程序。这将把我们重定向到登录页面:

Auth login

此路由受到保护,因此如果未经身份验证的人员尝试访问它,则会将其重定向到我们的登录页面。这就是关于基本用户身份验证的所有内容。始终建议我们使用持久性会话系统并使用哈希进行密码传输。现在有更好的方法来验证用户,利用 JSON 令牌。

ExpressJS - RESTful API

创建移动应用程序、单页面应用程序、使用 AJAX 调用以及向客户端提供数据始终需要 API。一种关于如何构建和命名这些 API 及其端点的流行架构风格称为 REST(具象状态传输)HTTP 1.1 在设计时就考虑了 REST 原则。REST 由 Roy Fielding 于 2000 年在其论文 Fielding Dissertations 中提出。

RESTful URI 和方法为我们提供了处理请求所需的大部分信息。下表总结了如何使用各种动词以及如何命名 URI。我们将在最后创建一个电影 API;现在让我们讨论一下它将如何构建。

方法 URI 详情 功能
GET /movies 安全,可缓存 获取所有电影及其详细信息的列表
GET /movies/1234 安全,可缓存 获取电影 ID 1234 的详细信息
POST /movies N/A 使用提供的详细信息创建一个新电影。响应包含此新创建资源的 URI。
PUT /movies/1234 幂等 修改电影 ID 1234(如果不存在则创建一个)。响应包含此新创建资源的 URI。
DELETE /movies/1234 幂等 如果存在,应删除电影 ID 1234。响应应包含请求的状态。
DELETE 或 PUT /movies 无效 应为无效。DELETEPUT 应指定它们正在处理哪个资源。

现在让我们在 Express 中创建此 API。我们将使用 JSON 作为我们的传输数据格式,因为它易于在 JavaScript 中使用并且具有其他优势。将你的 index.js 文件替换为以下程序中的 movies.js 文件。

index.js

var express = require('express');
var bodyParser = require('body-parser');
var multer = require('multer');
var upload = multer();

var app = express();

app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(upload.array());

//Require the Router we defined in movies.js
var movies = require('./movies.js');

//Use the Router on the sub route /movies
app.use('/movies', movies);

app.listen(3000);

现在我们已经设置了应用程序,让我们专注于创建 API。

首先设置 movies.js 文件。我们没有使用数据库来存储电影,而是将它们存储在内存中;因此,每次服务器重启时,我们添加的电影都会消失。这可以通过使用数据库或文件(使用 node fs 模块)轻松模拟。

导入 Express 后,创建一个路由器并使用 module.exports 导出它:

var express = require('express');
var router = express.Router();
var movies = [
   {id: 101, name: "Fight Club", year: 1999, rating: 8.1},
   {id: 102, name: "Inception", year: 2010, rating: 8.7},
   {id: 103, name: "The Dark Knight", year: 2008, rating: 9},
   {id: 104, name: "12 Angry Men", year: 1957, rating: 8.9}
];

//Routes will go here
module.exports = router;

GET 路由

让我们定义获取所有电影的 GET 路由:

router.get('/', function(req, res){
   res.json(movies);
});

要测试它是否正常工作,运行你的应用程序,然后打开你的终端并输入:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET 
localhost:3000/movies

将显示以下响应:

[{"id":101,"name":"Fight Club","year":1999,"rating":8.1},
{"id":102,"name":"Inception","year":2010,"rating":8.7},
{"id":103,"name":"The Dark Knight","year":2008,"rating":9},
{"id":104,"name":"12 Angry Men","year":1957,"rating":8.9}]

我们有一个获取所有电影的路由。现在让我们创建一个通过其 ID 获取特定电影的路由。

router.get('/:id([0-9]{3,})', function(req, res){
   var currMovie = movies.filter(function(movie){
      if(movie.id == req.params.id){
         return true;
      }
   });
   if(currMovie.length == 1){
      res.json(currMovie[0])
   } else {
      res.status(404);//Set status to 404 as movie was not found
      res.json({message: "Not Found"});
   }
});

这将根据我们提供的 ID 获取电影。要检查输出,请在你的终端中使用以下命令:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET 
localhost:3000/movies/101

你将得到以下响应:

{"id":101,"name":"Fight Club","year":1999,"rating":8.1}

如果你访问无效的路由,它会产生 无法获取错误,而如果你访问具有不存在 ID 的有效路由,它会产生 404 错误。

我们完成了 GET 路由,现在让我们继续 POST 路由。

POST 路由

使用以下路由处理 POST 的数据:

router.post('/', function(req, res){
   //Check if all fields are provided and are valid:
   if(!req.body.name ||
      !req.body.year.toString().match(/^[0-9]{4}$/g) ||
      !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){
      
      res.status(400);
      res.json({message: "Bad Request"});
   } else {
      var newId = movies[movies.length-1].id+1;
      movies.push({
         id: newId,
         name: req.body.name,
         year: req.body.year,
         rating: req.body.rating
      });
      res.json({message: "New movie created.", location: "/movies/" + newId});
   }
});

这将创建一个新的电影并将其存储在 movies 变量中。要检查此路由,请在你的终端中输入以下代码:

curl -X POST --data "name = Toy%20story&year = 1995&rating = 8.5" https://127.0.0.1:3000/movies

将显示以下响应:

{"message":"New movie created.","location":"/movies/105"}

要测试这是否已添加到 movies 对象中,请再次运行 /movies/105 的 get 请求。将显示以下响应:

{"id":105,"name":"Toy story","year":"1995","rating":"8.5"}

让我们继续创建 PUT 和 DELETE 路由。

PUT 路由

PUT 路由与 POST 路由几乎相同。我们将为要更新/创建的对象指定 ID。以以下方式创建路由。

router.put('/:id', function(req, res){
   //Check if all fields are provided and are valid:
   if(!req.body.name ||
      !req.body.year.toString().match(/^[0-9]{4}$/g) ||
      !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g) ||
      !req.params.id.toString().match(/^[0-9]{3,}$/g)){
      
      res.status(400);
      res.json({message: "Bad Request"});
   } else {
      //Gets us the index of movie with given id.
      var updateIndex = movies.map(function(movie){
         return movie.id;
      }).indexOf(parseInt(req.params.id));
      
      if(updateIndex === -1){
         //Movie not found, create new
         movies.push({
            id: req.params.id,
            name: req.body.name,
            year: req.body.year,
            rating: req.body.rating
         });
         res.json({message: "New movie created.", location: "/movies/" + req.params.id});
      } else {
         //Update existing movie
         movies[updateIndex] = {
            id: req.params.id,
            name: req.body.name,
            year: req.body.year,
            rating: req.body.rating
         };
         res.json({message: "Movie id " + req.params.id + " updated.", 
            location: "/movies/" + req.params.id});
      }
   }
});

此路由将执行上表中指定的功能。如果对象存在,它将使用新详细信息更新对象。如果不存在,它将创建一个新对象。要检查路由,请使用以下 curl 命令。这将更新现有的电影。要创建一个新电影,只需将 ID 更改为不存在的 ID。

curl -X PUT --data "name = Toy%20story&year = 1995&rating = 8.5" 
https://127.0.0.1:3000/movies/101

响应

{"message":"Movie id 101 updated.","location":"/movies/101"}

DELETE 路由

使用以下代码创建一个删除路由:

router.delete('/:id', function(req, res){
   var removeIndex = movies.map(function(movie){
      return movie.id;
   }).indexOf(req.params.id); //Gets us the index of movie with given id.
   
   if(removeIndex === -1){
      res.json({message: "Not found"});
   } else {
      movies.splice(removeIndex, 1);
      res.send({message: "Movie id " + req.params.id + " removed."});
   }
});

以与我们检查其他路由相同的方式检查路由。成功删除(例如 ID 105)后,你将获得以下输出:

{message: "Movie id 105 removed."}

最后,我们的 movies.js 文件将如下所示。

var express = require('express');
var router = express.Router();
var movies = [
   {id: 101, name: "Fight Club", year: 1999, rating: 8.1},
   {id: 102, name: "Inception", year: 2010, rating: 8.7},
   {id: 103, name: "The Dark Knight", year: 2008, rating: 9},
   {id: 104, name: "12 Angry Men", year: 1957, rating: 8.9}
];
router.get('/:id([0-9]{3,})', function(req, res){
   var currMovie = movies.filter(function(movie){
      if(movie.id == req.params.id){
         return true;
      }
   });
   
   if(currMovie.length == 1){
      res.json(currMovie[0])
   } else {
      res.status(404);  //Set status to 404 as movie was not found
      res.json({message: "Not Found"});
   }
});
router.post('/', function(req, res){
   //Check if all fields are provided and are valid:
   if(!req.body.name ||
      !req.body.year.toString().match(/^[0-9]{4}$/g) ||
      !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){
      res.status(400);
      res.json({message: "Bad Request"});
   } else {
      var newId = movies[movies.length-1].id+1;
      movies.push({
         id: newId,
         name: req.body.name,
         year: req.body.year,
         rating: req.body.rating
      });
      res.json({message: "New movie created.", location: "/movies/" + newId});
   }
});

router.put('/:id', function(req, res) {
   //Check if all fields are provided and are valid:
   if(!req.body.name ||
      !req.body.year.toString().match(/^[0-9]{4}$/g) ||
      !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g) ||
      !req.params.id.toString().match(/^[0-9]{3,}$/g)){
      res.status(400);
      res.json({message: "Bad Request"});
   } else {
      //Gets us the index of movie with given id.
      var updateIndex = movies.map(function(movie){
         return movie.id;
      }).indexOf(parseInt(req.params.id));
      
      if(updateIndex === -1){
         //Movie not found, create new
         movies.push({
            id: req.params.id,
            name: req.body.name,
            year: req.body.year,
            rating: req.body.rating
         });
         res.json({
            message: "New movie created.", location: "/movies/" + req.params.id});
      } else {
         //Update existing movie
         movies[updateIndex] = {
            id: req.params.id,
            name: req.body.name,
            year: req.body.year,
            rating: req.body.rating
         };
         res.json({message: "Movie id " + req.params.id + " updated.",
            location: "/movies/" + req.params.id});
      }
   }
});

router.delete('/:id', function(req, res){
   var removeIndex = movies.map(function(movie){
      return movie.id;
   }).indexOf(req.params.id); //Gets us the index of movie with given id.
   
   if(removeIndex === -1){
      res.json({message: "Not found"});
   } else {
      movies.splice(removeIndex, 1);
      res.send({message: "Movie id " + req.params.id + " removed."});
   }
});
module.exports = router;

这完成了我们的 REST API。现在你可以使用这种简单的架构风格和 Express 创建更复杂的应用程序。

ExpressJS - 脚手架

脚手架使我们能够轻松地创建 Web 应用程序的框架。我们手动创建我们的 public 目录,添加中间件,创建单独的路由文件等。脚手架工具为我们设置了所有这些东西,以便我们可以直接开始构建我们的应用程序。

我们将使用的脚手架工具称为 Yeoman。它是一个为 Node.js 构建的脚手架工具,但也为其他几个框架(如 flask、rails、django 等)提供了生成器。要安装 Yeoman,请在你的终端中输入以下命令:

npm install -g yeoman

Yeoman 使用生成器来构建应用程序。要查看 npm 上可用于 Yeoman 的生成器,你可以点击此 链接。在本教程中,我们将使用 'generator-Express-simple'。要安装此生成器,请在你的终端中输入以下命令:

npm install -g generator-express-simple

要使用此生成器,请输入以下命令:

yo express-simple test-app

系统会询问你一些简单的问题,例如你想在你的应用程序中使用哪些功能。选择以下答案,或者如果你已经了解这些技术,那么可以选择你希望它们如何使用。

express-simple comes with bootstrap and jquery
[?] Select the express version you want: 4.x
[?] Do you want an mvc express app: Yes
[?] Select the css preprocessor you would like to use: sass
[?] Select view engine you would like to use: jade
[?] Select the build tool you want to use for this project: gulp
[?] Select the build tool you want to use for this project: gulp
[?] Select the language you want to use for the build tool: javascript
   create public/sass/styles.scss
   create public/js/main.js
   create views/layout.jade
   create views/index.jade
   create views/404.jade
   create app.js
   create config.js
   create routes/index.js
   create package.json
   create bower.json
identical .bowerrc
identical .editorconfig
identical .gitignore
identical .jshintrc
   create gulpfile.js

I'm all done. Running bower install & npm install for you to install the
required dependencies. If this fails, try running the command yourself.

然后它将为你创建一个新的应用程序,安装所有依赖项,向你的应用程序添加一些页面(主页、404 未找到页面等)并为你提供一个目录结构来进行操作。

此生成器为我们创建了一个非常简单的结构。探索 Express 可用的众多生成器,并选择最适合你的那个。使用所有生成器的步骤相同。你需要安装一个生成器,使用 Yeoman 运行它;它会问你一些问题,然后根据你的答案为你的应用程序创建一个框架。

ExpressJS - 错误处理

Express 中的错误处理是使用中间件完成的。但是此中间件具有特殊属性。错误处理中间件的定义方式与其他中间件函数相同,只是错误处理函数 必须有四个参数而不是三个 - err, req, res, next。例如,要对任何错误发送响应,我们可以使用:

app.use(function(err, req, res, next) {
   console.error(err.stack);
   res.status(500).send('Something broke!');
});

到目前为止,我们一直在路由本身处理错误。错误处理中间件允许我们分离错误逻辑并相应地发送响应。我们在中间件中讨论的 next() 方法将我们带到下一个 中间件/路由处理程序

对于错误处理,我们有 next(err) 函数。对该函数的调用将跳过所有中间件,并使我们匹配到该路由的下一个错误处理程序。让我们通过一个例子来理解这一点。

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

app.get('/', function(req, res){
   //Create an error and pass it to the next function
   var err = new Error("Something went wrong");
   next(err);
});

/*
 * other route handlers and middleware here
 * ....
 */

//An error handling middleware
app.use(function(err, req, res, next) {
   res.status(500);
   res.send("Oops, something went wrong.")
});

app.listen(3000);

此错误处理中间件可以策略性地放置在路由之后,或者包含检测错误类型并相应地响应客户端的条件。上述程序将显示以下输出。

Error handling

ExpressJS - 调试

Express 使用 Debug 模块在内部记录有关路由匹配、中间件函数、应用程序模式等的信息。

要查看 Express 中使用的所有内部日志,在启动应用程序时将 DEBUG 环境变量设置为 Express:*

DEBUG = express:* node index.js

将显示以下输出。

Debug log

当你的应用程序的某个组件无法正常工作时,这些日志非常有用。此详细输出可能有点压倒性。你还可以将 DEBUG 变量限制为要记录的特定区域。例如,如果你希望将记录器限制在应用程序和路由器中,可以使用以下代码。

DEBUG = express:application,express:router node index.js

默认情况下,调试功能处于关闭状态,并在生产环境中自动打开。调试功能还可以扩展以满足你的需求,你可以在 其 npm 页面 上阅读更多相关信息。

ExpressJS - 最佳实践

与 Django 和 Rails 这些拥有明确开发规范(包括文件结构等)的框架不同,Express 并没有固定的开发方式。这意味着您可以根据自己的喜好构建应用程序的结构。但是,随着应用程序规模的扩大,如果没有一个明确定义的结构,维护起来将会非常困难。在本章中,我们将了解一些常用的目录结构和关注点分离方法,以便构建我们的应用程序。

首先,我们将讨论创建 Node 和 Express 应用程序的最佳实践。

  • 始终使用 **npm init** 开始一个 Node 项目。

  • 始终使用 **--save** 或 **--save-dev** 安装依赖项。这将确保如果您迁移到不同的平台,只需运行 npm install 即可安装所有依赖项。

  • 坚持使用小写文件名和驼峰式变量命名。如果您查看任何 npm 模块,其名称都是小写并用连字符分隔。当您需要这些模块时,请使用驼峰式命名法。

  • 不要将 node_modules 推送到您的代码仓库。相反,npm 会在开发机器上安装所有内容。

  • 使用 **config** 文件存储变量。

  • 将路由分组并隔离到它们自己的文件中。例如,在 REST API 页面中看到的电影示例中的 CRUD 操作。

目录结构

现在让我们讨论 Express 的目录结构。

网站

Express 没有社区定义的应用程序创建结构。以下是网站常用的项目结构。

test-project/
   node_modules/
   config/
      db.js                //Database connection and configuration
      credentials.js       //Passwords/API keys for external services used by your app
      config.js            //Other environment variables
   models/                 //For mongoose schemas
      users.js
      things.js
   routes/                 //All routes for different entities in different files 
      users.js
      things.js
   views/
      index.pug
      404.pug
        ...
   public/                 //All static content being served
      images/
      css/
      javascript/
   app.js
   routes.js               //Require all routes in this and then require this file in 
   app.js 
   package.json

还有其他方法可以使用 Express 构建网站。您可以使用 MVC 设计模式构建网站。有关更多信息,您可以访问以下链接。

https://code.tutsplus.com/tutorials/build-a-complete-mvc-website-with-expressjs--net-34168

以及,

https://www.terlici.com/2014/08/25/best-practices-express-structure.html.

RESTful API

API 的设计更简单,不需要公共目录或视图目录。使用以下结构构建 API:

test-project/
   node_modules/
   config/
      db.js                //Database connection and configuration
      credentials.js       //Passwords/API keys for external services used by your app
   models/                 //For mongoose schemas
      users.js
      things.js
   routes/                 //All routes for different entities in different files 
      users.js
      things.js
   app.js
   routes.js               //Require all routes in this and then require this file in 
   app.js 
   package.json

您还可以使用 Yeoman 生成器 获取类似的结构。

ExpressJS - 资源

本章列出了我们本教程中使用的各种资源。

广告