Koa.js - 快速指南

Koa.js - 概述

一个 Web 应用程序框架为您提供了一个简单的 API 来构建网站、Web 应用程序和后端。您无需担心低级协议、流程等。

什么是 Koa?

Koa 提供了一个最小的接口来构建应用程序。它是一个非常小的框架(600 LoC),它提供了构建应用程序所需的工具,并且非常灵活。npm 上有许多适用于 Koa 的模块,可以直接插入其中。Koa 可以被认为是 express.js 的核心,没有所有的花哨功能。

为什么是 Koa?

Koa 占用空间小(600 LoC),是节点上非常薄的抽象层,用于创建服务器端应用程序。它完全可插入,并且拥有庞大的社区。这也使我们能够轻松扩展 Koa 并根据需要使用它。它使用前沿技术 (ES6) 构建,这使其比 express 等旧框架更具优势。

Pug

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

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

它是与 Koa 一起使用的最流行的模板语言之一。

MongoDB 和 Mongoose

MongoDB 是一个开源文档数据库,旨在轻松开发和扩展。我们将使用此数据库来存储数据。

Mongoose 是 node.js 的客户端 API,可让您轻松地从 Koa 应用程序访问数据库。

Koa.js - 安装和环境设置

要开始使用 Koa 框架进行开发,您需要安装 Node 和 npm(节点包管理器)。如果您还没有安装这些,请转到 Node 设置 在本地系统上安装节点。通过在终端中运行以下命令确认已安装节点和 npm。

$ node --version
$ npm --version

您应该收到类似于的输出 −

v5.0.0
3.5.2

请确保您的节点版本高于 6.5.0。现在我们已经设置了 Node 和 npm,让我们了解 npm 是什么以及如何使用它。

Node 包管理器 (npm)

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

如何使用 npm?

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

全局 − 此方法通常用于安装开发工具和基于 CLI 的包。要全局安装包,请使用以下命令。

$ npm install -g <package-name>

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

$ npm install <package-name>

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

步骤 1 −启动终端/cmd,创建一个名为 hello-world 的新文件夹并进入其中 −

Environment mkdir

步骤 2 − 现在使用 npm 创建 package.json 文件,使用以下命令。

npm init

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

Environment NPM

只需继续按 Enter,然后在"作者姓名"字段中输入您的姓名。

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

$ npm install --save koa

要确认 Koa 已正确安装,请运行以下命令。

$ ls node_modules #(dir node_modules for windows)

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

这就是我们使用 Koa 框架开始开发所需的全部内容。为了使我们的开发过程更加简单,我们将从 npm 安装一个工具,nodemon。此工具的作用是,只要我们对任何文件进行更改,它就会重新启动我们的服务器,否则我们需要在每次修改文件后手动重新启动服务器。要安装 nodemon,请使用以下命令。

$ npm install -g nodemon

现在我们已准备好深入研究 Koa!

Koa.js - Hello World

一旦我们设置了开发,就该开始使用 Koa 开发我们的第一个应用程序了。创建一个名为 app.js 的新文件,并在其中输入以下内容。

var koa = require('koa');
var app = new koa();

app.use(function* (){
    this.body = 'Hello world!';
});

app.listen(3000, function(){
console.log('Server running on https://localhost:3000')
});

保存文件,转到您的终端并输入。

$ nodemon app.js

这将启动服务器。要测试此应用程序,请打开浏览器并转到 https://localhost:3000,您应该会收到以下消息。

Hello world

此应用程序如何工作?

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

app.use(function) − 此函数是一个中间件,每当我们的服务器收到请求时都会调用它。我们将在后续章节中了解有关中间件的更多信息。回调函数是一个生成器,我们将在下一章中看到它。此生成器的上下文在 Koa 中称为 context。此上下文用于访问和修改请求和响应对象。我们将此响应的主体设置为 Hello world!

app.listen(port, function) − 此函数绑定并侦听指定端口上的连接。Port 是此处唯一必需的参数。如果应用程序成功运行,则执行回调函数。

Koa.js - 生成器

JavaScript ES6 最令人兴奋的新功能之一是新函数,称为生成器。在生成器出现之前,整个脚本通常按从上到下的顺序执行,没有简单的方法停止代码执行并在稍后使用相同的堆栈恢复。生成器是可以退出并稍后重新进入的函数。它们的上下文(变量绑定)将在重新进入时保存。

生成器允许我们在中间停止代码执行。因此,让我们看一个简单的生成器。

var generator_func = function* (){
   yield 1;
   yield 2;
};

var itr = generator_func();
console.log(itr.next());
console.log(itr.next());
console.log(itr.next());

运行上述代码时,结果如下。

{ value: 1, done: false }
{ value: 2, done: false }
{ value: undefined, done: true }

让我们看看上面的代码。我们首先创建一个名为 generator_func() 的生成器。我们创建了这个看起来很奇怪的函数的一个实例,并将其分配给 itr。然后我们开始在这个 itr 变量上调用 next()

调用 next() 会启动生成器,它会一直运行,直到达到收益。然后它返回带有值和 done 的对象,其中值具有表达式值。这个表达式可以是任何东西。此时,它会暂停执行。再次,当我们调用此函数(next)时,生成器将从最后一个收益点恢复执行,函数状态与暂停时相同,直到下一个收益点。一直到代码中没有更多收益点为止,都会这样做。

Koa 中的生成器

那么我们为什么要在本教程中讨论生成器呢?您可能还记得 hello world 程序,我们使用 function* () 符号将回调传递给 app.use()。Koa 是一个对象,它包含一个中间件生成器函数数组,所有这些函数都是在每次请求时以类似堆栈的方式组合和执行的。Koa 还实现了控制流的下游和上游。

请看下面的例子,以更好地理解这一点。

var koa = require('koa');
var app = koa();
 
app.use(function* (next) {
    //在屈服于下一个生成器函数之前执行某些操作
    
    //在将成为下游中的第一个事件的行中
    console.log("1");
    屈服下一个;
    
    //在执行返回上游时执行某些操作,
    //这将是上游中的最后一个事件
    console.log("2");
});
app.use(function* (next) {
    // 这将是下游的第二个事件
    console.log("3");
    屈服下一个;
    
    // 这将是上游的第二个事件
    console.log("4");
});
app.use(function* () {
    // 这里将是下游的最后一个函数
    console.log("5");
    
    // 设置响应主体
    this.body = "Hello Generators";
    
    // 上游的第一个事件(从最后一个到第一个)
    console.log("6");
});

app.listen(3000);

运行上述代码并导航到 https://localhost:3000/ 时,我们会在控制台上获得以下输出。

