Koa.js 快速指南



Koa.js - 概述

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

什么是 Koa?

Koa 提供了一个最小的接口来构建应用程序。它是一个非常小的框架(600 行代码),提供了构建应用程序所需的工具,并且非常灵活。npm 上有大量可用于 Koa 的模块,可以直接插入其中。可以将 Koa 视为 express.js 的核心,去除了所有花里胡哨的功能。

为什么选择 Koa?

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

Pug

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

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

它是 Koa 最流行的模板引擎之一。

MongoDB 和 Mongoose

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

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

Koa.js - 环境

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

$ node --version
$ npm --version

您应该会收到类似以下的输出:

v5.0.0
3.5.2

请确保您的 Node 版本高于 6.5.0。现在我们已经设置了 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 的新文件夹并进入其中:

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://127.0.0.1:3000')
});

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

$ nodemon app.js

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

Hello world

此应用程序如何工作?

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

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

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

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() 会启动生成器,它会一直运行到遇到 yield。然后它返回一个包含 value 和 done 的对象,其中 value 包含表达式的值。此表达式可以是任何内容。此时,它会暂停执行。再次调用此函数(next)时,生成器会从上次 yield 点恢复执行,函数状态与暂停时的状态相同,直到下一个 yield 点。这会一直持续到代码中没有更多 yield 点。

Koa 中的生成器

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

请查看以下示例以更好地理解这一点。

var koa = require('koa');
var app = koa();
 
app.use(function* (next) {
   //do something before yielding to next generator function 
   
   //in line which will be 1st event in downstream
   console.log("1");
   yield next;
 
   //do something when the execution returns upstream, 
   //this will be last event in upstream
   console.log("2");
});
app.use(function* (next) {
   // This shall be 2nd event downstream
   console.log("3");
   yield next;
 
   // This would be 2nd event upstream
   console.log("4");
});
app.use(function* () { 
   // Here it would be last function downstream
   console.log("5");
   
   // Set response body
   this.body = "Hello Generators";

   // First event of upstream (from the last to first)
   console.log("6");
});

app.listen(3000);

运行上述代码并导航到 https://127.0.0.1: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();              //Instantiate the router
_.get('/hello', getMessage);   // Define routes

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

app.use(_.routes());           //Use the routes defined using the router
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(); //Instantiate the 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'!\n";
};
app.use(_.routes()); //Use the routes defined using the router
app.listen(3000);

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

curl -X POST "https://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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);

请注意,这匹配具有五位数 ID 的请求。您可以使用更复杂的正则表达式来匹配/验证您的路由。如果您的任何路由都不匹配请求,您将收到“未找到”消息作为响应。

例如,如果我们定义与上面相同的路由,则在使用有效 URL 请求时,我们将获得:

URL Matching Correct

Koa.js - 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

Koa.js - 请求对象

Koa Request 对象是 node 原生 request 对象之上的抽象,提供了对日常 HTTP 服务器开发有用的附加功能。Koa request 对象嵌入在上下文对象 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://127.0.0.1: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://127.0.0.1:3000/hello/?name=Ayush&age=20&country=India,那么我们将获得以下对象。

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

request.accepts(type)

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

你可以在文档中阅读更多关于 request 对象的信息 Request

Koa.js - 响应对象

Koa Response 对象是 node 原生 response 对象之上的抽象,提供了对日常 HTTP 服务器开发有用的附加功能。Koa response 对象嵌入在上下文对象 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://127.0.0.1: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

获取或设置响应体。通常,我们使用上下文对象访问它。这只是另一种访问方式。主体可以是以下类型:字符串、缓冲区、流、对象或空。

response.type

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

response.get(field)

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

response.set(field, value)

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

response.remove(field)

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

你可以在文档中阅读更多关于 response 对象的信息 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://127.0.0.1:3000/hello 时,我们得到 -

Redirect Hello

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

Redirect Error

Koa.js - 错误处理

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

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

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

//Error handling middleware
app.use(function *(next) {
   try {
      yield next;
   } catch (err) {
      this.status = err.status || 500;
      this.body = err.message;
      this.app.emit('error', err, this);
   }
});