1
3
5
6
4
2

这本质上就是 Koa 使用生成器的方式。它允许我们使用这个属性创建紧凑的中间件,并为上游和下游功能编写代码,从而节省我们的回调。

Koa.js - 路由

Web框架在不同的路由上提供HTML页面、脚本、图片等资源。Koa在核心模块中不支持路由,我们需要使用Koa-router模块来轻松在Koa中创建路由。使用以下命令安装此模块。

npm install --save koa-router

现在我们已经安装了Koa-router,让我们看一个简单的GET路由示例。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router(); //实例化路由器
_.get('/hello', getMessage); // 定义路由

function *getMessage() {
    this.body = "Hello world!";
};

app.use(_.routes()); //使用通过路由器定义的路由
app.listen(3000);

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

Routing Hello

我们还可以在同一路由中使用多种不同的方法。例如,

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router(); //实例化路由器

_.get('/hello', getMessage);
_.post('/hello', postMessage);

function *getMessage() {
    this.body = "Hello world!";
};
function *postMessage() {
     this.body = "You just called the post method at '/hello'!";
};
app.use(_.routes()); //使用通过路由器定义的路由
app.listen(3000);

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

curl -X POST "https://localhost:3000/hello"
Curl Routing

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

_.all('/test', allMessage);

function *allMessage(){
   this.body = "All HTTP calls regardless of the verb will get this response";
};

Koa.js - URL 构建

我们现在可以定义路由;它们要么是静态的,要么是固定的。要使用动态路由,我们需要提供不同类型的路由。使用动态路由允许我们传递参数并根据它们进行处理。以下是动态路由的示例。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router();

_.get('/:id', sendID);

function *sendID() {
    this.body = 'The id you specified is ' + this.params.id;
}

app.use(_.routes());
app.listen(3000);

要测试这一点,请转到 https://localhost:3000/123。您将获得以下响应。

URL Building ID

您可以将 URL 中的"123"替换为其他任何内容,它将反映在响应中。以下是上述内容的一个复杂示例。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router();

_.get('/things/:name/:id', sendIdAndName);

function *sendIdAndName(){
   this.body = 'id: ' + this.params.id + ' and name: ' + this.params.name;
};

app.use(_.routes());

app.listen(3000);

要测试这一点,请转到 https://localhost:3000/things/tutorialspoint/12345

URL Building Complex

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

模式匹配路由

您还可以使用正则表达式来限制 URL 参数匹配。假设您需要 id 为五位数字。您可以使用以下路由定义。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router();

_.get('/things/:id([0-9]{5})', sendID);

    function *sendID(){
this.body = 'id: ' + this.params.id;
}

app.use(_.routes());
app.listen(3000);

请注意,这将匹配具有 5 位长 id 的请求。您可以使用更复杂的正则表达式来匹配/验证您的路由。如果您的路由均不符合请求,您将收到一条"未找到"消息作为响应。

例如,如果我们定义与上述相同的路由,则在使用有效 URL 进行请求时,我们会得到 −

URL Matching Correct

Koa.js - HTTP 方法

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

Sr.No. 方法和说明
1

GET

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

2

POST

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

3

PUT

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

4

DELETE

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

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

Koa.js - Request 请求对象

Koa Request 请求对象是节点原始请求对象之上的抽象,提供对日常 HTTP 服务器开发有用的附加功能。Koa 请求对象嵌入在上下文对象 this 中。每当我们收到请求时,让我们注销请求对象。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router();

_.get('/hello', getMessage);

function *getMessage(){
   console.log(this.request);
   this.body = 'Your request has been logged.';
}
app.use(_.routes());
app.listen(3000);

当您运行此代码并导航到 https://localhost:3000/hello 时,您将收到以下响应。

Request Object

在您的控制台上,您将获得已注销的请求对象。

{ 
   method: 'GET',
   url: '/hello/',
   header: 
   { 
      host: 'localhost:3000',
      connection: 'keep-alive',
      'upgrade-insecure-requests': '1',
      'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) 
         AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36',
      accept: 'text/html,application/xhtml+xml,
         application/xml;q = 0.9,image/webp,*/*;q = 0.8',
      dnt: '1',
      'accept-encoding': 'gzip, deflate, sdch',
      'accept-language': 'en-US,en;q = 0.8' 
   }
}

使用此对象,我们可以访问请求的许多有用属性。让我们看一些例子。

request.header

提供所有请求标头。

request.method

提供请求方法(GET、POST 等)

request.href

提供完整的请求 URL。

request.path

提供请求的路径。没有查询字符串和基本 URL。

request.query

提供已解析的查询字符串。例如,如果我们在诸如 https://localhost:3000/hello/?name=Ayush&age=20&country=India 之类的请求上记录此信息,那么我们将获得以下对象。

{
   name: 'Ayush',
   age: '20',
   country: 'India'
}

request.accepts(type)

此函数根据请求的资源是否接受给定的请求类型返回 true 或 false。

您可以在 Request 文档中阅读有关请求对象的更多信息。

Koa.js - Response 响应对象

Koa Response 响应对象是节点原始响应对象之上的抽象,提供对日常 HTTP 服务器开发有用的附加功能。Koa 响应对象嵌入在上下文对象 this 中。每当我们收到请求时,让我们注销响应对象。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router();

_.get('/hello', getMessage);

function *getMessage(){
   this.body = 'Your request has been logged.';
   console.log(this.response);
}

app.use(_.routes());
app.listen(3000);

当您运行此代码并导航到 https://localhost:3000/hello 时,您将收到以下响应。

Request Object

在您的控制台上,您将获得已注销的请求对象。

{ 
   status: 200,
   message: 'OK',
   header: 
   {
      'content-type': 'text/plain; charset=utf-8',
      'content-length': '12' 
   },
   body: 'Your request has been logged.' 
}

状态和消息由 Koa 自动设置,但我们可以修改。如果我们不设置响应主体,状态代码将设置为 404。一旦我们设置了响应主体,状态将默认设置为 200。我们可以明确覆盖此行为。

使用此对象,我们可以访问响应的许多有用属性。让我们看一些例子 −

response.header

提供所有响应标头。

response.status

提供响应状态(200、404、500 等)。此属性也用于设置响应状态。

response.message

提供响应消息。此属性也用于设置带有响应的自定义消息。它与 response.status 相关联。

response.body

获取或设置响应主体。通常,我们使用上下文对象访问它。这只是访问它的另一种方式。主体可以是以下类型:String、Buffer、Stream、Object 或 Null。

response.type

获取或设置当前响应的内容类型。

response.get(field)

此函数用于获取不区分大小写值字段的标头的值。

response.set(field, value)

此函数用于使用字段和值对在响应上设置标头。

response.remove(field)

此函数用于使用字段名称在响应上取消设置标头。

您可以在 Response 的文档中阅读有关响应对象的更多信息。

Koa.js - 重定向

创建网站时,重定向非常重要。如果请求的 URL 格式不正确或服务器上存在一些错误,则应将其重定向到相应的错误页面。重定向还可用于阻止人们进入您网站的限制区域。

让我们创建一个错误页面,并在有人请求格式错误的 URL 时重定向到该页面。

var koa = require('koa');
var router = require('koa-router');
var app = koa();
var _ = router();

_.get('/not_found', printErrorMessage);
_.get('/hello', printHelloMessage);

app.use(_.routes());
app.use(handle404Errors);

function *printErrorMessage() {
   this.status = 404;
   this.body = "Sorry we do not have this resource.";
}
function *printHelloMessage() {
   this.status = 200;
   this.body = "Hey there!";
}
function *handle404Errors(next) {
   if (404 != this.status) return;
   this.redirect('/not_found');
}
app.listen(3000);

当我们运行此代码并导航到 /hello 以外的任何路由时,我们将被重定向到 /not_found。我们将中间件放在最后(app.use 函数调用此中间件)。这确保我们最终到达中间件并发送相应的响应。以下是我们运行上述代码时看到的结果。

当我们导航到 https://localhost:3000/hello 时,我们得到 −

Redirect Hello

如果我们导航到任何其他路由,我们将得到 −

Redirect Error

Koa.js - 错误处理

错误处理在构建 Web 应用程序中起着重要作用。Koa 也为此目的使用中间件。

在 Koa 中,您可以添加一个执行 try {yield next } 的中间件作为第一个中间件之一。如果我们在下游遇到任何错误,我们将返回到关联的 catch 子句并在此处处理错误。例如 −

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

//错误处理中间件
app.use(function *(next) {
   try {
      yield next;
   } catch (err) {
      this.status = err.status || 500;
      this.body = err.message;
      this.app.emit('error', err, this);
   }
});

//在下一个中间件中创建错误
//设置错误消息和状态代码并使用上下文对象抛出它

app.use(function *(next) {
    //这将设置状态和消息
    this.throw('Error Message', 500);
});

app.listen(3000);

我们故意在上面的代码中创建了一个错误,并在第一个中间件的 catch 块中处理该错误。然后将其发送到我们的控制台并作为响应发送给我们的客户端。以下是我们触发此错误时收到的错误消息。

InternalServerError: Error Message
   at Object.module.exports.throw 
      (/home/ayushgp/learning/koa.js/node_modules/koa/lib/context.js:91:23)
   at Object.<anonymous> (/home/ayushgp/learning/koa.js/error.js:18:13)
   at next (native)
   at onFulfilled (/home/ayushgp/learning/koa.js/node_modules/co/index.js:65:19)
   at /home/ayushgp/learning/koa.js/node_modules/co/index.js:54:5
   at Object.co (/home/ayushgp/learning/koa.js/node_modules/co/index.js:50:10)
   at Object.toPromise (/home/ayushgp/learning/koa.js/node_modules/co/index.js:118:63)
   at next (/home/ayushgp/learning/koa.js/node_modules/co/index.js:99:29)
   at onFulfilled (/home/ayushgp/learning/koa.js/node_modules/co/index.js:69:7)
   at /home/ayushgp/learning/koa.js/node_modules/co/index.js:54:5

现在发送到服务器的任何请求都将导致此错误。

Koa.js - 级联

中间件函数是可以访问 context 对象 和应用程序请求-响应周期中的下一个中间件函数的函数。这些函数用于修改请求和响应对象,以执行诸如解析请求主体、添加响应标头等任务。Koa 更进一步,产生 'downstream',然后将控制权流回 'upstream'。这种效果称为 级联

以下是中间件函数实际运行的简单示例。

var koa = require('koa');
var app = koa();
var _ = router();

//简单请求时间记录器
app.use(function* (next) {
    console.log("A new request received at " + Date.now());
    
    //此函数调用非常重要。它表示当前请求需要更多处理,并且位于下一个中间件函数/路由处理程序中。
    yield next;
});

app.listen(3000);

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

A new request received at 1467267512545

要将其限制到特定路由(及其所有子路由),我们只需像路由一样创建路由即可。实际上,这些中间件只是处理我们的请求。

例如,

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var _ = router();

//简单的请求时间记录器
_.get('/request/*', function* (next) {
    console.log("A new request received at " + Date.now());
    Yield next;
});

app.use(_.routes());
app.listen(3000);

现在,只要您请求"/request"的任何子路由,它就会记录时间。

中间件调用顺序

Koa 中关于中间件最重要的事情之一是,它们在文件中的编写/包含顺序就是它们在下游执行的顺序。只要我们在中间件中遇到一个yield 语句,它就会切换到下一个中​​间件,直到我们到达最后一个。然后我们再次开始向上移动并从yield 语句恢复函数。

例如,在下面的代码片段中,第一个函数首先执行直到yield,然后第二个中间件直到yield,然后是第三个。由于这里没有更多的中间件,我们开始向上移动,以相反的顺序执行,即第三、第二、第一。此示例总结了如何以 Koa 方式使用中间件。

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

//Order of middlewares
app.use(first);
app.use(second);
app.use(third);

function *first(next) {
   	console.log("I'll be logged first. ");

    //现在我们让位于下一个中间件
    yield next;
    
    //当所有其他中间件都结束后,我们将回到这里
    console.log("我将最后被记录。 ");
};

function *second(next) {
   console.log("I'll be logged second. ");
   yield next;
   console.log("I'll be logged fifth. ");
};

function *third(next) {
   console.log("I'll be logged third. ");
   yield next;
   console.log("I'll be logged fourth. ");
};

app.listen(3000);

运行此代码后,当我们访问 '/' 时,我们将在控制台上获得 −

I'll be logged first. 
I'll be logged second. 
I'll be logged third. 
I'll be logged fourth. 
I'll be logged fifth. 
I'll be logged last. 

下图总结了上述示例中实际发生的情况。

Middleware Desc

现在我们知道如何创建自己的中间件,让我们讨论一些最常用的社区创建的中间件。

第三方中间件

express 的第三方中间件列表可在此处获取。以下是一些最常用的中间件 −

  • koa-bodyparser
  • koa-router
  • koa-static
  • koa-compress

我们将讨论多个中间件。

Koa.js - 模板

Pug 是一个模板引擎。模板引擎用于消除服务器代码与 HTML 的混乱,将字符串与现有 HTML 模板进行疯狂连接。Pug 是一个非常强大的模板引擎,它具有多种功能,例如过滤器、包含、继承、插值等。这方面有很多内容需要介绍。

要将 Pug 与 Koa 一起使用,我们需要使用以下命令安装它。

$ npm install --save pug koa-pug

安装 pug 后,将其设置为应用程序的模板引擎。将以下代码添加到您的 app.js 文件中。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var Pug = require('koa-pug');
var pug = new Pug({
   viewPath: './views',
   basedir: './views',
   app: app //Equivalent to app.use(pug)
});

var _ = router(); //实例化路由器

app.use(_.routes()); //使用路由器定义的路由
app.listen(3000);

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

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

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

_.get('/hello', getMessage); // 定义路由

function *getMessage(){
    this.render('first_view');
};

您将收到输出为 −

Hello Views

Pug 所做的是将这个非常简单的标记转换为 html。我们不需要跟踪关闭标签,不需要使用 class 和 id 关键字,而是使用 '.' 和 '#' 来定义它们。上面的代码首先转换为

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

Pug 的功能远不止简化 HTML 标记。让我们来探索一下 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-->

属性

要定义属性,我们使用括号中以逗号分隔的属性列表。类和 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 koa = require('koa');
var router = require('koa-router');
var app = koa();

var Pug = require('koa-pug');
var pug = new Pug({
   viewPath: './views',
   basedir: './views',
   app: app // 等于 pug.use(app) 和 app.use(pug.middleware)
});

var _ = router(); //实例化路由器

_.get('//dynamic_view', dynamicMessage); //定义路由

function *dynamicMessage(){
   this.render('dynamic', {
      name: "TutorialsPoint", 
      url:"https://www.tutorialspoint.com"
   });
};

app.use(_.routes()); //使用通过路由器定义的路由
app.listen(3000);

然后,使用以下代码在 views 目录中创建一个名为 dynamic.pug 的新视图文件。

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

在浏览器中打开 localhost:3000/dynamic,输出内容如下。−

Templating Variables

我们还可以在文本中使用这些传递的变量。要在标签文本之间插入传递的变量,我们使用 #{variableName} 语法。例如,在上面的例子中,如果我们想插入来自 TutorialsPoint 的问候语,那么我们必须使用以下代码。

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

这种使用值的方法称为插值。

条件语句

我们也可以使用条件语句和循环结构。考虑这个实际示例,如果用户已登录,我们希望显示"嗨,用户",如果没有,那么我们希望向他显示"登录/注册"链接。为了实现这一点,我们可以定义一个简单的模板,例如 −

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

当我们使用我们的路由渲染它时,如果我们传递一个像 − 这样的对象

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

它会显示一条消息,显示 Hi, Ayush。但是,如果我们不传递任何对象或传递没有用户密钥的对象,那么我们将获得一个注册链接。

包含和组件

Pug 提供了一种非常直观的方式来为网页创建组件。例如,如果您看到一个新闻网站,带有徽标和类别的标题始终是固定的。我们可以使用包含,而不是将其复制到每个视图。以下示例显示了如何使用包含 −

使用以下代码创建三个视图 −

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 koa = require('koa');
var router = require('koa-router');
var app = koa();

var Pug = require('koa-pug');
var pug = new Pug({
   viewPath: './views',
   basedir: './views',
   app: app //相当于 app.use(pug)
});

var _ = router(); //实例化路由器

_.get('/components', getComponents);

function *getComponents(){
   this.render('content.pug');
}

app.use(_.routes()); //使用通过路由器定义的路由
app.listen(3000);

转到 localhost:3000/components,您应该获得以下输出。

Templating Components

include 还可用于包含纯文本、CSS 和 JavaScript。

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

Koa.js - 表单数据

表单是网络不可或缺的一部分。我们访问的几乎每个网站都提供表单,用于提交或获取一些信息。要开始使用表单,我们首先要安装 koa-body。要安装它,请转到您的终端并使用 −

$ npm install --save koa-body

用以下代码替换您的 app.js 文件内容。

var koa = require('koa');
var router = require('koa-router');
var bodyParser = require('koa-body');
var app = koa();

//设置 Pug
var Pug = require('koa-pug');
var pug = new Pug({
   viewPath: './views',
   basedir: './views',
   app: app //相当于 app.use(pug)
});

//设置 body 解析中间件
app.use(bodyParser({
   formidable:{uploadDir: './uploads'},
   multipart: true,
   urlencoded: true
}));

_.get('/', renderForm);
_.post('/', handleForm);

function * renderForm(){
   this.render('form');
}
function *handleForm(){
   console.log(this.request.body);
   console.log(this.req.body);
   this.body = this.request.body; //这是存储解析请求的地方
}

app.use(_.routes());
app.listen(3000);

我们在这里做的新事情是导入 body 解析器和 multer。我们使用 body 解析器来解析 json 和 x-www-form-urlencoded 标头请求,而我们使用 multer 来解析 multipart/form-data。

让我们创建一个 html 表单来测试一下!使用以下代码创建一个名为 form.pug 的新视图。

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 = "Koa form")
         br
         button(type = "submit") Send my greetings

使用以下方式运行你的服务器 −

nodemon index.js

现在转到 localhost:3000/ 并根据需要填写表单,然后提交。您将收到响应作为 −

Form Received

查看您的控制台,它将以 JavaScript 对象的形式显示您的请求正文。例如 −

Form Console

this.request.body 对象包含您解析的请求正文。要使用该对象的字段,只需将它们用作普通 JS 对象即可。

这只是发送请求的一种方法。还有许多其他方法,但这些方法与此处介绍无关,因为我们的 Koa 应用程序将以相同的方式处理所有这些请求。要了解有关发出请求的不同方式的更多信息,请查看页面。

Koa.js - 文件上传

Web 应用程序需要提供允许文件上传的功能。让我们看看如何从客户端接收文件并将其存储在我们的服务器上。

我们已经使用 koa-body 中间件来解析请求。此中间件还用于处理文件上传。让我们创建一个表单,允许我们上传文件,然后使用 Koa 保存这些文件。首先创建一个名为 file_upload.pug 的模板,其中包含以下内容。

html
   head
      title File uploads
   body
      form(action = "/upload" method = "POST" enctype = "multipart/form-data")
         div
            input(type = "text" name = "name" placeholder = "Name")
         
         div
            input(type = "file" name = "image")
         
         div
            input(type = "submit")

请注意,您需要在表单中提供与上述相同的编码类型。现在让我们在服务器上处理这些数据。

var koa = require('koa');
var router = require('koa-router');
var bodyParser = require('koa-body');
var app = koa();

//设置 Pug
var Pug = require('koa-pug');
var pug = new Pug({
   viewPath: './views',
   basedir: './views',
   app: app 
});

//设置主体解析中间件
app.use(bodyParser({
    formidable:{uploadDir: './uploads'}, //这是文件所在的位置
  	multipart: true,
   	urlencoded: true
}));

var _ = router(); //实例化路由器

_.get('/files', renderForm);
_.post('/upload', handleForm);

function * renderForm(){
   this.render('file_upload');
}

function *handleForm(){
   console.log("Files: ", this.request.body.files);
   console.log("Fields: ", this.request.body.fields);
   this.body = "Received your data!"; //这是存储解析后的请求的地方
}

app.use(_.routes());
app.listen(3000);

运行此程序时,您将获得以下表单。

文件上传表单

提交此程序时,您的控制台将产生以下输出。

文件控制台屏幕

上传的文件存储在上述输出的路径中。您可以使用 this.request.body.files 访问请求中的文件,并通过 this.request.body.fields 访问该请求中的字段。

Koa.js - 静态文件

静态文件是客户端从服务器直接下载的文件。创建一个新目录 public。默认情况下,Express 不允许您提供静态文件。

我们需要一个中间件来实现此目的。继续安装 koa-serve

$ npm install --save koa-static

现在我们需要使用这个中间件。在此之前,创建一个名为 public 的目录。我们将在这里存储所有静态文件。这使我们能够确保服务器代码的安全,因为客户端无法访问此公共文件夹上方的任何内容。创建公共目录后,在其中创建一个名为 hello.txt 的文件,其中包含您喜欢的任何内容。现在将以下内容添加到您的 app.js。

var serve = require('koa-static');
var koa = require('koa');
var app = koa();

app.use(serve('./public'));

app.listen(3000);

注意 − Koa 查找相对于静态目录的文件,因此静态目录的名称不是 URL 的一部分。根路由现在设置为您的公共目录,因此您加载的所有静态文件都将被视为公共目录。要测试这是否正常工作,请运行您的应用程序并访问 https://localhost:3000/hello.txt

您应该得到以下输出。请注意,这不是 HTML 文档或 Pug 视图,而是一个简单的 txt 文件。

Static Files

多个静态目录

我们还可以使用 − 设置多个静态资产目录

var serve = require('koa-static');
var koa = require('koa');
var app = koa();

app.use(serve('./public'));
app.use(serve('./images'));

app.listen(3000);

现在,当我们请求文件时,Koa 将搜索这些目录并向我们发送匹配的文件。

Koa.js - Cookies

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

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

要在 Koa 中使用 cookie,我们有以下函数:ctx.cookies.set()ctx.cookies.get()。要设置新的 cookie,让我们在 Koa 应用中定义一条新路由。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

_.get('/', setACookie);

function *setACookie() {
this.cookies.set('foo', 'bar', {httpOnly: false});
}

var _ = router();

app.use(_.routes());
app.listen(3000);

要检查 cookie 是否已设置,只需转到浏览器,启动控制台,然后输入 −

console.log(document.cookie);

这将产生以下输出(您可能由于浏览器扩展而设置了更多 cookie)。

"foo = bar"

以下是上述示例。

Cookie

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

console.log('Cookies: foo = ', this.cookies.get('foo'));

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

Cookies: foo = bar

添加带有过期时间的 Cookie

您可以添加过期的 Cookie。要添加过期的 Cookie,只需传递一个对象,并将属性"expires"设置为您希望其过期的时间。例如,

var koa = require('koa');
var router = require('koa-router');
var app = koa();

_.get('/', setACookie);

function *setACookie(){
   //从设置时间起 360000 毫秒后过期。
	this.cookies.set('name', 'value', { 
      httpOnly: false, expires: 360000 + Date.now() });
}

var _ = router();

app.use(_.routes());
app.listen(3000);

删除现有 Cookie

要取消设置 Cookie,只需将 Cookie 设置为空字符串即可。例如,如果您需要清除名为 foo 的 Cookie,请使用以下代码。

var koa = require('koa');
var router = require('koa-router');
var app = koa();

_.get('/', setACookie);

function *setACookie(){
   //从设置时间起 360000 毫秒后过期。
   this.cookies.set('name', '');
}

var _ = router();

app.use(_.routes());
app.listen(3000);

这将取消设置所述 cookie。请注意,当在客户端代码中不使用 cookie 时,应将 HttpOnly 选项保留为 true。

Koa.js - 会话

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

我们需要 koa-session,因此使用 − 安装它

npm install --save koa-session

我们将安装 koa-session 中间件。在此示例中,我们将使用 RAM 来存储会话。切勿在生产环境中使用它。会话中间件处理所有事情,即创建会话、设置会话 cookie 以及在上下文对象中创建会话对象。

每当我们再次从同一客户端发出请求时,我们都会存储他们的会话信息(假设服务器未重新启动)。我们可以向此会话对象添加更多属性。在下面的示例中,我们将为客户端创建一个视图计数器。

var session = require('koa-session');
var koa = require('koa');
var app = koa();

app.keys = ['Shh, its a secret!'];
app.use(session(app)); // 包含会话中间件

app.use(function *(){
   var n = this.session.views || 0;
   this.session.views = ++n;
   
   if(n === 1)
      this.body = 'Welcome here for the first time!';
   else
      this.body = "You've visited this page " + n + " times!";
})

app.listen(3000);

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

现在,如果您运行应用程序并转到 localhost:3000,您将获得以下响应。

Session First

如果您重新访问该页面,页面计数器将增加。在本例中,页面刷新了 12 次。

Session 12

Koa.js - 身份验证

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

我们将创建一个非常基本的身份验证系统,该系统将使用基本 HTTP 身份验证。这是强制访问控制的最简单方法,因为它不需要 cookie、会话或其他任何东西。要使用此功能,客户端必须随其发出的每个请求一起发送授权标头。用户名和密码未加密,但会像下面这样连接成一个字符串。

用户名:密码

此字符串使用 Base64 编码,并且单词 Basic 放在此值之前。例如,如果您的用户名是 Ayush,密码是 India,那么字符串 "Ayush:India" 将以授权标头中的编码形式发送。

Authorization: Basic QXl1c2g6SW5kaWE=

要在您的 koa 应用中实现此功能,您需要 koa-basic-auth 中间件。使用 − 安装它

$ npm install --save koa-basic-auth

现在打开您的 app.js 文件并在其中输入以下代码。

//这是将要检查身份验证的内容
var credentials = { name: 'Ayush', pass: 'India' }

var koa = require('koa');
var auth = require('koa-basic-auth');
var _ = require('koa-router')();

var app = koa();

//错误处理中间件
app.use(function *(next){
   try {
      yield next;
   } catch (err) {
      if (401 == err.status) {
         this.status = 401;
         this.set('WWW-Authenticate', 'Basic');
         this.body = 'You have no access here';
      } else {
         throw err;
      }
   }
});

// 在此处将身份验证设置为第一个中间件。
// 如果用户未通过身份验证,则返回错误。
_.get('/protected', auth(credentials), function *(){
    this.body = '您有权访问受保护区域。';
    Yield next;
});

// 此处不存在身份验证中间件。
_.get('/unprotected', function*(next){
    this.body = "任何人都可以访问此区域";
    Yield next;
});

app.use(_.routes());
app.listen(3000);

我们创建了一个错误处理中间件来处理所有与身份验证相关的错误。然后,我们创建了 2 个路由 −

  • /protected − 仅当用户发送正确的身份验证标头时,才能访问此路由。对于所有其他情况,它将给出错误。

  • /unprotected − 任何人都可以访问此路由,无论是否经过身份验证。

现在,如果您向 /protected 发送请求时没有身份验证标头或使用错误的凭据,您将收到错误。例如,

$ curl https://localhost:3000/protected

您将收到以下响应 −

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic
Content-Type: text/plain; charset=utf-8
Content-Length: 28
Date: Sat, 17 Sep 2016 19:05:56 GMT
Connection: keep-alive

Please authenticate yourself

但是,如果凭证正确,您将获得预期的响应。例如,

$ curl -H "Authorization: basic QXl1c2g6SW5kaWE=" https://localhost:3000/protected -i

您将获得以下响应 −

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 38
Date: Sat, 17 Sep 2016 19:07:33 GMT
Connection: keep-alive

You have access to the protected area.

/unprotected 路由仍然可供所有人访问。

Koa.js - 压缩

压缩是一种简单有效的节省带宽和加快网站速度的方法。它仅与现代浏览器兼容,如果您的用户也使用旧版浏览器,则应谨慎使用。

从服务器发送响应时,如果使用压缩,则可以大大缩短加载时间。我们将使用名为 koa-compress 的中间件来处理文件的压缩以及设置适当的标头。

继续使用 − 安装中间件

$ npm install --save koa-compress

现在在您的 app.js 文件中,添加以下代码 −

var koa = require('koa');
var router = require('koa-router');
var app = koa();

var Pug = require('koa-pug');
var pug = new Pug({
   viewPath: './views',
   basedir: './views',
   app: app //相当于app.use(pug)
});

app.use(compress({
   filter: function (content_type) {
      return /text/i.test(content_type)
   },
   threshold: 2048,
   flush: require('zlib').Z_SYNC_FLUSH
}));

var _ = router(); //Instantiate the router

_.get('/', getRoot);

function *getRoot(next){
   this.render('index');
}

app.use(_.routes()); //使用通过路由器定义的路由
app.listen(3000);

这将我们的压缩中间件放置到位。filter 选项是一个函数,用于检查响应内容类型以决定是否压缩。threshold 选项是压缩的最小响应大小(以字节为单位)。这确保我们不会压缩每个小响应。

以下是未压缩的响应。

Uncompressed

以下是压缩后的类似响应。

Compressed

如果您查看底部的大小选项卡,您可以清楚地看到两者之间的差异。当我们压缩文件时,改进幅度超过 150%。

Koa.js - 缓存

缓存是指存储可重复使用的响应,以便使后续请求更快。每个浏览器都附带 HTTP 缓存的实现。我们所要做的就是确保每个服务器响应都提供正确的 HTTP 标头指令,以指示浏览器何时以及可以缓存响应多长时间。

以下是将缓存包含在 Web 应用中的一些好处 −

  • 您的网络成本会降低。如果您的内容被缓存,则您需要为每个后续请求发送更少的内容。

  • 您的网站速度和性能会提高。

  • 即使您的客户端处于离线状态,也可以提供您的内容。

我们将使用 koa-static-cache 中间件在我们的应用中实现缓存。使用 − 安装这些中间件

$ npm install --save koa-static-cache

转到你的 app.js 文件并向其中添加以下代码。

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

var path = require('path');
var staticCache = require('koa-static-cache');

app.use(staticCache(path.join(__dirname, 'public'), {
    maxAge: 365 * 24 * 60 * 60 //将这些文件添加到缓存中一年
}))

app.listen(3000);

koa-static-cache 中间件用于在客户端缓存服务器响应。 cache-control 标头是根据我们在初始化缓存对象时提供的选项设置的。我们已将此缓存响应的过期时间设置为 1 年。以下是文件缓存前后我们发送的请求的比较。

在此文件缓存之前,返回的状态代码为 200,这是正常的。响应标头包含有关要缓存的内容的多个信息,并且还为内容提供了 ETag

Before Cache

下次发送请求时,它会与 ETtag 一起发送。由于服务器上的内容没有改变,其对应的 ETag 也保持不变,客户端被告知其本地的副本与服务器将提供的内容是最新的,应该使用本地副本而不是再次请求。

After Cache

注意 − 要使任何缓存文件无效,您只需更改其文件名并更新其引用。这将确保您有一个新文件要发送给客户端,并且客户端无法从缓存中加载它。

Koa.js - 数据库

我们接收请求,但并未将它们存储在任何地方。我们需要一个数据库来存储数据。我们将使用一个著名的 NoSQL 数据库 MongoDB。要安装并了解 Mongo,请访问此链接。

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

设置 Mongoose

现在我们已经安装了 Mongo,让我们安装 mongoose,就像我们安装其他节点包一样。

$ npm install --save mongoose

在开始使用 mongoose 之前,我们必须使用 Mongo shell 创建一个数据库。要创建新数据库,请打开终端并输入"mongo"。 Mongo shell 将启动,输入以下内容。

use my_db

将为您创建一个新数据库。每当您打开 Mongo shell 时,它都会默认为"test"db,您必须使用与上述相同的命令更改为您的数据库。

要使用 mongoose,我们将在 app.js 文件中需要它,然后连接到在 mongodb://localhost 上运行的 mongod 服务

var koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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

app.use(_.routes());
app.listen(3000);

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

var koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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

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

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

app.use(_.routes());
app.listen(3000);

上述代码定义了人员的架构,并用于创建 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 路由来呈现此文档。

var koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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

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

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

_.get('/person', getPerson);

function *getPerson(next){
   this.render('person');
   yield next;
}

app.use(_.routes());
app.listen(3000);

转到 localhost:3000/person 检查我们的表单是否正确显示。请注意,这只是 UI,尚未运行。我们的表单如下所示。

Mongoose Create

我们现在将在"/person"处定义一个帖子路由处理程序来处理此请求。

var koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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

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

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

_.post('/person', createPerson);

function *createPerson(next){
   var self = this;
   var personInfo = self.request.body; //获取解析后的信息
   
   if(!personInfo.name || !personInfo.age || !personInfo.nationality){
      self.render(
         'show_message', {message: "Sorry, you provided wrong info", type: "error"});
   } else {
      var newPerson = new Person({
         name: personInfo.name,
         age: personInfo.age,
         nationality: personInfo.nationality
      });
      yield newPerson.save(function(err, res) {
         if(err)
            self.render('show_message', 
               {message: "Database error", type: "error"});
         else
            self.render('show_message', 
               {message: "New person added", type: "success", person: personInfo});
      });
   }
}

app.use(_.routes());
app.listen(3000);

在上面的代码中,如果我们收到任何空字段或没有收到任何字段,我们将发送错误响应。但是,如果我们收到格式正确的文档,则我们从 Person 模型创建一个 newPerson 文档,并使用 newPerson.save() 函数将其保存到我们的数据库中。这是在 mongoose 中定义的,并接受回调作为参数。此回调有两个参数,errorresponse。这将呈现 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 提供了很多用于检索文档的函数,我们将重点介绍其中的三个。所有这些函数也将回调作为最后一个参数,并且与 save 函数一样,它们的参数是 error 和 response。

这三个函数是 −

Model.find(conditions, callback)

此函数查找与条件对象中的字段匹配的所有文档。Mongo 中使用的相同运算符也适用于 mongoose。例如,这将从人员集合中获取所有文档。

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

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

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

我们还可以提供所需的投影,即所需的字段。例如,如果我们只想要国籍"Indian"的人的姓名,我们使用 −

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

Model.findOne(conditions, callback)

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

Model.findById(id, callback)

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

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

让我们创建一个查看所有人员记录的路由。

var koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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

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

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

_.get('/people', getPeople);
function *getPeople(next){
   var self = this;
   
   yield Person.find(function(err, response){
      self.body = response;
   });
}
app.use(_.routes());
app.listen(3000);

更新文档

Mongoose 提供三个函数来更新文档。

Model.update(condition, updates, callback)

此函数以条件和更新对象作为输入,并将更改应用于集合中符合条件的所有文档。例如,以下代码将更新所有 Person 文档,使其国籍为"美国"。

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

Model.findOneAndUpdate(condition, updates, callback)

它的作用正如其名称所示。根据查询查找一个文档,并根据第二个参数更新该文档。它还将回调作为最后一个参数。例如,

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 koa = require('koa');
var _ = require('koa-router')();
var app = koa();
var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/my_db');

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

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

_.put('/people/:id', updatePerson);

function *updatePerson() {
   var self = this;
   yield Person.findByIdAndUpdate(self.params.id, 
      {$set: {self.request.body}}, function(err, response){
      
      if(err) {
         self.body = {
            message: "Error in updating person with id " + self.params.id};
      } else {
         self.body = response;
      }
   });
}

app.use(_.routes());
app.listen(3000);

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

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

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

删除文档

我们已经介绍了Create、Read 和Update,现在我们将了解如何使用 mongoose 删除文档。这里有三个函数,与更新完全相同。

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 koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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

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

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

_.delete('/people/:id', deletePerson);
function *deletePerson(next){
   var self = this;
   yield Person.findByIdAndRemove(self.params.id, function(err, response){
      if(err) {
         self.body = {message: "Error in deleting record id " + self.params.id};
      } else {
         self.body = {message: "Person with id " + self.params.id + " removed."};
      }
   });
}

app.use(_.routes());
app.listen(3000);

要测试这一点,请使用以下 curl 命令 −

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

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

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

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

Koa.js - 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。
删除 /movies/1234 幂等 如果存在电影 ID 1234,则应将其删除。响应应包含请求的状态。
DELETE 或 PUT /movies 无效 应为无效。DELETE 和 PUT 应指定它们正在处理的资源。

现在让我们在 Koa 中创建此 API。我们将使用 JSON 作为传输数据格式,因为它在 JavaScript 中易于使用,并且具有许多其他好处。将您的 index.js 文件替换为以下 −

INDEX.JS

var koa = require('koa');
var router = require('koa-router');
var bodyParser = require('koa-body');

var app = koa();

//设置 body 解析中间件
app.use(bodyParser({
    formidable:{uploadDir: './uploads'},
    multipart: true,
    urlencoded: true
}));

//需要我们在 movies.js 中定义的 Router
var movies = require('./movies.js');

//在子路由 /movies 上使用 Router
app.use(movies.routes());

app.listen(3000);

现在我们已经设置好了应用程序,让我们集中精力创建 API。首先设置 movies.js 文件。我们不使用数据库来存储电影,而是将它们存储在内存中,因此每次服务器重新启动时,我们添加的电影都会消失。这可以通过使用数据库或文件(使用 node fs 模块)轻松模拟。

导入 koa-router,创建一个 Router 并使用 module.exports 导出它。

var Router = require('koa-router');
var router = Router({
prefix: '/movies'
}); //为所有路由添加 /movies 前缀

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}
];

//路由将在此处进行

module.exports = router;

GET 路由

定义获取所有电影的 GET 路由。

router.get('/', sendMovies);
function *sendMovies(next){
    this.body = movies;
    Yield next;
}

就是这样。要测试这是否正常工作,请运行您的应用程序,然后打开您的终端并输入 −

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,})', sendMovieWithId);

function *sendMovieWithId(next){
   var ctx = this;
   var currMovie = movies.filter(function(movie){
      if(movie.id == ctx.params.id){
         return true;
      }
   });
   if(currMovie.length == 1){
      this.body = currMovie[0];
   } else {
      this.response.status = 404;//由于未找到电影,将状态设置为 404
      this.body = {message: "Not Found"};
   }
   yield next;
}

这将根据我们提供的 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}

如果您访问无效路由,它将产生无法 GET 错误,而如果您访问具有不存在的 id 的有效路由,它将产生 404 错误。

我们已完成 GET 路由。现在,让我们继续讨论 POST 路由。

POST 路由

使用以下路由处理 POST 数据。

router.post('/', addNewMovie);

function *addNewMovie(next){
   //检查所有字段是否提供且有效:
   if(!this.request.body.name || 
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) || 
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      var newId = movies[movies.length-1].id+1;
      
      movies.push({
         id: newId,
         name: this.request.body.name,
         year: this.request.body.year,
         rating: this.request.body.rating
      });
      this.body = {message: "New movie created.", location: "/movies/" + newId};
   }
   yield next;
}

这将创建一个新电影并将其存储在 movies 变量中。要测试此路由,请在终端中输入以下内容 −

curl -X POST --data "name = Toy%20story&year = 1995&rating = 8.5"
https://localhost: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', updateMovieWithId);

function *updateMovieWithId(next){
   //检查所有字段是否提供且有效:
   if(!this.request.body.name || 
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) || 
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g) ||
      !this.params.id.toString().match(/^[0-9]{3,}$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      //获取具有指定 ID 的电影的索引。
      var updateIndex = movies.map(function(movie){
         return movie.id;
      }).indexOf(parseInt(this.params.id));
      
      if(updateIndex === -1){
         //Movie not found, create new movies.push({
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         });
         this.body = {message: "New movie created.", location: "/movies/" + this.params.id};    
      } else {
         //更新现有电影
         movies[updateIndex] = {
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         };
         this.body = {message: "Movie id " + this.params.id + " updated.", location: "/movies/" + this.params.id};
      }
   }
}

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

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

响应

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

DELETE 路由

使用以下代码创建删除路由。

router.delete('/:id', deleteMovieWithId);

function *deleteMovieWithId(next){
   var removeIndex = movies.map(function(movie){
      return movie.id;
   }).indexOf(this.params.id); //Gets us the index of movie with given id.
   
   if(removeIndex === -1){
      this.body = {message: "Not found"};
   } else {
      movies.splice(removeIndex, 1);
      this.body = {message: "Movie id " + this.params.id + " removed."};
   }
}

以与测试其他路由相同的方式测试该路由。成功删除后(例如 id 105),您将得到 −

{message: "Movie id 105 removed."}

最后,我们的 movies.js 文件看起来像 −

var Router = require('koa-router');
var router = Router({
prefix: '/movies'
}); //所有路由都以 /movies 为前缀
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
router.get('/', sendMovies);
router.get('/:id([0-9]{3,})', sendMovieWithId);
router.post('/', addNewMovie);
router.put('/:id', updateMovieWithId);
router.delete('/:id', deleteMovieWithId);

function *deleteMovieWithId(next){
   var removeIndex = movies.map(function(movie){
      return movie.id;
   }).indexOf(this.params.id); //获取具有指定 ID 的电影的索引。
   
   if(removeIndex === -1){
      this.body = {message: "Not found"};
   } else {
      movies.splice(removeIndex, 1);
      this.body = {message: "Movie id " + this.params.id + " removed."};
   }
}

function *updateMovieWithId(next) {
   //检查所有字段是否提供且有效:
   if(!this.request.body.name ||
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) ||
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g) ||
      !this.params.id.toString().match(/^[0-9]{3,}$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      //获取具有指定 ID 的电影的索引。
      var updateIndex = movies.map(function(movie){
         return movie.id;
      }).indexOf(parseInt(this.params.id));
      
      if(updateIndex === -1){
         //未找到影片,创建新影片
         movies.push({
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         });
         this.body = {message: "New movie created.", location: "/movies/" + this.params.id};
      } else {
         //更新现有电影
            movies[updateIndex] = {
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         };
         this.body = {message: "Movie id " + this.params.id + " updated.", 
            location: "/movies/" + this.params.id};
      }
   }
}

function *addNewMovie(next){
   //检查所有字段是否提供且有效:
   if(!this.request.body.name ||
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) ||
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      var newId = movies[movies.length-1].id+1;
      
      movies.push({
         id: newId,
         name: this.request.body.name,
         year: this.request.body.year,
         rating: this.request.body.rating
      });
      this.body = {message: "New movie created.", location: "/movies/" + newId};
   }
   yield next;
}
function *sendMovies(next){
   this.body = movies;
   yield next;
}
function *sendMovieWithId(next){
   var ctx = this
   
   var currMovie = movies.filter(function(movie){
      if(movie.id == ctx.params.id){
         return true;
      }
   });
   if(currMovie.length == 1){
      this.body = currMovie[0];
   } else {
      this.response.status = 404;//将状态设置为 404,因为未找到电影
      this.body = {message: "Not Found"};
   }
   yield next;
}
module.exports = router;

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

Koa.js - 日志记录

在创建 Web 应用程序时,日志记录非常有用,因为它们会告诉我们哪里出了问题。我们还可以了解出错情况的背景,并提出可能的解决方案。

要在 Koa 中启用日志记录,我们需要中间件 koa-logger。使用以下命令安装它。

$ npm install --save-dev koa-logger

现在在您的应用程序中,添加以下代码以启用日志记录。

var logger = require('koa-logger')
var koa = require('koa')

var app = koa()
app.use(logger())

app.use(function*(){
    this.body = "Hello Logger";
})

app.listen(3000)

运行此服务器并访问服务器上的任何路由。您应该会看到类似的日志 −

Logging

现在,如果您在特定路由或请求上遇到错误,这些日志应该可以帮助您找出每个路由或请求中出现的问题。

Koa.js - 脚手架

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

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

$ npm install -g yeoman

Yeoman 使用生成器来搭建应用程序。要查看 npm 上可用于 yeoman 的生成器,请前往此处。在本教程中,我们将使用"generator-koa"。要安装此生成器,请在终端中输入以下命令。

$ npm install -g generator-koa

要使用此生成器,请输入 −

yo koa

然后它将创建一个目录结构并为您创建以下文件。它还将为您安装必要的 npm 模块和 bower 组件。

create package.json
create test/routeSpec.js
create views/layout.html
create views/list.html
create public/styles/main.css
create public/scripts/.gitkeep
create controllers/messages.js
create app.js
create .editorconfig
create .jshintrc

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

该生成器为我们创建一个非常简单的结构。

.
├── controllers
│   └── messages.js
├── public
|   ├── scripts
|   └── styles
|       └── main.css    
├── test
|   └── routeSpec.js
├── views
|   ├── layout.html
|   └── list.html
├── .editorconfig
├── .jshintrc
├── app.js
└── package.json

探索适用于 Koa 的众多生成器,然后选择最适合您的生成器。使用所有生成器的步骤相同。您需要安装一个生成器,使用 yeoman 运行它,它会问您一些问题,然后根据您的答案为您的应用程序创建一个骨架。

Koa.js - 资源

以下是我们在开发本教程时使用的资源列表 −