//Create an error in the next middleware
//Set the error message and status code and throw it using context object

app.use(function *(next) {
   //This will set status and message
   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 - 级联

中间件函数是可以访问应用程序请求-响应周期中的上下文对象和下一个中间件函数的函数。这些函数用于修改请求和响应对象以执行任务,例如解析请求主体、添加响应头等。Koa 更进一步,先“向下游”传递,然后将控制流回“上游”。这种效果称为级联

以下是在实际操作中的中间件函数的简单示例。

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

//Simple request time logger
app.use(function* (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.
   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();

//Simple request time logger
_.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. ");
   
   //Now we yield to the next middleware
   yield next;
   
   //We'll come back here at the end after all other middlewares have ended
   console.log("I'll be logged last. ");
};

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 的第三方中间件列表可在此处获得 here.以下是一些最常用的中间件 -

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

我们将在后续章节中讨论多个中间件。

Koa.js - 模板引擎

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

要在 Koa 中使用 Pug,我们需要使用以下命令安装它。

$ 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(); //Instantiate the router

app.use(_.routes()); //Use the routes defined using the router
app.listen(3000);

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

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

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

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

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-->

属性

要定义属性,我们使用用括号括起来的逗号分隔的属性列表。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 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 // equals to pug.use(app) and app.use(pug.middleware)
});

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

_.get('//dynamic_view', dynamicMessage); // Define routes

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

app.use(_.routes()); //Use the routes defined using the router
app.listen(3000);

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

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

在浏览器中打开 **localhost:3000/dynamic**,以下应该是输出结果。−

Templating Variables

我们也可以在文本中使用这些传递的变量。要在标签文本之间插入传递的变量,我们使用 #{variableName} 语法。例如,在上面的示例中,如果要插入 Greetings from TutorialsPoint,则必须使用以下代码。

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

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

条件语句

我们也可以使用条件语句和循环结构。考虑这个实际示例,如果用户已登录,我们希望显示“Hi, User”,否则,我们希望向他显示“Login/Sign Up”链接。为了实现这一点,我们可以定义一个简单的模板,例如 −

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。但是,如果我们不传递任何对象或传递一个没有 user 键的对象,那么我们将得到一个注册链接。

包含和组件

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

创建三个具有以下代码的视图 −

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 //Equivalent to app.use(pug)
});

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

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

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

app.use(_.routes()); //Use the routes defined using the router
app.listen(3000);

转到 **localhost:3000/components**,您应该会看到以下输出。

Templating Components

**include** 也可以用于包含纯文本、CSS 和 JavaScript。

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

Koa.js - 表单数据

表单是 Web 不可或缺的一部分。我们访问的几乎每个网站都提供表单,这些表单可以为我们提交或获取一些信息。要开始使用表单,我们首先需要安装 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();

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

//Set up body parsing middleware
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; //This is where the parsed request is stored
}

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();

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

//Set up body parsing middleware
app.use(bodyParser({
   formidable:{uploadDir: './uploads'},    //This is where the files would come
   multipart: true,
   urlencoded: true
}));

var _ = router(); //Instantiate the 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!"; //This is where the parsed request is stored
}

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

当您运行此操作时,您将获得以下表单。

File Upload Form

提交后,您的控制台将产生以下输出。

File Console Screen

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

Koa.js - 静态文件

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

我们需要一个中间件来服务此目的。继续安装 **koa-serve** −

$ npm install --save koa-static

现在我们需要 **使用** 此中间件。在此之前,创建一个名为 public 的目录。我们将在此处存储所有静态文件。这使我们能够保持服务器代码的安全,因为 public 文件夹以上的内容将无法被客户端访问。创建 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 的一部分。根路由现在设置为您的 public 目录,因此您加载的所有静态文件都将考虑 public 作为根目录。要测试这是否正常工作,请运行您的应用程序并访问 **https://127.0.0.1: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 - Cookie

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

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

要在 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(){
   //Expires after 360000 ms from the time it is set.
	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(){
   //Expires after 360000 ms from the time it is set.
   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));  // Include the session middleware

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、会话或任何其他内容。要使用此功能,客户端必须在其发出的每个请求中都发送 Authorization 标头。用户名和密码未加密,但连接在一个字符串中,如下所示。

username:password

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

Authorization: Basic QXl1c2g6SW5kaWE=

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

$ npm install --save koa-basic-auth

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

//This is what the authentication would be checked against
var credentials = { name: 'Ayush', pass: 'India' }

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

var app = koa();

//Error handling middleware
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;
      }
   }
});

// Set up authentication here as first middleware. 
// This returns an error if user is not authenticated.
_.get('/protected', auth(credentials), function *(){
   this.body = 'You have access to the protected area.';
   yield next;
});

// No authentication middleware present here.
_.get('/unprotected', function*(next){
   this.body = "Anyone can access this area";
   yield next;
});

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

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

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

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

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

$ curl https://127.0.0.1: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://127.0.0.1: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 //Equivalent to 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()); //Use the routes defined using the router
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  //Add these files to caches for a year
}))

app.listen(3000);

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

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

Before Cache

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

After Cache

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

Koa.js - 数据库

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

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

设置 Mongoose

现在我们已经安装了 Mongo,让我们以我们安装其他 Node 包的方式安装 mongoose。

$ npm install --save mongoose

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

use my_db

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

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

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

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1/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://127.0.0.1/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://127.0.0.1/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' 上定义一个 post 路由处理程序,它将处理此请求。

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

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);

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

function *createPerson(next){
   var self = this;
   var personInfo = self.request.body; //Get the parsed information
   
   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 函数一样,它们的 arguments 是 error 和 response。

这三个函数是:

Model.find(conditions, callback)

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

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

这将获取 name 字段为“Ayush”且 age 为 20 的所有文档。

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

我们还可以提供所需的投影,即我们所需的字段。例如,如果我们只需要 nationality 为“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 koa = require('koa');
var _ = require('koa-router')();
var app = koa();

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);

_.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)

此函数以 condition 和 updates 对象作为输入,并将更改应用于集合中与条件匹配的所有文档。例如,以下代码将更新所有 Person 文档,使其 nationality 为“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 koa = require('koa');
var _ = require('koa-router')();
var app = koa();
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);

_.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://127.0.0.1:3000/people/507f1f77bcf86cd799439011

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

删除文档

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

Model.remove(condition, [callback])

此函数以 condition 对象作为输入,并删除与条件匹配的所有文档。例如,如果我们需要删除所有年龄为 20 的人,

Person.remove({age:20});

Model.findOneAndRemove(condition, [callback])

此函数根据 condition 对象删除一个最相关的文档。例如,

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://127.0.0.1/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://127.0.0.1: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。
DELETE /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();

//Set up body parsing middleware
app.use(bodyParser({
   formidable:{uploadDir: './uploads'},
   multipart: true,
   urlencoded: true
}));

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

//Use the Router on the sub route /movies
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'
});  //Prefixed all routes with /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

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;//Set status to 404 as movie was not found
      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}

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

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

POST 路由

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

router.post('/', addNewMovie);

function *addNewMovie(next){
   //Check if all fields are provided and are valid:
   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://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', updateMovieWithId);

function *updateMovieWithId(next){
   //Check if all fields are provided and are valid:
   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 {
      //Gets us the index of movie with given 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 {
         //Update existing movie
         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://127.0.0.1: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'
});  //Prefixed all routes with /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); //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."};
   }
}

function *updateMovieWithId(next) {
   //Check if all fields are provided and are valid:
   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 {
      //Gets us the index of movie with given 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 {
         //Update existing movie
            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){
   //Check if all fields are provided and are valid:
   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;//Set status to 404 as movie was not found
      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 应用程序的框架。我们手动创建了 public 目录,添加了中间件,创建了单独的路由文件等。脚手架工具为我们设置了所有这些内容,以便我们可以直接开始构建应用程序。

我们将使用的脚手架工具称为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 - 资源

以下是我们在开发本教程时使用的一些资源 -

广告