Express/Node introduction - 學習該如何開發 Web

Express/Node introduction
=========================

* Overview: Express Nodejs
* 次頁

在這篇文章中回答了「什麼是 Node？」和「什麼是 Express」，同時概述是什麼讓 Express 框架如此特別。本文將概述主要特性、展示一些 Express 應用的主要建構模塊(雖然此時你還沒有能測試它的開發環境)

|  |  |
| --- | --- |
| 前置需求: | 基本的電腦知識。 對伺服器端網站程式設計的基本了解，特別是網站中客戶端 - 伺服器交互的機制 (en-US)。 |
| 目標: | 提升對Express的了解、如何與Node搭配使用、提供的功能和Express應用的主要建構模塊。 |

什麼是 Express 和 Node?
-------------------

Node (或者說*Node.js*) 是一個開源、跨平台和允許開發者使用Javascript創造伺服器端工具和應用的執行環境。運行的目的是為了能在瀏覽器外使用，例如：直接執行在電腦或伺服器上。所以該環境捨棄了瀏覽器限定的 JavaScript APIs 並增加更多傳統 OS APIs 的支援，例如：HTTP 和檔案系統的程式庫。

從網站伺服器開發的觀點來看 Node 有幾項優點：

* 高效能!Node 旨在提升生產率和網頁應用的可擴充性。而且它非常適合網站開發常見的問題，例如：即時網站應用
* 使用舊版本的 JavaScript 進行程式編寫，這表示不用多花力氣在轉換瀏覽器和伺服器上的程式碼
* 與其他傳統的 Web 伺服器語言（例如 Python，PHP 等）相比，JavaScript 是一種相對新的程式語言，它受益於語言設計的改進。許多其他新的和流行的語言都可以編譯/轉換成 JavaScript，因此你還可以使用 CoffeeScript， ClojureScript，Scala，LiveScript 等
* Node Package Manager(NPM) 提供數十萬個第三方套件，是最佳的依賴解決方案也可以用來自動化大部分構建工具鏈。
* 它是可移植的，能夠在 Windows, OS x, Linux, Solaris, FreeBSD, OpenBSD, WebOS 和 NonStop OS 上執行。許多 web 主機提供方也支援使用 Node，通常會提供特定的基礎設施和文件
* 擁有非常活耀的第三方生態系統和開發者社群，許多人樂意提供幫助

你可以只用 Node 的 HTTP 模組創造一個簡單的 web 伺服器來回應任何請求，如下所示。此教學不會告訴建議的檔案名稱或如何執行該檔案 ;-)

這將創造一個伺服器並會監聽`http://127.0.0.1:8000/`上任何種類的 HTTP 請求，當接收到任何請求時回傳一個「Hello World」的純文字回應。

```js
// 載入 HTTP 模組
var http = require("http");

// 創建 HTTP 伺服器並監聽8000 port
http
  .createServer(function (request, response) {
    // Set the response HTTP header with HTTP status and Content type
    response.writeHead(200, { "Content-Type": "text/plain" });

    // Send the response body "Hello World"
    response.end("Hello World\n");
  })
  .listen(8000);

// Print URL for accessing server
console.log("Server running at http://127.0.0.1:8000/");
```

Node 並不原生支持其他常見的 web 開發任務，如果你想為不同的 HTTP 方法(例如：`GET`, `POST`, `DELETE`等)增加特定的處理、替不同的 URL 路徑提供靜態檔案、使用樣板或動態性的產生 response，你需要自己完成相關的程式**或者是**避免重新造輪子 - 使用 web 框架！

Express 是最受歡迎的 Node web 框架，還是其他許多流行的Node web 框架的底層庫，它提供：

* 替不同 HTTP Method、不同 URL 路徑的 requests 編寫不同的處理方法
* 透過整合「畫面」的渲染引擎來達到插入資料到樣板中產生 response
* 設定常見的 web 應用設定，例如：連線用的 port 和產生 response 的樣板位置
* 在 request 的處理流程中增加額外的「中間層」進行處理

雖然 Express 本身非常簡單，但開發者們已經創造相容的中間層套件來解決大部份 web 開發的問題，這些套件能處理 cookies, sessions，登入，URL 參數，POST 資料，安全標頭等等，你能在Express Middleware中找到這些套件的列表（以及其他流行的第三方套件）

**備註：** 這種靈活性是一把雙刃劍。有一些中間層套件能解決大部份的問題或需求，但使用正確的套件有時會是一個問題。也沒有「正確的方法」來創建應用，你在網路上找到的範例也並非都是最佳解或是只有開發上所需要做的一小部份。

歷史
--

2009 年 Node 在 Linux 平台上初次發佈. 2010 年 NPM 套件管利器發佈, 2012 年增加 Windows 的原生支援. 現在的 LTS 版本為 Node v8.11.2，最新版本為 Node v10.1.0。這只是它深厚歷史的一小片斷，欲知更多詳情請洽 Wikipedia。

2010 年 11 月 Express 初次發佈，現在的 API 版本為 4.16。你可以查閱更新紀錄來了解此版本做了甚麼更改或是從GitHub中了解詳細的歷史紀錄。

Node/Express 有多流行?
------------------

對於 web 框架而言流行度很重要，這代表他會不會被繼續更新、文件、附加套件和技術支援方面有多少資源

現在沒有一個明確的指標來評斷伺服器端框架的流行度，雖然有 Hot Frameworks透過計算 GitHub 的專案數量和 StackOverflow 的問題來衡量流行度。更好的問題是，Node 和 Express 是否「夠流行」以避免成為不流行的平台。有沒有持續進步？需要時是否能得到幫助？能不能找到 Express 相關的工作？

從眾多使用 Express 的公司、貢獻程式碼的人數和那些提供免費/收費支援的人員來看，是的！Express 是一個流行的框架。

Is Express opinionated?
-----------------------

Web 框架通常自稱為 "opinionated" 或 "unopinionated".

Opinionated 指的是那些有「正確」方法解決特定問題的框架。在特定的需求上他們通常能快速開發，因為正確的方法通常易懂且有良好的文件，然而在面對其他問題時則會失去靈活性。這類型的框架通常傾向於提供較少的選擇和套件來解決問題。

反過來說 Unopinionated 框架，對於如何組合套件來解決問題尚有較少的限制，開發者可以更輕易的使用適當的套件來解決特定問題，儘管代價是你需要自己找到適合的套件。

Express 是 Unopinionated 框架，你可以在 request 處理流程中使用任何相容套件，使用單一或複數個檔案來建構應用，有時候甚至會覺得擁有太多選擇了。

Express 的程式碼長怎樣?
----------------

傳統的資料驅動網站中，web 應用程式會等待來自瀏覽器(或其他客戶端)的 HTTP Request，接收到 Request 後根據 URL 和可能夾帶的`POST`/`GET`資料來決定需要回應什麼動作，根據需要可能對資料庫進行讀寫或執行滿足 Request 所需的其他任務。web 應用程式會回應 Response 給瀏覽器，通常是藉由插入檢所到的資料到 HTML 模板中動態產生 HTML 頁面讓瀏覽器顯示。

Express provides methods to specify what function is called for a particular HTTP verb (`GET`, `POST`, `SET`, etc.) and URL pattern ("Route"), and methods to specify what template ("view") engine is used, where template files are located, and what template to use to render a response. You can use Express middleware to add support for cookies, sessions, and users, getting `POST`/`GET` parameters, etc. You can use any database mechanism supported by Node (Express does not define any database-related behaviour).

The following sections explain some of the common things you'll see when working with *Express* and *Node* code.

### Helloworld Express

First lets consider the standard Express Hello World example (we discuss each part of this below, and in the following sections).

**備註：** If you have Node and Express already installed (or if you install them as shown in the next article), you can save this code in a text file called **app.js** and run it in a bash command prompt by calling:

**`./node ./app.js`**

```js
var express = require("express");
var app = express();

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

app.listen(3000, function () {
  console.log("Example app listening on port 3000!");
});
```

The first two lines `require()` (import) the express module and create an Express application. This object, which is traditionally named `app`, has methods for routing HTTP requests, configuring middleware, rendering HTML views, registering a template engine, and modifying application settings that control how the application behaves (e.g. the environment mode, whether route definitions are case sensitive, etc.)

The middle part of the code (the three lines starting with `app.get`) shows a *route definition*. The `app.get()` method specifies a callback function that will be invoked whenever there is an HTTP `GET` request with a path (`'/'`) relative to the site root. The callback function takes a request and a response object as arguments, and simply calls `send()` on the response to return the string "Hello World!"

The final block starts up the server on port '3000' and prints a log comment to the console. With the server running, you could go to `localhost:3000` in your browser to see the example response returned.

### Importing and creating modules

A module is a JavaScript library/file that you can import into other code using Node's `require()` function. *Express* itself is a module, as are the middleware and database libraries that we use in our *Express* applications.

The code below shows how we import a module by name, using the *Express* framework as an example. First we invoke the `require()` function, specifying the name of the module as a string (`'express'`), and calling the returned object to create an Express application. We can then access the properties and functions of the application object.

```js
var express = require("express");
var app = express();
```

You can also create your own modules that can be imported in the same way.

**備註：** You will *want* to create your own modules, because this allows you to organise your code into managable parts — a monolithic single-file application is hard to understand and maintain. Using modules also helps you manage your namespace, because only the variables you explicitly export are imported when you use a module.

To make objects available outside of a module you just need to assign them to the `exports` object. For example, the **square.js** module below is a file that exports `area()` and `perimeter()` methods:

```js
exports.area = function (width) {
  return width \* width;
};
exports.perimeter = function (width) {
  return 4 \* width;
};
```

We can import this module using `require()`, and then call the exported method(s) as shown:

```js
var square = require("./square"); // Here we require() the name of the file without the (optional) .js file extension
console.log("The area of a square with a width of 4 is " + square.area(4));
```

**備註：** You can also specify an absolute path to the module (or a name, as we did initially).

If you want to export a complete object in one assignment instead of building it one property at a time, assign it to `module.exports` as shown below (you can also do this to make the root of the exports object a constructor or other function):

```js
module.exports = {
  area: function (width) {
    return width \* width;
  },

  perimeter: function (width) {
    return 4 \* width;
  },
};
```

For a lot more information about modules see Modules (Node API docs).

### Using asynchronous APIs

JavaScript code frequently uses asynchronous rather than synchronous APIs for operations that may take some time to complete. A synchronous API is one in which each operation must complete before the next operation can start. For example, the following log functions are synchronous, and will print the text to the console in order (First, Second).

```js
console.log("First");
console.log("Second");
```

By contrast, an asynchronous API is one in which the API will start an operation and immediately return (before the operation is complete). Once the operation finishes, the API will use some mechanism to perform additional operations. For example, the code below will print out "Second, First" because even though `setTimeout()` method is called first, and returns immediately, the operation doesn't complete for several seconds.

```js
setTimeout(function () {
  console.log("First");
}, 3000);
console.log("Second");
```

Using non-blocking asynchronous APIs is even more important on Node than in the browser, because *Node* is a single threaded event-driven execution environment. "single threaded" means that all requests to the server are run on the same thread (rather than being spawned off into separate processes). This model is extremely efficient in terms of speed and server resources, but it does mean that if any of your functions call synchronous methods that take a long time to complete, they will block not just the current request, but every other request being handled by your web application.

There are a number of ways for an asynchronous API to notify your application that it has completed. The most common way is to register a callback function when you invoke the asynchronous API, that will be called back when the operation completes. This is the approach used above.

**備註：** Using callbacks can be quite "messy" if you have a sequence of dependent asynchronous operations that must be performed in order, because this results in multiple levels of nested callbacks. This problem is commonly known as "callback hell". This problem can be reduced by good coding practices (see http://callbackhell.com/), using a module like async, or even moving to ES6 features like Promises.

**備註：** A common convention for Node and Express is to use error-first callbacks. In this convention the first value in your *callback functions* is an error value, while subsequent arguments contain success data. There is a good explanation of why this approach is useful in this blog: The Node.js Way - Understanding Error-First Callbacks (fredkschott.com).

### Creating route handlers

In our *Hello World* Express example (see above), we defined a (callback) route handler function for HTTP `GET` requests to the site root (`'/'`).

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

The callback function takes a request and a response object as arguments. In this case the method simply calls `send()` on the response to return the string "Hello World!" There are a number of other response methods for ending the request/response cycle, for example you could call `res.json()` to send a JSON response or `res.sendFile()` to send a file.

**備註：** You can use any argument names you like in the callback functions; when the callback is invoked the first argument will always be the request and the second will always be the response. It makes sense to name them such that you can identify the object you're working with in the body of the callback.

The *Express application* object also provides methods to define route handlers for all the other HTTP verbs, which are mostly used in exactly the same way: `post()`, `put()`, `delete()`, `options()`, `trace()`, `copy()`, `lock()`, `mkcol()`, `move()`, `purge()`, `propfind()`, `proppatch()`, `unlock()`, `report()`, `mkactivity()`, `checkout()`, `merge()`, `m-search()`, `notify()`, `subscribe()`, `unsubscribe()`, `patch()`, `search()`, and `connect()`.

There is a special routing method, `app.all()`, which will be called in response to any HTTP method. This is used for loading middleware functions at a particular path for all request methods. The following example (from the Express documentation) shows a handler that will be executed for requests to `/secret` irrespective of the HTTP verb used (provided it is supported by the http module).

```js
app.all("/secret", function (req, res, next) {
  console.log("Accessing the secret section ...");
  next(); // pass control to the next handler
});
```

Routes allow you to match particular patterns of characters in a URL, and extract some values from the URL and pass them as parameters to the route handler (as attributes of the request object passed as a parameter).

Often it is useful to group route handlers for a particular part of a site together and access them using a common route-prefix (e.g. a site with a Wiki might have all wiki-related routes in one file and have them accessed with a route prefix of */wiki/*). In *Express* this is achieved by using the `express.Router` object. For example, we can create our wiki route in a module named **wiki.js**, and then export the `Router` object, as shown below:

```js
// wiki.js - Wiki route module

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

// Home page route
router.get("/", function (req, res) {
  res.send("Wiki home page");
});

// About page route
router.get("/about", function (req, res) {
  res.send("About this wiki");
});

module.exports = router;
```

**備註：** Adding routes to the `Router` object is just like adding routes to the `app` object (as shown previously).

To use the router in our main app file we would then `require()` the route module (**wiki.js**), then call `use()` on the *Express* application to add the Router to the middleware handling path. The two routes will then be accessible from `/wiki/` and `/wiki/about/`.

```js
var wiki = require("./wiki.js");
// ...
app.use("/wiki", wiki);
```

We'll show you a lot more about working with routes, and in particular about using the `Router`, later on in the linked section Routes and controllers.

### Using middleware

Middleware is used extensively in Express apps, for tasks from serving static files to error handling, to compressing HTTP responses. Whereas route functions end the HTTP request-response cycle by returning some response to the HTTP client, middleware functions *typically* perform some operation on the request or response and then call the next function in the "stack", which might be more middleware or a route handler. The order in which middleware is called is up to the app developer.

**備註：** The middleware can perform any operation, execute any code, make changes to the request and response object, and it can *also end the request-response cycle*. If it does not end the cycle then it must call `next()` to pass control to the next middleware function (or the request will be left hanging).

Most apps will use *third-party* middleware in order to simplify common web development tasks like working with cookies, sessions, user authentication, accessing request `POST` and JSON data, logging, etc. You can find a list of middleware packages maintained by the Express team (which also includes other popular 3rd party packages). Other Express packages are available on the NPM package manager.

To use third party middleware you first need to install it into your app using NPM. For example, to install the morgan HTTP request logger middleware, you'd do this:

```bash
npm install morgan
```

You could then call `use()` on the *Express application object* to add the middleware to the stack:

```js
var express = require('express');
var logger = require('morgan');
var app = express();
app.use(logger('dev'));
...
```

**備註：** Middleware and routing functions are called in the order that they are declared. For some middleware the order is important (for example if session middleware depends on cookie middleware, then the cookie handler must be added first). It is almost always the case that middleware is called before setting routes, or your route handlers will not have access to functionality added by your middleware.

You can write your own middleware functions, and you are likely to have to do so (if only to create error handling code). The **only** difference between a middleware function and a route handler callback is that middleware functions have a third argument `next`, which middleware functions are expected to call if they are not that which completes the request cycle (when the middleware function is called, this contains the *next* function that must be called).

You can add a middleware function to the processing chain with either `app.use()` or `app.add()`, depending on whether you want to apply the middleware to all responses or to responses with a particular HTTP verb (`GET`, `POST`, etc). You specify routes the same in both cases, though the route is optional when calling **app.use()**.

The example below shows how you can add the middleware function using both methods, and with/without a route.

```js
var express = require("express");
var app = express();

// An example middleware function
var a\_middleware\_function = function (req, res, next) {
  // ... perform some operations
  next(); // Call next() so Express will call the next middleware function in the chain.
};

// Function added with use() for all routes and verbs
app.use(a_middleware_function);

// Function added with use() for a specific route
app.use("/someroute", a_middleware_function);

// A middleware function added for a specific HTTP verb and route
app.get("/", a_middleware_function);

app.listen(3000);
```

**備註：** Above we declare the middleware function separately and then set it as the callback. In our previous route handler function we declared the callback function when it was used. In JavaScript, either approach is valid.

The Express documentation has a lot more excellent documentation about using and writing Express middleware.

### Serving static files

You can use the express.static middleware to serve static files, including your images, CSS and JavaScript (`static()` is the only middleware function that is actually **part** of *Express*). For example, you would use the line below to serve images, CSS files, and JavaScript files from a directory named '**public'** at the same level as where you call node:

```js
app.use(express.static("public"));
```

Any files in the public directory are served by adding their filename (*relative* to the base "public" directory) to the base URL. So for example:
```

http://localhost:3000/images/dog.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/about.html
```

You can call `static()` multiple times to serve multiple directories. If a file cannot be found by one middleware function then it will simply be passed on to the subsequent middleware (the order that middleware is called is based on your declaration order).

```js
app.use(express.static("public"));
app.use(express.static("media"));
```

You can also create a virtual prefix for your static URLs, rather than having the files added to the base URL. For example, here we specify a mount path so that the files are loaded with the prefix "/media":

```js
app.use("/media", express.static("public"));
```

Now, you can load the files that are in the `public` directory from the `/media` path prefix.
```

http://localhost:3000/media/images/dog.jpg
http://localhost:3000/media/video/cat.mp4
http://localhost:3000/media/cry.mp3
```

For more information, see Serving static files in Express.

### Handling errors

Errors are handled by one or more special middleware functions that have four arguments, instead of the usual three: `(err, req, res, next)`. For example:

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

These can return any content required, but must be called after all other `app.use()` and routes calls so that they are the last middleware in the request handling process!

Express comes with a built-in error handler, which takes care of any remaining errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack. If you pass an error to `next()` and you do not handle it in an error handler, it will be handled by the built-in error handler; the error will be written to the client with the stack trace.

**備註：** The stack trace is not included in the production environment. To run it in production mode you need to set the the environment variable `NODE_ENV` to '`production'`.

**備註：** HTTP404 and other "error" status codes are not treated as errors. If you want to handle these, you can add a middleware function to do so. For more information see the FAQ.

For more information see Error handling (Express docs).

### Using databases

*Express* apps can use any database mechanism supported by *Node* (*Express* itself doesn't define any specific additional behaviour/requirements for database management). There are many options, including PostgreSQL, MySQL, Redis, SQLite, MongoDB, etc.

In order to use these you have to first install the database driver using NPM. For example, to install the driver for the popular NoSQL MongoDB you would use the command:

```bash
npm install mongodb
```

The database itself can be installed locally or on a cloud server. In your Express code you require the driver, connect to the database, and then perform create, read, update, and delete (CRUD) operations. The example below (from the Express documentation) shows how you can find "mammal" records using MongoDB.

```js
//this works with older versions of mongodb version ~ 2.2.33
var MongoClient = require('mongodb').MongoClient;

MongoClient.connect('mongodb://localhost:27017/animals', function(err, db) {
  if (err) throw err;

  db.collection('mammals').find().toArray(function (err, result) {
    if (err) throw err;

    console.log(result);
  });
});

//for mongodb version 3.0 and up
let MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/animals', function(err, client){
   if(err) throw err;

   let db = client.db('animals');
   db.collection('mammals').find().toArray(function(err, result){
     if(err) throw err;
     console.log(result);
     client.close();
   });
}
```

Another popular approach is to access your database indirectly, via an Object Relational Mapper ("ORM"). In this approach you define your data as "objects" or "models" and the ORM maps these through to the underlying database format. This approach has the benefit that as a developer you can continue to think in terms of JavaScript objects rather than database semantics, and that there is an obvious place to perform validation and checking of incoming data. We'll talk more about databases in a later article.

For more information see Database integration (Express docs).

### Rendering data (views)

Template engines (referred to as "view engines" by *Express*) allow you to specify the *structure* of an output document in a template, using placeholders for data that will be filled in when a page is generated. Templates are often used to create HTML, but can also create other types of documents. Express has support for a number of template engines, and there is a useful comparison of the more popular engines here: Comparing JavaScript Templating Engines: Jade, Mustache, Dust and More.

In your application settings code you set the template engine to use and the location where Express should look for templates using the 'views' and 'view engines' settings, as shown below (you will also have to install the package containing your template library too!)

```js
var express = require("express");
var app = express();

// Set directory to contain the templates ('views')
app.set("views", path.join(__dirname, "views"));

// Set view engine to use, in this case 'some\_template\_engine\_name'
app.set("view engine", "some\_template\_engine\_name");
```

The appearance of the template will depend on what engine you use. Assuming that you have a template file named "index.<template\_extension>" that contains placeholders for data variables named 'title' and "message", you would call `Response.render()` in a route handler function to create and send the HTML response:

```js
app.get("/", function (req, res) {
  res.render("index", { title: "About dogs", message: "Dogs rock!" });
});
```

For more information see Using template engines with Express (Express docs).

### File structure

Express makes no assumptions in terms of structure or what components you use. Routes, views, static files, and other application-specific logic can live in any number of files with any directory structure. While it is perfectly possible to have the whole *Express* application in one file, typically it makes sense to split your application into files based on function (e.g. account management, blogs, discussion boards) and architectural problem domain (e.g. model, view or controller if you happen to be using an MVC architecture).

In a later topic we'll use the *Express Application Generator*, which creates a modular app skeleton that we can easily extend for creating web applications.

總結
--

恭喜，你已完成 Express / Node 之旅的第一步！你現在應該了解 Express 和 Node 的主要優點，以及 Express 應用程序的主要部分（路由，中間件，錯誤處理和模板代碼）。你還應該明白，Express 是一個不固執己見的框架，你將這些組件組合在一起的方式以及你使用的函式庫，在很大程度上取決於你！

當然，Express 是一個非常輕量級的 Web 應用程序框架，它的許多好處和潛力來自第三方函式庫和功能。我們將在以下文章中更詳細地介紹這些內容。在下一篇文章中，我們將介紹如何設置 Node 開發環境，以便你可以開始查看一些 Express 代碼。

See also
--------

* Venkat.R - Manage Multiple Node versions
* Modules (Node API docs)
* Express (home page)
* Basic routing (Express docs)
* Routing guide (Express docs)
* Using template engines with Express (Express docs)
* Using middleware (Express docs)
* Writing middleware for use in Express apps (Express docs)
* Database integration (Express docs)
* Serving static files in Express (Express docs)
* Error handling (Express docs)
* Overview: Express Nodejs
* 次頁


Express 教學 2: 創建一個骨架網站 - 學習該如何開發 Web

Express 教學 2: 創建一個骨架網站
======================

* 前頁
* Overview: Express Nodejs
* 次頁

在 Express 教程的第二篇文章，演示如何創建一個 "骨架" 網站項目，你可以接著在裡面加入網站特定的路由、模板/視圖、和數据庫調用。

|  |  |
| --- | --- |
| 前置條件: | 架設一個Node 開發環境。回顧Express 教程。 |
| 目標: | 能夠使用*Express 應用產生器，*創建自己新的網頁項目。 |

概覽
--

本文演示如何使用 Express 應用產生器 工具，創建一個 "骨架" 網站，然後你可以使用特定於站點的路由，視圖/模板和數據庫調用來填充它們。在這個教程，我們將使用該工具，為我們的本地圖書館網站創建框架，我們稍後將添加該網站所需的所有其他代碼。該過程非常簡單，只需要在命令行上，用新項目名稱調用生成器，還可以指定站點的模板引擎和 CSS 生成器。

以下部分向你展示如何調用應用程序生成器，並提供關於視圖或 CSS 的不同選項的一些解釋。我們還將解釋骨架網站的結構。最後，我們將展示如何運行網站，來驗證它是否有效。

**備註：** Express Application Generator 並非 Express 應用程序的唯一生成器，生成的項目不是構建文件和目錄的唯一可行方式。然而，生成的網站具有易於擴展和理解的模塊化結構。有關最小 Express 應用程序的信息，請參閱 Hello world 示例（Express docs）。

使用應用產生器
-------

你應該已經安裝了生成器，作為設置 Node 開發環境的一部分。作為快速提醒，你可以使用 NPM 軟件包管理器，在整個站點安裝生成器工具，如下所示：

```bash
npm install express-generator -g
```

生成器有許多選項，你可以使用`--help`（或`-h`）命令，在命令行上查看它們：

```bash
> express --help

  Usage: express [options] [dir]

  Options:

    -h, --help           output usage information
        --version        output the version number
    -e, --ejs            add ejs engine support
        --pug            add pug engine support
        --hbs            add handlebars engine support
    -H, --hogan          add hogan.js engine support
    -v, --view <engine>  add view <engine> support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
    -c, --css <engine>   add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git            add .gitignore
    -f, --force          force on non-empty directory
```

你可以使用 Jade 視圖引擎和純 CSS 來指定 `express`，以在當前目錄中創建項目（如果指定目錄名，則項目將創建在具有該名稱的子文件夾中）。

```bash
express
```

你還可以使用`--view`選擇視圖（模板）引擎，並且/或者使用`--css`選擇 CSS 生成引擎。

**備註：** 選擇模板引擎的其他選項（例如 `--hogan`, `--ejs`, `--hbs`等）已被棄用。請用 `--view` (或 `-v`)!

### 我應該用哪個視圖引擎?

Express Application Generator 允許你配置許多流行的視圖/模板引擎，包括 EJS, Hbs, Pug (Jade), Twig, 和 Vash，但如果你沒有指定視圖選項，它會默認選擇 Jade。 Express 本身也可以支持大量其他模板語言，是「開箱即可使用」的。

**備註：** 如果要使用生成器不支持的模板引擎，請參閱在 Express 中使用模板引擎（Express 文檔），並參閱目標視圖引擎的文檔。

 一般來說，你應該選擇一種「可以提供你所需的所有功能」的模板引擎，
 並使你能夠儘早提高生產力 - 換句話說，就像你選擇其他組件一樣！比較模板引擎時需要考慮的一些事項如下：

* 具備生產力之前所需要花費的時間 — 如果你的團隊已經有模板語言的經驗，那麼使用該語言可能會更快地提高生產力。如果不是，那麼你應該考慮候選模板引擎的相對學習曲線（即以投入時間與得到的生產力為 XY 座標所繪製的曲線）。
* 人氣和活躍 — 回顧引擎的普及程度以及它是否擁有活躍的社區。當你在網站的生命週期中遇到問題時，能夠獲得對引擎的支持非常重要。
* 風格 — 某些模板引擎使用特定的標記，來指示在 「普通」 HTML 內插入的內容，而其他模板引擎使用不同的語法（例如，使用縮進和區塊名稱）構造 HTML。
* 性能/渲染時間。
* 特點 — 你應該考慮你看中的引擎是否具有以下功能：
    + 佈局繼承：允許你定義基本模板，然後 「繼承」 它的一部分，使特定頁面可以有不同的呈現。比起通過包含大量必需組件，或每次從頭開始構建模板，這通常是更好的方式。
    + "Include" 支持：允許你通過包含其他模板，來構建模板。
    + 簡潔的變量和循環控制語法。
    + 能夠在模板級別過濾變量值（例如，將變量設置為大寫，或格式化日期值）。
    + 能夠生成 HTML 以外的輸出格式（例如 JSON 或 XML）。
    + 支持異步操作和流媒體。
    + 可以在客戶端和服務器上使用。如果可以在客戶端上使用模板引擎，則允許提供數據，並有可能在客戶端完成所有渲染，或大部分渲染。

**備註：** 互聯網上有許多資源，可幫助你比較不同的視圖/模板引擎選擇！

對於這個項目，我們將使用 Pug 模板引擎（這是最近更名的 Jade 引擎），因為這是最流行的 Express / JavaScript 模板語言之一，並且應用發生器支持開箱即用。

### 我應該用哪個 CSS 樣式引擎?

Express 應用生成器允許你創建一個項目，並配置最常見的 CSS 樣式表引擎：LESS, SASS, Compass, Stylus。

**備註：** CSS 有一些限制，使某些任務變得困難。 CSS 樣式表引擎允許你使用更強大的語法來定義你的 CSS，然後將定義編譯為純粹的舊式 CSS，以供瀏覽器使用。

與模板引擎一樣，你應該使用樣式表引擎，這樣可以讓你的團隊獲得最高生產力。對於這個項目，我們將使用普通的 CSS（默認值），因為我們的 CSS 要求不夠複雜，沒有必要使用其他任何東西。

### 我應該用哪個數據庫?

 生成的代碼不使用/包含任何數據庫。 Express 應用程序可以使用 Node 支持的任何數據庫機制（Express 本身並未針對數據庫管理，定義任何特定的附加行為/要求）。
 我們將在後面的文章中，討論如何與數據庫集成。

創建項目
----

對於我們要構建的示例 Local Library 應用程序，我們將使用 Pug 模板庫，創建一個名為 express-locallibrary-tutorial 的項目，並且不使用 CSS 樣式表引擎。

首先到要創建項目的位置，然後在命令提示符下，運行 Express 應用生成器，如下所示：

```bash
express express-locallibrary-tutorial --view=pug
```

成器將創建（並列出）項目的文件。

```bash
   create : express-locallibrary-tutorial
   create : express-locallibrary-tutorial/package.json
   create : express-locallibrary-tutorial/app.js
   create : express-locallibrary-tutorial/public/images
   create : express-locallibrary-tutorial/public
   create : express-locallibrary-tutorial/public/stylesheets
   create : express-locallibrary-tutorial/public/stylesheets/style.css
   create : express-locallibrary-tutorial/public/javascripts
   create : express-locallibrary-tutorial/routes
   create : express-locallibrary-tutorial/routes/index.js
   create : express-locallibrary-tutorial/routes/users.js
   create : express-locallibrary-tutorial/views
   create : express-locallibrary-tutorial/views/index.pug
   create : express-locallibrary-tutorial/views/layout.pug
   create : express-locallibrary-tutorial/views/error.pug
   create : express-locallibrary-tutorial/bin
   create : express-locallibrary-tutorial/bin/www

   install dependencies:
     > cd express-locallibrary-tutorial && npm install

   run the app:
     > SET DEBUG=express-locallibrary-tutorial:* & npm start
```

在輸出結束時，生成器提供關於「如何安裝依賴關係」的指示信息（如 **package.json** 文件中所列），以及如何運行應用程序（上述說明適用於 Windows；在 Linux / macOS 上，它們會略有不同）。

運行骨架網站
------

在這一時間點上，我們有一個完整的骨架項目。該網站實際上並沒有做太多工作，但運行它，能夠展示它是如何工作的。

1. 首先安裝依賴項（`install`安裝命令，將獲取項目的 **package.json** 文件中列出的所有依賴項包）。

```bash
cd express-locallibrary-tutorial
npm install
```

2. 然後運行該應用程序。
    * 在 Windows 上，使用此命令:
     bash
    ```bash
    SET DEBUG=express-locallibrary-tutorial:* & npm start

    ```
    * 在 macOS 或 Linux，使用此命令:
     bash
    ```bash
    DEBUG=express-locallibrary-tutorial:* npm start

    ```
3. 然後在瀏覽器中加載 `http://localhost:3000/`，以訪問該應用程序。

你應該會看到一個瀏覽器頁面，就像這樣:

![Browser for default Express app generator website](/zh-TW/docs/Learn/Server-side/Express_Nodejs/skeleton_website/expressgeneratorskeletonwebsite.png)

你有一個能工作的 Express 應用了，讓它在 `http://localhost:3000/` 服務。

**備註：** 你也可以使用 `npm start` 命令啟動應用程序。如下圖所示，指定 DEBUG 變量可啟用控制台日誌記錄/調試。例如，當你訪問上面的頁面時，你會看到像這樣的調試輸出：

```bash
>SET DEBUG=express-locallibrary-tutorial:* & npm start

> express-locallibrary-tutorial@0.0.0 start D:\express-locallibrary-tutorial
> node ./bin/www

  express-locallibrary-tutorial:server Listening on port 3000 +0ms
GET / 200 288.474 ms - 170
GET /stylesheets/style.css 200 5.799 ms - 111
GET /favicon.ico 404 34.134 ms - 1335
```

讓伺服器在檔案更改時重新啟動
--------------

在你重新啟動服務器之前，你對 Express 網站所做的任何更改，目前都不可見。每次進行更改時，必須停止並重新啟動服務器，很快變得非常煩人，因此值得花時間使服務器在需要時，自動重新啟動。

這種工具中，最簡單的之一就是 nodemon。這通常是全局安裝的（因為它是一個「工具」），但在這裡，我們將在本地安裝和使用它，作為開發人員依賴項，以便任何使用該項目的開發人員，在安裝應用程序時自動獲取它。在骨架項目的根目錄中，使用以下命令：

```bash
npm install --save-dev nodemon
```

如果你打開項目的 **package.json** 文件，你現在將看到一個具有此依賴關係的新區段：

```json
  "devDependencies": {
    "nodemon": "^1.14.11"
  }
```

由於該工具沒有全局安裝，我們無法從命令行啟動它（除非我們將其添加到路徑中），但是我們可以從 NPM 腳本中調用它，因為 NPM 知道所有關於安裝的軟件包的信息。找到你的 package.json 的腳本 scripts 區塊。我們更新 `scripts` 區塊，最初的一行，以"`start`"開頭，在該行的末尾添加逗號，並添加 "`devstart`" 開頭的一行，如下所示：

```json
  "scripts": {
    "start": "node ./bin/www",
    "devstart": "nodemon ./bin/www"
  },
```

現在我們可以用與前面幾乎完全相同的方式，啟動服務器，但使用指定的 devstart 命令：

* 在 Windows，使用此命令:

```bash
SET DEBUG=express-locallibrary-tutorial:* & npm run devstart
```

* 在 macOS or Linux，使用此命令:

```bash
DEBUG=express-locallibrary-tutorial:* npm run devstart
```

**備註：** 現在，如果你編輯項目中的任何文件，服務器將重新啟動（或者你可以隨時在命令提示符下，鍵入`rs`來重新啟動它）。你仍需要重新加載瀏覽器，以刷新頁面。

我們現在必須調用「`npm run <scriptname>`」而不是 `npm start`，因為「start」實際上是映射到指定腳本的 NPM 命令。我們可以在啟動腳本中替換該命令，但我們只想在開發期間使用 nodemon，因此創建新的腳本命令是有意義的。

從產生器得到的項目
---------

現在我們來看看我們剛剛創建的項目。

### 目錄結構

從產生器得到的生成項目，現在已經安裝了依賴項，具有以下文件結構 （**不帶**前綴 「/」 的項目，表示文件）。 **package.json** 文件定義了應用程序依賴項，和其他信息。它還定義了一個啟動腳本，它將調用應用程序入口點 JavaScript 文件 **/bin/www**。這設置了一些應用程序的錯誤處理，然後加載 **app.js** ，來完成剩下的工作。應用程序路徑，存儲在 **/routes** 目錄下的單獨模塊中。模板存儲在 **/views** 目錄下。
```

/express-locallibrary-tutorial
    app.js
    /bin
        www
    package.json
    /node_modules
        [about 4,500 subdirectories and files]
    /public
        /images
        /javascripts
        /stylesheets
            style.css
    /routes
        index.js
        users.js
    /views
        error.pug
        index.pug
        layout.pug
```

以下各節將詳細介紹這些文件。

### package.json

**package.json** 文件定義了應用程序依賴關係，和其他訊息：

```json
{
  "name": "express-locallibrary-tutorial",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "devstart": "nodemon ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.18.2",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.9",
    "express": "~4.16.2",
    "morgan": "~1.9.0",
    "pug": "~2.0.0-rc.4",
    "serve-favicon": "~2.4.5"
  },
  "devDependencies": {
    "nodemon": "^1.14.11"
  }
}
```

依賴關係包括 express 套件，和我們所選視圖引擎（pug）的套件。另外，我們還有以下的套件，在許多 Web 應用程序中很有用：

* body-parser: 這解析傳入 HTTP 請求的正文 body 部分，並更容易提取包含信息的不同部分。例如，你可以使用它來讀取 `POST` 參數。
* cookie-parser: 用於解析 cookie header 並填充 `req.cookies`（本質上提供了訪問 cookie 信息的便捷方法）。
* debug: 一個小型 node 調試程序，仿照 node 核心的調試技術建立的。
* morgan: 搭配 node 使用的 HTTP 請求記錄器中間層軟件。
* serve-favicon: 用於提供收藏圖標 favicon 的 node 中間層軟件（這是用於表示瀏覽器選項卡、書籤等網站內的圖標）。

腳本部分，定義了一個「開始」 "start" 腳本，當我們調用 `npm start` 來啟動服務器時，這就是我們所調用的腳本。從腳本定義中，你可以看到這實際上用 node 啟動了 JavaScript 文件 **./bin/www**。它還定義了一個「devstart」 腳本，我們在調用 `npm run devstart` 時調用它。這將啟動相同的 **./bin/www** 文件，但使用 nodemon 調用而不是 node 。

```json
  "scripts": {
    "start": "node ./bin/www",
    "devstart": "nodemon ./bin/www"
  },
```

### www 文件

文件 **/bin/www** 是應用程序入口點！它做的第一件事是 `require()` 「真正的」 應用程序入口點（即項目根目錄中的 app.js ），**app.js** 會設置並返回 `express()`應用程序的對象。

```js
#!/usr/bin/env node

/\*\*
 \* Module dependencies.
 \*/

var app = require("../app");
```

**備註：** `require()` 是一個全局 node 函數，用於將模塊導入當前文件。這裡我們使用相對路徑指定 **app.js** 模塊，並省略可選的（**.js**）文件擴展名。

此文件中的其餘代碼，將設置一個 node 運行的 HTTP 服務器，並將應用 app 設置為特定的端口（在環境變量中定義，如果變量未定義，則定義為 3000），並開始監聽和報告服務器錯誤和連接。現在你並不需要知道代碼的其他內容（這個文件中的所有內容都是 「樣板文件」 ），但如果你感興趣，可以隨時查看它。

### app.js

此文件創建一個 `express` 應用程序對象（按傳統命名為 `app`），使用各種設置和中間件，以設置應用程序，然後從模塊導出應用程序。下面的代碼只顯示了文件的一部分，創建和導出應用程序對象的部分：

```js
var express = require('express');
var app = express();
...
module.exports = app;
```

回到上面的 **www** 入口點文件，它是在導入該文件時，提供給調用者的這個 `module.exports` 對象。

讓我們詳細了解 **app.js** 文件。首先，我們使用 `require()`將一些有用的 node 庫導入到文件中，其中包括我們先前使用 NPM 為應用程序下載的 express，serve-favicon，morgan，cookie-parser 和 body-parser；和 path 庫，它是解析文件和目錄路徑的核心 node 庫。

```js
var express = require("express");
var path = require("path");
var favicon = require("serve-favicon");
var logger = require("morgan");
var cookieParser = require("cookie-parser");
var bodyParser = require("body-parser");
```

然後我們用 `require()`導入來自我們的路由目錄的模塊。這些模塊/文件包含用於處理特定的相關「路由」集合（URL 路徑）的代碼。當我們擴展骨架應用程序，例如列出圖書館中的所有書籍時，我們將添加一個新文件，來處理與書籍相關的路由。

```js
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
```

**備註：** 此時我們剛剛導入了模塊；我們還沒有真正使用過它的路由（在文件的更下方一點將使用到路由）。

接下來，我們使用導入的 express 模塊 ，創建應用程序 `app` 對象，然後使用它來設置視圖（模板）引擎。引擎的設置有兩個部分。首先我們設置 '`views`' 值，來指定模板將被存儲的文件夾（在這種情況下是子文件夾 **/views**）。然後我們設置 '`view engine`' 的值，來指定模板庫（在本例中為 「pug」 ）。

```js
var app = express();

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
```

下一組函數調用 `app.use()`，將中間件的庫，添加到請求處理鏈中。除了我們之前導入的第三方庫之外，我們還使用 `express.static` 中間件，來使 Express 提供在項目根目錄下，**/public** 目錄中的所有靜態文件。

```js
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(\_\_dirname, 'public', 'favicon.ico')));
app.use(logger("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));
```

現在所有其他中間件都已設置完畢，我們將（先前導入的）路由處理代碼，添加到請求處理鏈中。導入的代碼，將為網站的不同部分定義特定路由：

```js
app.use("/", indexRouter);
app.use("/users", usersRouter);
```

**備註：** 上面指定的路徑 ('/' and '`/users`')，被視為定義在導入文件中的路由前綴。因此，例如，如果導入的用戶模塊 **users**為`/profile`定義了路由，則可以在 `/users/profile`中訪問該路由。我們將在後面的文章中，詳細討論路由。

文件中的最後一個中間件，為錯誤和 HTTP 404 響應添加了處理程序方法。

```js
// catch 404 and forward to error handler
app.use(function (req, res, next) {
  var err = new Error("Not Found");
  err.status = 404;
  next(err);
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

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

Express 應用程序對象（app）現已完全完成配置。最後一步，是將其添加到模塊導出（這允許它通過 **/bin/www** 導入）。

```js
module.exports = app;
```

### 路由

路由文檔 **/routes/users.js** 如下所示（路由文件共享一個類似的結構，所以我們不需要也顯示 **index.js**）。首先加載 express 模塊 ，並使用它獲取 `express.Router`對象。然後它在該對像上指定一個路由，最後從模塊中導出路由器（這就是允許將文件導入到 **app.js** 中的路由）。

```js
var express = require("express");
var router = express.Router();

/\* GET users listing. \*/
router.get("/", function (req, res, next) {
  res.send("respond with a resource");
});

module.exports = router;
```

該路由定義了一個回調，只要檢測到具有正確模式的 HTTP `GET` 請求，就會調用該回調。匹配模式是模塊導入時指定的路由（'`/users`'），加上（'`/`'）文件中定義的任何內容。換句話說，當收到`/users/`的 URL 時，將使用此路由。

**備註：** 嘗試運行帶有 node 的服務器，並在瀏覽器中訪問以下 URL: http://localhost:3000/users/。你應該看到一條消息：'respond with a resource'。

上面有趣的事情是，回調函數有第三個參數 '`next`'，因此是一個中間件函數，而不是簡單的路由回調。雖然代碼當前不使用 `next` 參數，但如果要在'`/`'根路由路徑中，添加多個路由處理程序，將來可能會有用。

### 視圖(模板)

視圖（模板）存儲在 **/views** 目錄中（如 **app.js** 中指定的）並且被賦予文件擴展名\*\*.pug\*\*。方法 `Response.render()`用於呈現指定的模板，以及在對像中傳遞的命名變量的值，然後將結果作為響應發送。在來自 **/routes/index.js** 的以下代碼中，你可以看到，該路由如何使用模板 "index" 傳遞模板變量 "title" ，以呈現響應。

```js
/\* GET home page. \*/
router.get("/", function (req, res) {
  res.render("index", { title: "Express" });
});
```

上面路由的相應模板在下面給出（**index.pug**）。我們稍後會詳細討論這個語法。你現在需要知道的是，標題變量 `title`（值為 '`Express`'）將插入模板中指定的位置。
```

extends layout

block content
  h1= title
  p Welcome to #{title}
```

挑戰自己
----

在 **/routes/users.js** 中創建一個新路由，它將在 `/users/cool/`上顯示文本 「You're so cool」。通過運行服務器，並在瀏覽器中訪問 http://localhost:3000/users/cool/ 來測試它。

總結
--

你現在為 本地圖書館 創建了一個骨架網站項目，並且用 node 驗證了它能夠運行。最重要的，你也理解了項目的結構，因此你也明白了我們需要為本地圖書館加上路由和視圖。

接下來我們將開始修改骨架，讓它能像一個圖書館網站一樣運作。

參閱
--

* Express application generator (Express docs)
* Using template engines with Express (Express docs)
* 前頁
* Overview: Express Nodejs
* 次頁


Express 教學 3: 使用資料庫 ( Mongoose) - 學習該如何開發 Web

Express 教學 3: 使用資料庫 ( Mongoose)
===============================

* 前頁
* Overview: Express Nodejs
* 次頁

本文簡短介紹數據庫，以及如何搭配 Node / Express 應用，使用數據庫。接下來會演示我們如何使用 Mongoose，為本地圖書館提供數據庫存取。本文說明物件要求與模型如何宣告，主要的欄位型態，以及基本驗證。本文也簡短演示一些存取模型數據的主要方法。

|  |  |
| --- | --- |
| 前置條件: | Express 教學 2: 創建一個骨架網站 |
| 目標: | 能夠使用Mongoose設計並創造自己的模型。 |

概覽
--

圖書館職員會使用本地圖書館網站，存放書本和借書者訊息。圖書館使用者會用網站瀏覽與尋找書本，看看是否有可以藉閱的書本複本，然後預約或者藉閱。為了有效率地存放與取用訊息，我們將把它存放到數據庫。

Express 應用可以使用許多不同的數據庫，並且有好幾種方法可以執行創建 **C**reate、讀取 **R**ead、更新 **U**pdate 和刪除 **D**elete (CRUD) 操作。本教程為一些可用的選項，提供簡短的概覽，然後接著詳細演示該選項的特定運行機制。

### 我可以使用什麼數據庫?

Express 應用程序可以使用 Node 支持的任何數據庫（Express 本身不會為數據庫管理，定義任何特定的附加行為/要求）。有許多流行的選項，包括 PostgreSQL，MySQL，Redis，SQLite 和 MongoDB。

在選擇數據庫時，你應該考慮時間 - 生產力/學習曲線，性能，易複製/備份，成本，社區支持等等。雖然沒有單一的 「最佳」 數據庫，但幾乎任何流行的解決方案，我們的本地圖書館這樣的中小型網站，應該都可以接受。

有關選項的更多訊息，請參閱：數據庫集成（Express docs）。

### 與數據庫互動的最好方式是什麼?

有兩種與數據庫互動的方法：

* 使用數據庫的原生查詢語言（例如 SQL）
* 使用對像數據模型（「ODM」）/對象關係模型（「ORM」）。 ODM / ORM 將網站的數據表示為 JavaScript 對象，然後將其映射到底層數據庫。一些 ORM 綁定到特定的數據庫，而另一些則提供了一個不特定數據庫的後端。

通過使用 SQL 或數據庫支持的任何查詢語言，都可以獲得最佳性能。 ODM 通常比較慢，因為它們使用翻譯代碼，在對象和數據庫格式之間進行映射，這可能不會使用最有效的數據庫查詢（尤其是如果 ODM 支持不同的數據庫後端，並且必須在各個數據庫所支持的功能方面，做出更大的折衷）。

使用 ORM 的好處是，程序員可以繼續用 JavaScript 對象而不是數據庫語義來思考 — 如果你需要使用不同數據庫（在相同或不同的網站上），那麼尤其如此。他們還提供了一個明顯的地方來執行數據驗證和檢查。

**備註：** 使用 ODM / ORM 通常可以降低開發和維護成本！除非你非常熟悉本地查詢語言，或者性能對你至關重要，否則你應該強烈考慮使用 ODM。

### 我應該使用哪個 ORM/ODM ?

NPM 套件管理器站點上，有許多 ODM / ORM 解決方案（查看 odm 和 orm 標籤的子集合！）。

在撰寫本文時，受歡迎的幾種解決方案是：

* Mongoose: Mongoose 是一個 MongoDB對象建模工具，用於在異步環境中工作。
* Waterline: 它是從基於 Express 的 Sails web 框架中提取的 ORM。它提供了一個統一的 API，來訪問眾多不同的數據庫，包括 Redis，mySQL，LDAP，MongoDB 和 Postgres。
* Bookshelf: 提供基於 promise 和傳統回調的接口，提供事務支持，eager/嵌套 eager 關係加載，多態關聯以及對一對一，一對多和多對多關係的支持。適用於 PostgreSQL，MySQL 和 SQLite3。
* Objection: 以盡可能簡單的方式，使用 SQL 的全部功能，和底層數據庫引擎（支持 SQLite3，Postgres 和 MySQL）。
* Sequelize 是 Node.js 和 io.js 基於 promise 的 ORM。它支持以下數據庫方言，PostgreSQL，MySQL，MariaDB，SQLite 和 MSSQL，並具有可靠的事務支持，關係，唯讀複本等功能。

一般來說，在選擇解決方案時，你應該考慮提供的功能和 「社區活動」 （下載，貢獻，錯誤報告，文檔質量等）。在撰寫本文時，Mongoose 是迄今為止最受歡迎的 ODM，如果你將 MongoDB 用於你的數據庫，那麼它是一個合理的選擇。

### 在本地圖書館使用 Mongoose 和 MongoDb

對於本地圖書館示例（以及本主題的其餘部分），我們將使用 Mongoose ODM 來訪問我們的圖書館數據。 Mongoose 是 MongoDB 的前端，MongoDB 是一個使用面向文檔數據模型的開源 NoSQL 數據庫。在 MongoDB 數據庫中，「文檔」 的 「集合」 ，類似於關係數據庫中 「行」 的 「表」。

這種 ODM 和數據庫的結合在 Node 社區中非常流行，部分原因是文檔存儲和查詢系統，看起來非常像 JSON，因此對 JavaScript 開發人員來說很熟悉。

**備註：** 使用 Mongoose 時，你不需要事先了解 MongoDB，但是如果你已經熟悉 MongoDB，Mongoose documentation文檔的一部分會更易於使用和理解。

本教程的其餘部分，將介紹如何為 本地圖書館網站示例，定義和訪問 Mongoose 模式和模型。

設計本地圖書館的模型
----------

在你開始編寫模型之前，花幾分鐘的時間思考，我們需要儲存的數據以及不同對象之間的關係。

我們知道，我們需要儲存有關書籍的訊息（標題，摘要，作者，種類，國際標準書號），以及我們可能有多個副本可用（具有全域唯一 ID，可用狀態等）。我們可能需要存儲有關作者的更多訊息，而不僅僅是他們的名字，並且可能有多個作者，具有相同或相似的名稱。我們希望能夠根據書名，作者，種類和類別對訊息進行分類。

在設計模型時，對於每個「對象」（相關訊息組）都有獨立的模型，是有意義的。在這種情況下，明顯的對像是書籍，書籍實例和作者。

你可能還希望，使用模型來表示選擇列表選項（例如，選擇的下拉列表），而不是將選項硬編碼到網站本身— 在無法預先知道所有選項，或者可能更改時，更建議使用模型來表示。很明顯的，書本類型是這種模型的可能人選（例如科幻小說，法國詩歌等）。

一旦我們決定了我們的模型和字段，我們就需要考慮它們之間的關係。

考慮到這一點，下面的 UML 關聯圖，顯示了我們在這種情況下定義的模型（一個框對應一個模型）。如上所述，我們創建了以下模型，圖書（本書的通用細節），書本實例（系統中可用圖書的特定實際副本的狀態）和作者。我們還決定建立一個種類模型，以便可以動態創建它的值，而不是將下拉選項硬編碼。我們已經決定不為書本實例：狀態`BookInstance:status`建立模型—我們將硬編碼可接受的值，因為我們不希望這些值發生變化。在下圖每個框中，你可以看到模型名稱，字段名稱和類型，以及方法及其返回類型。

下圖還顯示了模型之間的關係，包括它們的多重性。多重性是圖中顯示可能存在於關係中的每個模型的數量（最大值和最小值）的數字。例如，框之間的連接線，顯示書本`Book`和種類`Genre`是相關的。靠近書本`Book`模型的數字，表明一本書必須有零個或多個種類（你想要多少都可以），而種類`Genre`旁邊一行的數字，表明它可以有零個或多個相關書籍。

**備註：** 正如我們在下面的**Mongoose 入門**中所討論的那樣，通常只需要在一個模型中定義文檔/模型之間關係的字段（通過在另一個模型中搜索相關的`_id`仍然可以找到反向關係）。下面我們選擇在書本綱要（Book schema）中定義 Book/Genre 和 Book/Author 之間的關係，以及書本實例綱要（BookInstance Schema）中 Book/BookInstance 之間的關係。這種選擇有點武斷—我們同樣可以在其他綱要中擁有該字段。

![Mongoose Library Model with correct cardinality](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/library_website_-_mongoose_express.png)

**備註：** 下一節提供了一個基本的入門知識，解釋如何定義和使用模型。在你閱讀它時，請想想我們將如何構建上圖中的每個模型。

Mongoose 入門
-----------

本節概述如何將 Mongoose 連接到 MongoDB 數據庫，如何定義模型綱要和模型，以及如何進行基本查詢。

**備註：** 本入門受到 npm 上的Mongoose 快速入門和Mongoose 官方文檔的「深度影響」。

### 安裝 Mongoose 和 MongoDB

Mongoose 像任何其他依賴項一樣，安裝在你的項目（**package.json**）中—使用 NPM。要安裝它，請在項目文件夾中，使用以下命令：

```bash
npm install mongoose
```

安裝 Mongoose 會添加所有依賴項，包括 MongoDB 數據庫驅動程序，但它不會安裝 MongoDB 。如果你想安裝一個 MongoDB 服務器，那麼你可以從這裡下載各種操作系統的安裝程序，並在本地安裝。你還可以使用基於雲端的 MongoDB 實例。

**備註：** 對於本教程，我們將使用基於 mLab 雲的數據庫，作為服務沙箱層來提供數據庫。這適用於開發，也對於本教程很有意義，因為它使「安裝」與操作系統無關（數據庫即服務，也是你可能會用於生產環境數據庫的一種方法）。

### 連接到 MongoDB

Mongoose 需要連接到 MongoDB 數據庫。你可以`require()`並使用`mongoose.connect()`，以連接到本地託管的數據庫，如下所示。

```js
//Import the mongoose module
var mongoose = require("mongoose");

//Set up default mongoose connection
var mongoDB = "mongodb://127.0.0.1/my\_database";
mongoose.connect(mongoDB);
// Get Mongoose to use the global promise library
mongoose.Promise = global.Promise;
//Get the default connection
var db = mongoose.connection;

//Bind connection to error event (to get notification of connection errors)
db.on("error", console.error.bind(console, "MongoDB connection error:"));
```

你可以使用`mongoose.connection`獲取默認的`Connection`對象。一旦連接，在`Connection`實例上，將觸發打開事件。

**備註：** 如果需要創建其他連接，可以使用`mongoose.createConnection()`。這與`connect()`採用相同形式的數據庫 URI（包含主機，數據庫，端口，選項等），並返回`Connection`對象。

### 定義並創建模型

模型使用`Schema`接口進行定義。Schema 允許你定義存儲在每個文檔中的字段，及其驗證要求和默認值。此外，你可以定義靜態和實例助手方法，以更輕鬆地處理數據類型，以及可以像其他任何字段一樣使用的虛擬屬性，但實際上並不存儲在數據庫中（我們稍後將討論）。

然後，綱要 Schemas 被`mongoose.model()`方法「編譯」為模型。擁有模型後，你可以使用它來查找，創建，更新和刪除給定類型的對象。

**備註：** 每個模型都映射到 MongoDB 數據庫中的文檔集合。這些文檔將包含模型綱要`Schema`中定義的字段/綱要型態。

#### 定義綱要 Schemas

下面的代碼片段，顯示了你可以如何定義一個簡單的綱要。首先`require()`mongoose，然後使用 Schema 構造函數，創建一個新的 Schema 實例，在構造函數的對象參數中，定義其中的各個字段。

```js
//Require Mongoose
var mongoose = require("mongoose");

//Define a schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
  a\_string: String,
  a\_date: Date,
});
```

在上面的例子中，我們只有兩個字段，一個字符串和一個日期。在接下來的部分中，我們將展示一些其他的字段類型，驗證和其他方法。

#### 創建模型

使用`mongoose.model()`方法從綱要創建模型：

```js
// Define schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
  a\_string: String,
  a\_date: Date,
});

// Compile model from schema
var SomeModel = mongoose.model("SomeModel", SomeModelSchema);
```

第一個參數，是將為模型創建的集合的單數名稱（Mongoose 將為上面的 SomeModel 模型，創建數據庫集合），第二個參數，是你要在創建模型時使用的綱要 Shema。

**備註：** 定義模型類後，可以使用它們來創建，更新或刪除記錄，並運行查詢，以獲取記錄的所有記錄，或特定子集。我們將在以下「使用模型」部分，向你展示如何執行上述操作，以及當創建視圖時，如何執行此操作。

#### 綱要型態（字段）

綱要 schema 可以有任意數量的字段 — 每個字段代表存儲在 MongoDB 文檔中的字段。如下的示例綱要，顯示許多常見字段類型及其聲明方式。

```js
var schema = new Schema({
  name: String,
  binary: Buffer,
  living: Boolean,
  updated: { type: Date, default: Date.now },
  age: { type: Number, min: 18, max: 65, required: true },
  mixed: Schema.Types.Mixed,
  \_someId: Schema.Types.ObjectId,
  array: [],
  ofString: [String], // You can also have an array of each of the other types too.
  nested: { stuff: { type: String, lowercase: true, trim: true } },
});
```

大多數綱要型態SchemaTypes（「type：」之後或字段名稱之後的描述符）都是自解釋的。例外情況是：

* `ObjectId`:表示數據庫中模型的特定實例。例如，一本書可能會使用它來表示其作者對象。這實際上將包含指定對象的唯一 ID ( `_id`) 。我們可以使用`populate()`方法，在需要時提取相關訊息。
* Mixed :任意綱要型態。
* []：一個數組的項目。你可以在這些模型上執行 JavaScript 數組操作（push、pop、unshift 等）。上面的例子，顯示了一個沒有指定類型的對像數組，和一個 String 對像數組，但是你可以有任何類型的對像數組。

該代碼還顯示了聲明一個字段的兩種方式：

* 字段名稱和類型作為鍵值對（即是，像上面的`name`, `binary` and `living`）。
* 字段名稱後跟一個定義類型`type`的對象，以及該字段的任何其他選項。選項包括如下內容：
    + 默認值。
    + 內置驗證器（例如最大/最小值）和自定義驗證功能。
    + 該字段是否為必要
    + 是否應將字符串`String`字段自動設置為小寫，大寫或修剪（例如)`{ type:String, lowercase: true, trim: true }`

有關選項的更多訊息，請參閱SchemaTypes（Mongoose docs）。

#### 驗證

Mongoose 提供內置和自定義驗證器，以及同步和異步驗證器。它允許你在所有情況下，指定可接受的範圍或值，以及驗證失敗的錯誤消息。

內置的驗證器包括：

* 所有SchemaTypes都具有內置的必需驗證器。這用於指定，是否必須提供該字段才能保存文檔。
* Numbers 數字有最小min和最大max驗證器。
* Strings字符串有：
    + enum枚舉：指定該字段的允許值集合。
    + match :指定字符串必須匹配的正則表達式。
    + 字符串的最大長度maxlength和最小長度minlength

下面的示例（從 Mongoose 文檔稍微修改）顯示瞭如何指定一些驗證器類型和錯誤消息：

```js
    var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, 'Too few eggs'],
        max: 12
        required: [true, 'Why no eggs?']
      },
      drink: {
        type: String,
        enum: ['Coffee', 'Tea', 'Water',]
      }
    });
```

有關字段驗證的完整訊息，請參閱驗證（Mongoose docs）。

#### 虛擬屬性

虛擬屬性是你可以獲取和設置的文檔屬性，但不會持久保存到 MongoDB。getter 對格式化或組合字段非常有用，而 setter 可用於將單個值分解為多個值，以進行存儲。

文檔中的示例，從名字和姓氏字段構造（並解構）一個全名虛擬屬性，這比每次在模板中使用全名更簡單，更清晰。

**備註：** 我們將使用庫中的虛擬屬性，來為每個使用路徑和記錄的`_id`值的模型記錄，定義唯一的 URL。

欲了解更多訊息，請參閱虛擬（Mongoose 文檔）。

#### 方法和查詢幫助

綱要 schema 也可以有實例方法，靜態方法和查詢助手。實例和靜態方法很相似，但有明顯的區別，即實例方法與特定記錄相關聯，並且可以訪問當前對象。查詢助手允許你擴展 mongoose 的鍊式查詢構建器 API（例如，除了`find()`, `findOne()`和`findById()`方法外，還允許你添加一個「byName」查詢。

### 使用模型

一旦創建了綱要，就可以使用它來創建模型。該模型代表數據庫中可以搜索的文檔集合，而模型的實例代表你可以保存和檢索的單個文檔。

我們在下面簡要介紹一下。有關更多訊息，請參閱：模型（Mongoose docs）。

#### 創建和修改文檔

要創建記錄，你可以定義模型的實例，然後調用`save()`。下面的例子假設，SomeModel 是我們從綱要創建的模型（帶有單一字段「name」 ）。

```js
// Create an instance of model SomeModel
var awesome_instance = new SomeModel({ name: "awesome" });

// Save the new model instance, passing a callback
awesome_instance.save(function (err) {
  if (err) return handleError(err);
  // saved!
});
```

創建記錄（以及更新，刪除和查詢）是異步操作— 你提供在操作完成時調用的回調。API 使用錯誤優先參數約定，因此回調的第一個參數將始終為錯誤值（或 null）。如果 API 返回一些結果，則將作為第二個參數提供。

你還可以使用`create()`，同時定義模型實例，並保存模型實例。回調將為第一個參數返回錯誤，為第二個參數返回新創建的模型實例。

```js
SomeModel.create({ name: "also\_awesome" }, function (err, awesome\_instance) {
  if (err) return handleError(err);
  // saved!
});
```

每個模型都有一個關聯的連接（當你使用`mongoose.model()`時，這將成為默認連接）。你創建一個新連接並調用`.model()`，以在另一個數據庫上創建文檔。

你可以使用點語法訪問此新記錄中的字段，並更改值。你必須調用`save()`或`update()`，將修改的值存回數據庫。

```js
// Access model field values using dot notation
console.log(awesome_instance.name); //should log 'also\_awesome'

// Change record by modifying the fields, then calling save().
awesome_instance.name = "New cool name";
awesome_instance.save(function (err) {
  if (err) return handleError(err); // saved!
});
```

#### 尋找紀錄

可以使用查詢方法搜索記錄，將查詢條件指定為 JSON 文檔。下面的代碼片段，顯示瞭如何在數據庫中，找到所有參加網球運動的運動員，只返回運動員姓名和年齡的字段。這裡我們只指定一個匹配的字段（運動 sport），但你可以添加更多條件，指定正則表達式標準，或完全刪除條件以返回所有運動員。

```js
var Athlete = mongoose.model("Athlete", yourSchema);

// find all athletes who play tennis, selecting the 'name' and 'age' fields
Athlete.find({ sport: "Tennis" }, "name age", function (err, athletes) {
  if (err) return handleError(err);
  // 'athletes' contains the list of athletes that match the criteria.
});
```

如果你指定回調，如上所示，查詢將立即執行。搜索完成後將調用回調。

**備註：** Mongoose 中的所有回調，都使用此回調模式`callback(error, result)`。如果執行查詢時發生錯誤，錯誤參數`error`將包含錯誤文檔，並且結果`result`將為 null。如果查詢成功，則`error`參數將為 null，並且結果`result` 將被填充到查詢結果。

如果你未指定回調，則 API 將返回Query類型的變量。你可以使用此查詢對象來構建查詢，然後稍後使用`exec()`方法執行（使用回調）。

```js
// find all athletes that play tennis
var query = Athlete.find({ sport: "Tennis" });

// selecting the 'name' and 'age' fields
query.select("name age");

// limit our results to 5 items
query.limit(5);

// sort by age
query.sort({ age: -1 });

// execute the query at a later time
query.exec(function (err, athletes) {
  if (err) return handleError(err);
  // athletes contains an ordered list of 5 athletes who play Tennis
});
```

上面我們在`find()`方法中，定義了查詢條件。我們也可以使用`where()`函數來執行此操作，並且我們可以使用點運算符（ . ）將查詢的所有部分鏈接在一起，而不是分別添加它們。

下面的代碼片段，與我們上面的查詢相同，並有年齡的附加條件。
```

Athlete.
  find().
  where('sport').equals('Tennis').
  where('age').gt(17).lt(50).  //Additional where query
  limit(5).
  sort({ age: -1 }).
  select('name age').
  exec(callback); // where callback is the name of our callback function.
```

find() 方法獲取所有匹配的記錄，但通常你只想獲得一個匹配。以下方法可以查詢單個記錄：

* `findById()`:用指定的`id`查找文檔（每個文檔都有一個唯一的`id`）。
* `findOne()`: 查找與指定條件匹配的單個文檔。
* `findByIdAndRemove()`, `findByIdAndUpdate()`, `findOneAndRemove()`, `findOneAndUpdate()`:通過`id` 或條件查找單個文檔，並更新或刪除它。這些是用於更新和刪除記錄的有用便利功能。

**備註：** 還有一個`count()`方法，你可以使用它來獲取與條件匹配的項目數。如果你想要在不實際提取記錄的情況下執行計數，這非常有用。

查詢可以做更多的事情。有關更多訊息，請參閱：查詢（Mongoose 文檔）。

#### 運用相關文檔— population 方法

你可以使用`ObjectId`綱要字段，從一個文檔/模型實例，創建一對一引用，或者使用`ObjectIds`數組，從一個文檔創建一對多的引用。該字段存儲相關模型的 ID。如果需要關聯文檔的實際內容，可以在查詢中使用`populate()`方法，將 id 替換為實際數據。

例如，以下綱要定義作者和故事。每個作者可以有多個故事，我們將其表示為一個`ObjectId`數組。每個故事可以有一個作者。綱要從「ref」（以粗體突出顯示）得知，可以分配給該字段的模型。

```js
var mongoose = require("mongoose"),
  Schema = mongoose.Schema;

var authorSchema = Schema({
  name: String,
  stories: [{ type: Schema.Types.ObjectId, ref: "Story" }],
});

var storySchema = Schema({
  author: { type: Schema.Types.ObjectId, ref: "Author" },
  title: String,
});

var Story = mongoose.model("Story", storySchema);
var Author = mongoose.model("Author", authorSchema);
```

我們可以通過分配`_id`值，來保存對相關文檔的引用。下面我們創建一個作者，然後創建一個故事，並將作者 ID 分配給我們的故事作者字段。

```js
var bob = new Author({ name: "Bob Smith" });

bob.save(function (err) {
  if (err) return handleError(err);

  //Bob now exists, so lets create a story
  var story = new Story({
    title: "Bob goes sledding",
    author: bob._id, // assign the \_id from the our author Bob. This ID is created by default!
  });

  story.save(function (err) {
    if (err) return handleError(err);
    // Bob now has his story
  });
});
```

我們的故事文檔，現在有作者文檔 ID 引用的作者。為了在我們的故事結果中，獲取作者訊息，我們使用`populate()`，如下所示。

```js
Story.findOne({ title: "Bob goes sledding" })
  .populate("author") //This populates the author id with actual author information!
  .exec(function (err, story) {
    if (err) return handleError(err);
    console.log("The author is %s", story.author.name);
    // prints "The author is Bob Smith"
  });
```

**備註：** 敏銳的讀者會注意到，我們在故事中添加了作者，但我們沒有做任何事情，來將我們的故事添加到作者的故事`stories`數組中。那麼我們怎樣才能得到特定作者的所有故事？

一種方法，是將作者添加到故事數組中，但這會導致我們需要在兩個地方，維護與作者和故事有關的訊息。更好的方法是獲取作者的`_id`，然後使用`find()`，在所有故事的作者字段中搜索此內容。

```js
Story.find({ author: bob._id }).exec(function (err, stories) {
  if (err) return handleError(err);
  // returns all stories that have Bob's id as their author.
});
```

這幾乎是你在本教程中，使用相關項目時，需要了解的所有內容。有關更多詳細訊息，請參閱Population（Mongoose docs）。

### 一個檔案對應一個綱要/模型

雖然你可以使用任何喜歡的文件結構創建綱要和模型，但我們強烈建議在每個模型模塊（文件）中，定義每個模型綱要，導出方法以創建模型。如下所示：

```js
// File: ./models/somemodel.js

//Require Mongoose
var mongoose = require("mongoose");

//Define a schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
  a\_string: String,
  a\_date: Date,
});

//Export function to create "SomeModel" model class
module.exports = mongoose.model("SomeModel", SomeModelSchema);
```

然後，你可以在其他文件中，立即要求並使用該模型。下面我們展示如何使用它，來獲取模型的所有實例。

```js
//Create a SomeModel model just by requiring the module
var SomeModel = require("../models/somemodel");

// Use the SomeModel object (model) to find all SomeModel records
SomeModel.find(callback_function);
```

架設 MongoDB 數據庫
--------------

現在我們了解了 Mongoose 能做什麼，以及我們想如何設計我們的模型，現在該開始在 LocalLibrary 網站上工作了。我們想要做的第一件事，就是設置一個 MongoDb 數據庫，我們可以使用它來儲存我們的圖書館數據。

本教程，我們將使用mLab免費的雲託管的「 沙盒 」數據庫。這個數據庫層不適合生產環境的網站，因為它沒有冗餘設計，但它對於開發和原型設計來說非常有用。我們在這裡使用它，是因為它免費且易於設置，並且因為作為數據庫服務供應商來說，mLab 是流行的數據庫選擇之一，你可能會合理選擇你的生產環境數據庫（撰寫本文時，其他流行的選擇包括Compose、ScaleGrid和MongoDB Atlas）。

**備註：** 如果你願意，可以下載並安裝與系統相對應的二進製文件，在本地設置 MongoDb 數據庫。除了你在連接時指定的數據庫 URL 之外，本文中的其餘指令將很類似。

你首先需要使用 mLab 創建一個賬戶（這是免費的，只需要輸入基本聯繫訊息，並確認其服務條款）。

登錄後，你將進入mLab 主畫面:

1. 單擊 *MongoDB Deployments* 部分中的 **Create New**。
 ![](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlabcreatenewdeployment.png)
2. 這將打開「雲提供商」Cloud Provider 選擇畫面。

 ![MLab - screen for new deployment](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlab_new_deployment_form_v2.png)
    * 從「計劃類型」*Plan Type* 部分中，選擇「SANDBOX（免費）」計劃。
    * 從「雲提供商」*Cloud Provider* 部分，選擇任意提供商。不同的提供商，提供不同的地區（顯示在選定的計劃類型下面）。
    * 單擊「繼續」**Continue** 按鈕。
3. 這將打開「選擇區域」*Select Region* 畫面。

 ![Select new region screen](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlab_new_deployment_select_region_v2.png)
    * 選擇離你最近的地區，然後選擇繼續 **Continue**。
4. 這將打開 Final Details 畫面。

 ![New deployment database name](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlab_new_deployment_final_details.png)
    * 輸入新數據庫的名稱 `local_library`，然後選擇繼續 **Continue**。
5. 這將打開訂單確認畫面。

 ![Order confirmation screen](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlab_new_deployment_order_confirmation.png)
    * 單擊「提交訂單」**Submit Order** 以創建數據庫。
6. 你將返回到主畫面。單擊剛剛創建的新數據庫，以打開其詳細訊息畫面。正如你所看到的，數據庫沒有集合（數據）。

 ![mLab - Database details screen](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlab_new_deployment_database_details.png)
 你需要用來訪問數據庫的 URL，顯示在上面的表單中（如上圖所示）。為了使用它，你需要創建一個可以在 URL 中指定的數據庫用戶。
7. 單擊用戶**Users**選項卡，並選擇添加數據庫用戶按鈕**Add database user**。
8. 輸入用戶名和密碼（兩次），然後按創建**Create**。不要選擇只讀*read-only*。

 ![](/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose/mlab_database_users.png)

你現在已經創建了數據庫，並且有一個可以用來訪問它的 URL（帶有用戶名和密碼）。這看起來像是這樣的：`mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library`.

安裝 Mongoose
-----------

打開命令提示符，並到你創建本地圖書館骨架網站的目錄。輸入以下命令，安裝 Mongoose（及其依賴項），並將其添加到你的**package.json**文件中，除非你在閱讀上述**Mongoose 入門**時，已經這樣做了。

```bash
npm install mongoose
```

連接到 MongoDB
-----------

打開\*\*/app.js\*\*（位於項目的根目錄），並在宣告 Express 應用程序對象的位置（在`var app = express();`之後）複製以下文本。將數據庫 url 字符串（'insert\_your\_database\_url\_here'）替換為表示你自己的數據庫的位置 URL（即是使用**來自上面 mLab**的訊息）。

```js
//Set up mongoose connection
var mongoose = require("mongoose");
var mongoDB = "insert\_your\_database\_url\_here";
mongoose.connect(mongoDB);
mongoose.Promise = global.Promise;
var db = mongoose.connection;
db.on("error", console.error.bind(console, "MongoDB connection error:"));
```

正如上面的**Mongoose 入門**中所討論的，此代碼創建了與數據庫的默認連接，並綁定到錯誤事件（以便將錯誤打印到控制台）。

定義本地圖書館綱要
---------

如上所述，我們將為每個模型定義一個單獨的模塊。首先在項目根目錄（**/models**）中，為我們的模型創建一個文件夾，然後為每個模型創建單獨的文件：
```

/express-locallibrary-tutorial  //the project root
  /models
    author.js
    book.js
    bookinstance.js
    genre.js
```

### 作者模型

複製下面顯示的`Author`作者綱要代碼，並將其粘貼到\*\*./models/author.js\*\*文件中。該綱要定義了一個作者，具有`String`SchemaTypes 的第一個名稱和家族名稱，這是必需的，最多有 100 個字符，`Date`字段為出生和死亡日期。

```js
var mongoose = require("mongoose");

var Schema = mongoose.Schema;

var AuthorSchema = new Schema({
  first\_name: { type: String, required: true, max: 100 },
  family\_name: { type: String, required: true, max: 100 },
  date\_of\_birth: { type: Date },
  date\_of\_death: { type: Date },
});

// Virtual for author's full name
AuthorSchema.virtual("name").get(function () {
  return this.family_name + ", " + this.first_name;
});

// Virtual for author's URL
AuthorSchema.virtual("url").get(function () {
  return "/catalog/author/" + this._id;
});

//Export model
module.exports = mongoose.model("Author", AuthorSchema);
```

我們還為 AuthorSchema，聲明了一個名為「url」的虛擬屬性，它返回獲取模型的特定實例所需的絕對 URL — 每當我們需要獲取指向特定作者的鏈接時，我們將在模板中使用該屬性。

**備註：** 在綱要中聲明我們的 URL 是虛擬的，這是一個好主意，因為一個項目的 URL 只需要在一個地方更改。此時，使用此 URL 的鏈接將不起作用，因為我們還沒有任何路由，可以處理個別模型實例的代碼。我們將在後面的文章中介紹這些內容！

在模塊的最後，我們導出了模型。

### 書本模型

複製下面顯示的`Book`綱要代碼，並將其粘貼到\*\*./models/book.js\*\*文件中。其中大部分與作者模型相似—我們已經聲明了一個具有多個字符串字段的綱要，以及一個虛擬屬性，用於獲取特定書籍記錄的 URL，並且我們已經導出了模型。

```js
var mongoose = require("mongoose");

var Schema = mongoose.Schema;

var BookSchema = new Schema({
  title: { type: String, required: true },
  author: { type: Schema.Types.ObjectId, ref: "Author", required: true },
  summary: { type: String, required: true },
  isbn: { type: String, required: true },
  genre: [{ type: Schema.Types.ObjectId, ref: "Genre" }],
});

// Virtual for book's URL
BookSchema.virtual("url").get(function () {
  return "/catalog/book/" + this._id;
});

//Export model
module.exports = mongoose.model("Book", BookSchema);
```

這裡的主要區別，是我們已經創建了兩個對其他模型的引用：

* 作者是對單個`Author`作者模型對象的引用，並且是必要的。
* 種類是對`Genre`種類模型對像數組的引用。我們還沒有宣告這個對象！
### 書本實例模型

最後，複製下面顯示的`BookInstance`綱要代碼，並將其粘貼到\*\*./models/bookinstance.js\*\*文件中。`BookInstance`表示某人可能藉閱的書籍的特定副本，並包含有關該副本是否可用，或預期返回日期的訊息，「印記」或版本詳細訊息。

```js
var mongoose = require("mongoose");

var Schema = mongoose.Schema;

var BookInstanceSchema = new Schema({
  book: { type: Schema.Types.ObjectId, ref: "Book", required: true }, //reference to the associated book
  imprint: { type: String, required: true },
  status: {
    type: String,
    required: true,
    enum: ["Available", "Maintenance", "Loaned", "Reserved"],
    default: "Maintenance",
  },
  due\_back: { type: Date, default: Date.now },
});

// Virtual for bookinstance's URL
BookInstanceSchema.virtual("url").get(function () {
  return "/catalog/bookinstance/" + this._id;
});

//Export model
module.exports = mongoose.model("BookInstance", BookInstanceSchema);
```

我們在這裡展示的新東西，是字段選項：

* 枚舉`enum`:這允許我們設置字符串的允許值。在這種情況下，我們用它來指定我們書籍的可用性狀態（使用枚舉，意味著我們可以防止錯誤拼寫和任意值，成為我們的狀態）
* 默認值`default`:我們使用默認值，將新創 建的書本實例的默認狀態，設置為維護，並將默認的`due_back`日期，設置為現在`now`（請注意在設置日期時，如何調用 Date 函數！）

其他所有內容，大夥應該在前面教程裡邊已經熟悉了。

### 種類模型-自我挑戰!

打開你的\*\*./models/genre.js\*\*文件，並創建一個存儲類型的綱要（書本的類別，例如它是小說還是非小說，浪漫史或軍事歷史等）。

該定義將與其他模型非常相似：

* 該模型應該有一個名為`name`的`String`SchemaType ，來描述種類。
* 這個`name`字段應該是必要的，並且有 3 到 100 個字符。
* 為類型的 URL 聲明虛擬，名為`url`。
* 導出模型。
測試—創建一些項目
---------

就是這樣。我們現在已經為該網站建立了所有模型！

為了測試這些模型（並創建一些示例書籍，和其他項目以便於我們在後面文章使用），現在我們將運行一個獨立的腳本來創建每種類型的項目：

1. 在 express-locallibrary-tutorial 目錄下（與`package.json`處於同一級別），下載（或以其他方式創建）文件populatedb.js。

**備註：** 你不需要知道populatedb.js的工作原理;它只是將示例數據添加到數據庫中。
2. 在項目根目錄中，輸入以下命令，以安裝腳本所需的異步模塊（我們將在後面的教程中討論這一點）

```bash
npm install async
```

3. 在命令提示符下，使用 node 運行此腳本，傳遞 MongoDB 數據庫的 URL（與之前在`app.js`中替換 insert\_your\_database\_url\_here 佔位符的那個相同）：

```bash
node populatedb <your mongodb url>
```

4. 該腳本應一路運行至完成，並在終端中創建它們時顯示各項目。

**備註：** 至mLab上的數據庫。你現在應該可以深入到書本籍，作者，種類和書本實例的各個集合中，並查看單個文檔。

總結
--

本文中我們學到了一點數據庫和 Node/Express 的 ORMs，更多的是關於如何定義 Mongoose 綱要與模型。然後我們使用這些知識，為本地圖書館網站設計並實作出書本`Book`,書本實例`BookInstance`，作者`Author`和種類`Genre`模型。

最後，我們創建一些實例，以測試模型(使用獨立運作的命令稿)。下一篇文章，我們將關注於如何創建一些網頁，以呈現這些物件。

參閱
--

* Database integration (Express docs)
* Mongoose website (Mongoose docs)
* Mongoose Guide (Mongoose docs)
* Validation (Mongoose docs)
* Schema Types (Mongoose docs)
* Models (Mongoose docs)
* Queries (Mongoose docs)
* Population (Mongoose docs)
* 前頁
* Overview: Express Nodejs
* 次頁


Express 教學 1: 本地圖書館網站 - 學習該如何開發 Web

Express 教學 1: 本地圖書館網站
=====================

* 前頁
* Overview: Express Nodejs
* 次頁

我們實作教程系列的第一篇文章，會說明將學到什麼東西，並提供「本地圖書館」範例網站的概述 。我們將在接下來的文章中一步一步完成這個網站。

|  |  |
| --- | --- |
| 前置條件: |
 閱讀
 Express 介紹。
 在底下的教程，你將需要
 架設一個 Node 開發環境。 |
| 目標: | 介紹本教程的範例應用，讓讀者理解包含哪些主題。 |

概覽
--

歡迎來到 MDN "本地圖書館" Express (Node) 教程，我們將開發一個網站，用於管理本地圖書館的目錄。

本系列教程文章中，你將會:

* 使用 *Express 應用產生器工具，創建一個骨架網站與應用*
* 起動和停止 Node 網頁伺服器
* 使用數据庫存放應用的數据
* 創建路由用以要求不同的信息，創建模板 ("視圖") 以 HTML 的形式在瀏覽器中呈現數据
* 使用表單
* 部署應用到生產環境

這些主題中，有一部分你可能已經學過了，或者曾經簡短的接觸過。在本列系教程的最後，你應該知道的夠多，能夠自己開發簡單的 Express 應用。

本地圖書館網站
-------

*我們接下來將創建，並隨著本系列教程發展的網站，名字是本地圖書館。如同你的預測，此網站的目的，是為一間小型本地圖書館，提供一個線上目錄，使用者能夠瀏覽可取得的書本*，並管理他們的帳号。

本範例經過細心地考慮，因為它的規模可以放大或縮小，以配合我們的需要，演示盡可能多或少的細節。並且可以用來演示幾乎所有的 Express 特性。更重要的，它允許我們提供一條引導路徑，演示你在任何網站都會需要的功能:

* 在教程一開始，我們將定義一個簡單的、只能瀏覽的圖書館，圖書館成員能夠用來找到可以借的書。這允許我們找出，幾乎每個網站都會使用的共同操作: 從數据庫讀取並呈現內容。
* 跟隨教程的進展，圖書館的例子會一步一步擴充，以演示更高級的網站特征。比如我們會擴充圖書館，允許新書能夠被創建，並用這個來演示如何使用表單，並支持使用者授權。

即使這是一個具備相當擴充性的範例，它被叫做**本地**圖書館是有原因的 — 我們希望呈現給你最少的信息，能夠盡快幫助你上手並運行 Express。因此，我們將會存放書本、複本、作者、和其它關鍵信息。然而，我們不會存放其它圖書館可能用到的有關信息，或者提供支持多個圖書館網站的架構，又或者其它 "大型圖書館" 的特性。

我被卡住了，哪裡可以得到原始碼?
----------------

當你使用本教程，我們將在每個知識點，提供適當的代碼片段，讓你複制貼上，同時有些代碼，我們希望你能自己擴充 (會有一些指引)。

如果你被卡住了，你可以在 Github 的這裡，找到本地圖書館網站已經開發完成的版本。

**備註：** 在本教程中，指定版本的 node、Express、還有其它模組，都經過測試，並列出在專案項目的 package.json 檔案中。

總結
--

現在，你對本地圖書館網站以及將要學習的東西，有更多一點的認識，是時候開始創建一個 骨架項目，以存放我們的範例。

* 前頁
* Overview: Express Nodejs
* 次頁


Express 教學 4: 路由與控制器 - 學習該如何開發 Web

Express 教學 4: 路由與控制器
====================

* 前頁
* Overview: Express Nodejs
* 次頁

在本教程中，我們將為最終在 本地圖書館 網站中需要的所有資源端點，搭配 "空殼" 處理函式來配置路由 (URL handling code) 。完成後，我們的路由處理源碼將會有模組化結構，在接下來的文章中，我們可以用真實的處理函式加以擴充。我們也會對如何使用 Express 創建模組化路由，有更好的理解。

|  |  |
| --- | --- |
| 先備知識: |
 閱讀
 Express/Node 介紹。 完成先前教學主題 (包含
 Express 教學 3: 使用資料庫 (Mongoose)).
  |
| 目標: | 理解如何創建簡易路由配置。我們所有的URL端點。 |

概覽
--

在上一篇教程文章中，我們定義了 Mongoose 模型，以與數據庫互動，並使用（獨立）腳本創建一些初始庫記錄。現在我們可以編寫代碼，向用戶展示這些信息。我們需要做的第一件事，是確定我們希望能夠在頁面中顯示哪些信息，然後定義適當的 URL，以返回這些資源。然後我們將需要創建路由（URL 處理程序）和視圖（模板）來顯示這些頁面。

下圖是作為處理 HTTP 請求/響應時，需要實現的主要數據流和事項的提醒。除了視圖和路線之外，圖表還顯示「控制器」 — 實際處理請求的函數，那些與路由請求分開的代碼。

由於我們已經創建了模型，我們需要創建的主要內容是：

* 「路由」將支持的請求（以及請求 URL 中編碼的任何信息）轉發到適當的控制器功能。
* 控制器用於從模型中獲取請求的數據，創建一個顯示數據的 HTML 頁面，並將其返回給用戶，以在瀏覽器中查看。
* 視圖（模板）則由控制器用來呈現數據。

![](/zh-TW/docs/Learn/Server-side/Express_Nodejs/routes/mvc_express.png)

最終，我們可能會有頁面顯示書籍，流派，作者和書籍的列表和詳細信息，以及用於創建，更新和刪除記錄的頁面。對一篇文章來說，這是很多的內容。因此，本文的大部分內容，都將集中在設置我們的路由和控制器，以返回「虛擬」內容。我們將在後續文章中，擴展控制器方法，以使用模型數據。

下面的第一部分，提供了關於如何使用Express Router中間件的簡要「入門」。當我們設置 LocalLibrary 路由時，我們將在後面的章節中使用這些知識。

路由入門
----

路由是 Express 代碼的一部分，它將 HTTP 動詞（`GET`, `POST`, `PUT`, `DELETE`等），URL 路徑/模式和被調用來處理該模式的函數，相關聯起來。

有幾種方法可以創建路線。本教程將使用`express.Router`中間件，因為它允許我們將站點的特定部分的路由處理程序組合在一起，並使用通用的路由前綴訪問它們。我們會將所有與圖書館有關的路由，保存在「目錄」模塊中，如果我們添加路由來處理用戶帳戶或其他功能，我們可以將它們分開保存。

**備註：** 我們在Express 簡介>創建路由處理程序中，簡要討論了 Express 應用程序路由。除了為模塊化提供更好的支持之外（如下面第一小節所述），使用 Router 非常類似於直接在 Express 應用程序對像上定義路由。

本節的其餘部分，概述瞭如何使用路由器`Router`來定義路由。

### 定義和使用單獨的路由模塊

下面的代碼提供了一個具體示例，說明我們如何創建路由模塊，然後在 Express 應用程序中使用它。首先，我們在一個名為**wiki.js**的模塊中創建一個 wiki 的路由。代碼首先導入 Express 應用程序對象，使用它獲取一個

`Router`對象，然後使用`get()`方法向其添加一對路由。所有模塊的最後一個導出路由器`Router`對象。

```js
// wiki.js - Wiki route module.

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

// Home page route.
router.get("/", function (req, res) {
  res.send("Wiki home page");
});

// About page route.
router.get("/about", function (req, res) {
  res.send("About this wiki");
});

module.exports = router;
```

**備註：** 上面我們直接在路由器函數中定義路由處理程序回調。在 LocalLibrary 中，我們將在一個單獨的控制器模塊中，定義這些回調。

要在主應用程序文件中使用路由器模塊，我們首先`require()`路由模塊（**wiki.js**）。然後，我們在 Express 應用程序上調用`use()`，將路由器添加到中間件處理路徑，並指定一個'wiki'的 URL 路徑。

```js
var wiki = require("./wiki.js");
// ...
app.use("/wiki", wiki);
```

然後可以從`/wiki/`和`/wiki/about/`，訪問我們的 wiki 路由模塊中定義的兩個路由。

### 路由函數

我們上面的模塊，定義了幾個典型的路由功能。使用`Router.get()`方法定義「about」路由（在下面），該方法僅響應 HTTP GET 請求。此方法的第一個參數是 URL 路徑，而第二個參數是一個回調函數，如果收到帶有路徑的 HTTP GET 請求，將會調用該函數。

```js
router.get("/about", function (req, res) {
  res.send("About this wiki");
});
```

回調函數接受三個參數（通常如下所示命名：`req`, `res`, `next`），它將包含 HTTP 請求對象，HTTP 響應，以及中間件鏈中的下一個函數。

**備註：** 路由器功能是Express 中間件，這意味著它們必須完成（響應）請求或調用鏈中的下一個功能`next`。在上面的例子中，我們使用`send()`完成了請求，所以下一個參數`next`沒有被使用（我們選擇不指定它）。

上面的路由器函數只需要一次回調，但你可以根據需要指定任意數量的回調參數，或一組回調函數。每個函數都是中間件鏈的一部分，並且將按照添加到鏈中的順序調用（除非前面的函數完成請求）。

這裡的回調函數，在響應中調用`send()`，當我們收到帶有路徑（' `/about'`）的 GET 請求時，返回字符串「About this wiki」。有許多其他響應方法，可以結束請求/響應週期。例如，你可以調用`res.json()`，來發送 JSON 響應，或調用`res.sendFile()`來發送文件。構建庫時，我們最常使用的響應方法是render()，它使用模板和數據創建並返回 HTML 文件—我們將在後面的文章中，進一步討論這個問題！

### HTTP 動詞

上面的示例路由使用`Router.get()`方法，響應具有特定路徑的 HTTP GET 請求。路由器`Router`還為所有其他 HTTP 動詞提供路由方法，這些方法多數以完全相同的方式使用：`post()`, `put()`, `delete()`, `options()`, `trace()`, `copy()`, `lock()`, `mkcol()`, `move()`, `purge()`, `propfind()`, `proppatch()`, `unlock()`, `report()`, `mkactivity()`, `checkout()`, `merge()`, `m-search()`, `notify()`, `subscribe()`, `unsubscribe()`, `patch()`, `search()`,和`connect()`。

例如，下面的代碼就像上一個`/about`路由一樣，但只響應 HTTP POST 請求。

```js
router.post("/about", function (req, res) {
  res.send("About this wiki");
});
```

### 路由路徑

路由路徑定義可以進行請求的端點。我們到目前為止看到的例子，都是字符串，並且完全按照字符串的寫法使用：'/'，'/ about'，'/ book'，'/any-random.path'。

路由路徑也可以是字符串模式。字符串模式使用正則表達式語法的子集，來定義將匹配的端點模式。下面列出了子集（請注意，連字符（`-`）和點（`.`）由字符串路徑字面解釋）：

* ? :端點在?號前面的那個字符，必須為 0 個或 1 個。例如。`'/ab?cd'`的路徑路徑將匹配端點`acd` 或`abcd`。
* + :端點在+號前面的那個字符，必須為 1 個或多個。例如，`'/ab+cd'`的路徑路徑將與端點`abcd`，`abbcd`，`abbbcd`等匹配。
* \* :端點在放置\*字符的地方，可以代換為任意字符串。例如。`'ab\*cd'`的路由路徑，將匹配端點`abcd`, `abXcd`, `abSOMErandomTEXTcd`等。
* () :將一組字符進行匹配，以執行上面三個操作。例如。`'/ab(cd)?e'`，表示以？號對（cd）進行匹配-它會匹配`abe`和`abcde`。（譯註：即（cd）必須為 0 個或 1 個。若為 0，匹配`abe`。若為 1，匹配`abcde`）

路由路徑也可以是JavaScript 正則表達式。例如，下面的路由路徑將與鯰魚`catfish` 和角鯊魚`dogfish`相匹配，但不包括鯰魚`catflap`、鯰魚頭`catfishhead`等。請注意，正則表達式的路徑使用正則表達式語法（它不像以前那樣，是帶引號的字符串）。

```js
app.get(/.\*fish$/, function (req, res) {
  ...
})
```

**備註：** LocalLibrary 的大部分路由，都只使用字符串，而不是字符串模式和正則表達式。我們還將使用下一節中討論的路由參數。

### 路由參數

路徑參數是命名的 URL 段，用於捕獲在 URL 中的位置指定的值。命名段以冒號為前綴，然後是名稱（例如。捕獲的值，使用參數名稱作為鍵，存在對像中（例如）。`/:your_parameter_name/` `req.params` `req.params.your_parameter_name`

例如，考慮一個編碼的 URL，其中包含有關用戶和書本的信息：`http://localhost:3000/users/34/books/8989`。我們可以使用`userId`和`bookId`路徑參數，提取如下所示的信息：
```

app.get('/users/:userId/books/:bookId', function (req, res) {
  // Access userId via: req.params.userId
  // Access bookId via: req.params.bookId
  res.send(req.params);
})
```

路由參數的名稱，必須由「單詞字符」（AZ，az，0-9 和\_）組成。

**備註：** URL */book/create*將與`/book/:bookId` 之類的路由匹配（它將提取要創建' `create`'的「bookId」值）。將使用與傳入 URL 匹配的第一個路由，因此，如果要單獨處理`/book/create`URL，則必須在`/book/:bookId`路由之前，先定義其路由處理程序。

這就是你開始使用路由所需的全部內容-如果需要，你可以在 Express 文檔中找到更多信息：基本路由和路由指南。以下部分顯示了我們如何為 LocalLibrary 設置路由和控制器。

本地圖書館需要的路由
----------

下面列出了我們最終需要用於頁面的 URL，其中 object 被替換為每個模型的名稱（book，bookinstance，genre，author），objects 是對象的複數，id 是默認情況下，為每個 Mongoose 模型實例指定的唯一實例字段（`_id`）。

* `catalog/` — 主頁/索引頁面。
* `catalog/<objects>/`—所有書本，書本實例，種類或作者的列表（例如/ `catalog/books/`, / `catalog/genres/`等）
* `catalog/<object>/<id>`—具有給定`_id`字段值的特定書本，書本實例，種類或作者的詳細信息頁面（例如`/catalog/book/584493c1f4887f06c0e67d37`）。
* `catalog/<object>/create`—用於創建新的書本，書本實例，種類或作者的表單（例如`/catalog/book/create`）。
* `catalog/<object>/<id>/update`—使用給定的`_id`字段值更新特定書本，書本實例，種類或作者的表單（例如`/catalog/book/584493c1f4887f06c0e67d37/update`）。
* `catalog/<object>/<id>/delete`—刪除具有給定`_id`字段值的特定書本，書本實例，種類或作者的表單（例如`/catalog/book/584493c1f4887f06c0e67d37/delete`）。

第一個主頁和列表頁面，不編碼任何其他信息。雖然返回的結果，將取決於模型類型和數據庫中的內容，但為了獲取信息所運行的查詢，將始終相同（類似地，用於創建對象的代碼將始終類似）。相反的，其他 URL 用於處理特定文檔/模型實例—這些將項目的標識編碼在 URL 中（如上面的`<id>`）。

我們將使用路徑參數，來提取編碼信息，並將其傳遞給路由處理程序（在稍後的文章中，我們將使用它來動態確定從數據庫獲取的信息）。通過對我們的 URL 中的信息進行編碼，我們只需要一個路由，用於特定類型的每個資源（例如，一個路由來處理每個書本項目的顯示）。

**備註：** Express 允許你以任何方式構建 URL -你可以在 URL 正文中編碼信息，就像上面一樣，或使用 URL `GET`參數（例如`/book/?id=6`）。無論你使用哪種方法，URL 都應保持乾淨，合理且可讀（請在此處查看W3C 建議）。

接下來，我們為所有上述 URL，創建路由處理程序回調函數和路由代碼。

創建路由-handler 回調函式
-----------------

在我們定義路由之前，我們將首先創建它們將調用的所有虛擬/骨架回調函數。回調將存在 Books，BookInstances，Genres 和 Authors 的單獨「控制器」 模塊中（你可以使用任何文件/模塊結構，但這似乎是該項目的適當粒度）。

首先在項目根目錄（**/controllers**）中，為我們的控制器創建一個文件夾，然後創建單獨的控制器文件/模塊，來處理每個模型：
```

/express-locallibrary-tutorial  //the project root
  /controllers
    authorController.js
    bookController.js
    bookinstanceController.js
    genreController.js
```

### 作者控制器

打開 **/controllers/authorController.js** 文件，並複制以下代碼：

```js
var Author = require("../models/author");

// Display list of all Authors.
exports.author\_list = function (req, res) {
  res.send("NOT IMPLEMENTED: Author list");
};

// Display detail page for a specific Author.
exports.author\_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: Author detail: " + req.params.id);
};

// Display Author create form on GET.
exports.author\_create\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Author create GET");
};

// Handle Author create on POST.
exports.author\_create\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Author create POST");
};

// Display Author delete form on GET.
exports.author\_delete\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Author delete GET");
};

// Handle Author delete on POST.
exports.author\_delete\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Author delete POST");
};

// Display Author update form on GET.
exports.author\_update\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Author update GET");
};

// Handle Author update on POST.
exports.author\_update\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Author update POST");
};
```

該模塊首先導入我們稍後將使用的模型，來訪問和更新我們的數據。然後它為我們希望處理的每個 URL，導出函數（創建，更新和刪除操作使用表單，因此還有其他方法，來處理表單發布請求——我們將在稍後的「表單文章」中討論這些方法） 。

所有函數都具有 Express 中間件函數的標準形式，如果方法沒有完成請求週期，則會調用請求，響應和 `next` 下一個函數的參數（在所有這些情況下，它都會執行！）。這些方法只返回一個字符串，表明尚未創建關聯的頁面。如果期望控制器函數接收路徑參數，則在消息字符串中，輸出這些參數（參見上面的 `req.params.id`）。

#### 書本實例控制器

打開 **/controllers/bookinstanceController.js** 文件，並將其複製到以下代碼中（它遵循與 `Author` 控制器模塊相同的模式）：

```js
var BookInstance = require("../models/bookinstance");

// Display list of all BookInstances.
exports.bookinstance\_list = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance list");
};

// Display detail page for a specific BookInstance.
exports.bookinstance\_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance detail: " + req.params.id);
};

// Display BookInstance create form on GET.
exports.bookinstance\_create\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance create GET");
};

// Handle BookInstance create on POST.
exports.bookinstance\_create\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance create POST");
};

// Display BookInstance delete form on GET.
exports.bookinstance\_delete\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance delete GET");
};

// Handle BookInstance delete on POST.
exports.bookinstance\_delete\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance delete POST");
};

// Display BookInstance update form on GET.
exports.bookinstance\_update\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance update GET");
};

// Handle bookinstance update on POST.
exports.bookinstance\_update\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance update POST");
};
```

#### 種類控制器

打開 **/controllers/genreController.js** 文件，並複制以下文本（這與 `Author` 和 `BookInstance` 文件的模式相同）：

```js
var Genre = require("../models/genre");

// Display list of all Genre.
exports.genre\_list = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre list");
};

// Display detail page for a specific Genre.
exports.genre\_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre detail: " + req.params.id);
};

// Display Genre create form on GET.
exports.genre\_create\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre create GET");
};

// Handle Genre create on POST.
exports.genre\_create\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre create POST");
};

// Display Genre delete form on GET.
exports.genre\_delete\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre delete GET");
};

// Handle Genre delete on POST.
exports.genre\_delete\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre delete POST");
};

// Display Genre update form on GET.
exports.genre\_update\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre update GET");
};

// Handle Genre update on POST.
exports.genre\_update\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre update POST");
};
```

#### 書本控制器

打開 **/controllers/bookController.js** 文件，並複制以下代碼。它遵循與其他控制器模塊相同的模式，但另外還有一個 `index()` 函數，用於顯示站點歡迎頁面：

```js
var Book = require("../models/book");

exports.index = function (req, res) {
  res.send("NOT IMPLEMENTED: Site Home Page");
};

// Display list of all books.
exports.book\_list = function (req, res) {
  res.send("NOT IMPLEMENTED: Book list");
};

// Display detail page for a specific book.
exports.book\_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: Book detail: " + req.params.id);
};

// Display book create form on GET.
exports.book\_create\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Book create GET");
};

// Handle book create on POST.
exports.book\_create\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Book create POST");
};

// Display book delete form on GET.
exports.book\_delete\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Book delete GET");
};

// Handle book delete on POST.
exports.book\_delete\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Book delete POST");
};

// Display book update form on GET.
exports.book\_update\_get = function (req, res) {
  res.send("NOT IMPLEMENTED: Book update GET");
};

// Handle book update on POST.
exports.book\_update\_post = function (req, res) {
  res.send("NOT IMPLEMENTED: Book update POST");
};
```

創建目錄路由模組
--------

接下來，我們為 LocalLibrary 網站，創建所需全部 URL 的路由，這將調用我們在上一節中定義的控制器功能。

骨架網站已經有一個 **./routes** 文件夾，其中包含索引和用戶的路由。在此文件夾中，創建另一個路徑文件——**catalog.js**——如下圖所示。
```

/express-locallibrary-tutorial //the project root
  /routes
    index.js
    users.js
    catalog.js
```

打開 **/routes/catalog.js**，複製下面的代碼：

```js
var express = require("express");
var router = express.Router();

// Require controller modules.
var book_controller = require("../controllers/bookController");
var author_controller = require("../controllers/authorController");
var genre_controller = require("../controllers/genreController");
var book_instance_controller = require("../controllers/bookinstanceController");

/// BOOK ROUTES ///

// GET catalog home page.
router.get("/", book_controller.index);

// GET request for creating a Book. NOTE This must come before routes that display Book (uses id).
router.get("/book/create", book_controller.book_create_get);

// POST request for creating Book.
router.post("/book/create", book_controller.book_create_post);

// GET request to delete Book.
router.get("/book/:id/delete", book_controller.book_delete_get);

// POST request to delete Book.
router.post("/book/:id/delete", book_controller.book_delete_post);

// GET request to update Book.
router.get("/book/:id/update", book_controller.book_update_get);

// POST request to update Book.
router.post("/book/:id/update", book_controller.book_update_post);

// GET request for one Book.
router.get("/book/:id", book_controller.book_detail);

// GET request for list of all Book items.
router.get("/books", book_controller.book_list);

/// AUTHOR ROUTES ///

// GET request for creating Author. NOTE This must come before route for id (i.e. display author).
router.get("/author/create", author_controller.author_create_get);

// POST request for creating Author.
router.post("/author/create", author_controller.author_create_post);

// GET request to delete Author.
router.get("/author/:id/delete", author_controller.author_delete_get);

// POST request to delete Author.
router.post("/author/:id/delete", author_controller.author_delete_post);

// GET request to update Author.
router.get("/author/:id/update", author_controller.author_update_get);

// POST request to update Author.
router.post("/author/:id/update", author_controller.author_update_post);

// GET request for one Author.
router.get("/author/:id", author_controller.author_detail);

// GET request for list of all Authors.
router.get("/authors", author_controller.author_list);

/// GENRE ROUTES ///

// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get("/genre/create", genre_controller.genre_create_get);

//POST request for creating Genre.
router.post("/genre/create", genre_controller.genre_create_post);

// GET request to delete Genre.
router.get("/genre/:id/delete", genre_controller.genre_delete_get);

// POST request to delete Genre.
router.post("/genre/:id/delete", genre_controller.genre_delete_post);

// GET request to update Genre.
router.get("/genre/:id/update", genre_controller.genre_update_get);

// POST request to update Genre.
router.post("/genre/:id/update", genre_controller.genre_update_post);

// GET request for one Genre.
router.get("/genre/:id", genre_controller.genre_detail);

// GET request for list of all Genre.
router.get("/genres", genre_controller.genre_list);

/// BOOKINSTANCE ROUTES ///

// GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id).
router.get(
  "/bookinstance/create",
  book_instance_controller.bookinstance_create_get,
);

// POST request for creating BookInstance.
router.post(
  "/bookinstance/create",
  book_instance_controller.bookinstance_create_post,
);

// GET request to delete BookInstance.
router.get(
  "/bookinstance/:id/delete",
  book_instance_controller.bookinstance_delete_get,
);

// POST request to delete BookInstance.
router.post(
  "/bookinstance/:id/delete",
  book_instance_controller.bookinstance_delete_post,
);

// GET request to update BookInstance.
router.get(
  "/bookinstance/:id/update",
  book_instance_controller.bookinstance_update_get,
);

// POST request to update BookInstance.
router.post(
  "/bookinstance/:id/update",
  book_instance_controller.bookinstance_update_post,
);

// GET request for one BookInstance.
router.get("/bookinstance/:id", book_instance_controller.bookinstance_detail);

// GET request for list of all BookInstance.
router.get("/bookinstances", book_instance_controller.bookinstance_list);

module.exports = router;
```

該模塊導入 Express，然後使用它來創建一個 `Router` 對象。路由都在路由器上設置完成，然後導出。

路由是使用路由器對像上的 `.get()` 或 `.post()` 方法定義的。所有路徑都是使用字符串定義的（我們不使用字符串模式或正則表達式）。作用於某些特定資源（如書籍）的路由，則使用路徑參數從 URL 中獲取對象標識 id。

處理程序函數，都是從我們在上一節中，創建的控制器模塊導入的。

### 更新 index 路由模組

我們已經設置了所有新路由，但我們仍然有一個到原始頁面的路由。讓我們將其重定向，到我們在路徑『/catalog』創建的新索引頁面。

打開 **/routes/index.js** 並使用下面的函數，替換現有路由。

```js
// GET home page.
router.get("/", function (req, res) {
  res.redirect("/catalog");
});
```

**備註：** 這是我們第一次使用 redirect() 響應方法。這會重定向到指定的頁面，默認情況下會發送 HTTP 狀態代碼「302 Found」。你可以根據需要，更改返回的狀態代碼，並提供絕對路徑或相對路徑。

### 更新 app.js

最後一步，是將路由，添加到中間件鏈。我們在 `app.js` 這樣做。

打開 **app.js**，並要求其他路由下方的目錄路由（添加下面顯示的第三行，在其他兩個路由下面）：

```js
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
var catalogRouter = require("./routes/catalog"); //Import routes for "catalog" area of site
```

接下來，將目錄路由，添加到其他路由下面的中間件堆棧（添加下面顯示的第三行，在其他兩行下面）：

```js
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/catalog", catalogRouter); // Add catalog routes to middleware chain.
```

**備註：** 我們已在路徑 `'/catalog'` 中添加了目錄模塊。它預先添加到目錄模塊中定義的所有路徑。例如，要訪問書本列表，URL 將為：`/catalog/books/`。

就是這樣。現在應該為我們最終在 LocalLibrary 網站上支持的所有 URL，啟用路由和框架功能。

### 測試路由

要測試路由，首先使用你通常的方法啟動網站。

* 預設方法

```bash
// Windows
SET DEBUG=express-locallibrary-tutorial:* & npm start

// macOS or Linux
DEBUG=express-locallibrary-tutorial:* npm start
```

* 如果你先前設置了 nodemon，則可以使用：

```bash
// Windows
SET DEBUG=express-locallibrary-tutorial:* & npm run devstart

// macOS or Linux
DEBUG=express-locallibrary-tutorial:* npm run devstart
```

然後瀏覽一些上面的 LocalLibrary URL，並驗證你沒有收到錯誤頁面（HTTP 404）。為方便起見，下面列出了一小組網址：

* `http://localhost:3000/`
* `http://localhost:3000/catalog`
* `http://localhost:3000/catalog/books`
* `http://localhost:3000/catalog/bookinstances/`
* `http://localhost:3000/catalog/authors/`
* `http://localhost:3000/catalog/genres/`
* `http://localhost:3000/catalog/book/5846437593935e2f8c2aa226`
* `http://localhost:3000/catalog/book/create`
總結
--

我們現在為網站創建了所有的路由，在稍後的教程，我們可以將實作完成的代碼，填入到空殼控制器函式。以這樣的方式，我們學到了許多關於 Express 路由的基本信息，以及一些組織路由和控制器的方式。

下一篇文章，我們將使用視圖（模板）和存在模型裡的信息，為網站創建一個合適的歡迎頁面。

參閱
--

* Basic routing (Express docs)
* Routing guide (Express docs)
* 前頁
* Overview: Express Nodejs
* 次頁


使用 moment 做日期格式化 - 學習該如何開發 Web

使用 moment 做日期格式化
================

我們模型的日期預設呈現很難看: *Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)*。在本節中，我們將展示如何更新上一節中的 *書本實例 BookInstance 列表頁面*，以更友好的格式顯示`due_date`欄位字段：December 6th, 2016。

我們將使用的方法，是在我們的`BookInstance`模型中，創建一個返回格式化日期的虛擬屬性。我們將使用 moment 來做實際的格式化，這是一個輕量級 JavaScript 日期庫，用於解析，驗證，操作和格式化日期。

**備註：** 我們可以直接在 Pug 模板中，使用 moment 格式化字符串，或者可以在許多其它地方格式化字符串。使用虛擬屬性，可以使我們獲得格式化的日期，這與我們當前獲取 `due_date` 的方式完全相同。

安裝 moment
---------

在項目的根目錄，輸入下列命令

```bash
npm install moment
```

創建虛擬屬性
------

1. 打開 **./models/bookinstance.js**。
2. 在此頁面最上方，引入 *moment*。

```js
var moment = require("moment");
```

在 url 屬性後面，加入虛擬屬性 `due_back_formatted`。

```js
BookInstanceSchema.virtual("due\_back\_formatted").get(function () {
  return moment(this.due_back).format("MMMM Do, YYYY");
});
```

**備註：** 格式化方法可以使用幾乎任何模式顯示日期。moment文檔中，可以找到表示不同日期組件的語法。

更新視圖
----

打開 **/views/bookinstance\_list.pug** ，然後用 `due_back_formatted` 取代 `due_back` 。

```js
      if val.status!='Available'
        //span (Due: #{val.due\_back} )
        span  (Due: #{val.due_back_formatted} )
```

這就是本章節的全部了。如果你訪問側邊欄的 *All book-instances* ，你應該會看到所有的歸還日期都更吸引人了！

下一步
---

* 回到 Express 教學 5: 呈現圖書館資料
* 繼續教學 5 的下一個部分: Author list page and Genre list page challenge (en-US).


作者詳情頁面 - 學習該如何開發 Web

作者詳情頁面
======

作者細節頁面，需要呈現指定作者 `Author` 的信息，使用 `_id` 欄位的值（自動產生）識別，接著是這個作者 `Author`的所有書本物件 `Book`的列表。

Controller 控制器
--------------

打開 **/controllers/authorController.js**.

在檔案最上方，加入底下幾行，引入 *async* 和 *Book* 模組(作者細節頁面需要它們)。

```js
var async = require("async");
var Book = require("../models/book");
```

找到 exported `author_detail()` 控制器方法，並用底下代碼置換。

```js
// Display detail page for a specific Author.
exports.author\_detail = function (req, res, next) {
  async.parallel(
    {
      author: function (callback) {
        Author.findById(req.params.id).exec(callback);
      },
      authors\_books: function (callback) {
        Book.find({ author: req.params.id }, "title summary").exec(callback);
      },
    },
    function (err, results) {
      if (err) {
        return next(err);
      } // Error in API usage.
      if (results.author == null) {
        // No results.
        var err = new Error("Author not found");
        err.status = 404;
        return next(err);
      }
      // Successful, so render.
      res.render("author\_detail", {
        title: "Author Detail",
        author: results.author,
        author\_books: results.authors_books,
      });
    },
  );
};
```

此處的控制器方法使用 `async.parallel()`，用平行的方式，查詢作者 `Author`和相應的書本實例，並附加上繪製本頁面的回調，如果 2 個要求都成功完成，就運行回調。這個方式，就跟前面的*種類細節頁面*所說明的完全相同。

View 視圖
-------

創建 **/views/author\_detail.pug** ，並複制貼上底下的文字。

```js
extends layout

block content

  h1 Author: #{author.name}
  p #{author.date_of_birth} - #{author.date_of_death}

  div(style='margin-left:20px;margin-top:20px')

    h4 Books

    dl
      each book in author_books
        dt
          a(href=book.url) #{book.title}
        dd #{book.summary}

      else
        p This author has no books.
```

本模板裡的所有事物，都在先前的章節演示過了。

它看起來像是?
-------

運行本應用，並打開瀏覽器訪問 http://localhost:3000/。選擇 *All Authors* 連結，然後選擇一個作者。如果每個東西都設定正確了，你的網站看起來應該會像底下的截圖。

![Author Detail Page - Express Local Library site](/zh-TW/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page/locallibary_express_author_detail.png)

**備註：** 作者的出生與死亡日期的外觀很醜！我們將在本文最後的自我挑戰處理它。

下一步
---

* 回到 Express 教學 5: 呈現圖書館資料
* 繼續教學 5 的最後一個部分: 書本實例詳情頁面與自我挑戰


書本清單頁面 - 學習該如何開發 Web

書本清單頁面
======

接下做我們將實作書本列表頁面。這個頁面需要呈現數據庫中所有書本的列表，包含每本書的作者、標題，標題將成為一個超連結，連到書本詳細內容頁面。

控制器
---

書本列表控制器函數，需要獲取數據庫中所有 `Book`對象的列表，然後將這些對像傳給模板進行呈現。

打開 **/controllers/bookController.js**. 找到導出的 `book_list()`控制器方法，並替換為下面的代碼。

```js
// Display list of all Books.
exports.book\_list = function (req, res, next) {
  Book.find({}, "title author")
    .populate("author")
    .exec(function (err, list\_books) {
      if (err) {
        return next(err);
      }
      //Successful, so render
      res.render("book\_list", { title: "Book List", book\_list: list_books });
    });
};
```

該方法使用模型的`find()`函數，返回所有 `Book` 對象，選擇僅返回標題 `title` 和作者 `author`，因為我們不需要其他字段（它也會返回 `_id` 和虛擬欄位字段）。這裡我們還調用 `Book` 上的 `populate()`，指定作者 `author`欄位字段 — 這將用完整的作者信息，替換儲存的書本作者 id。

成功時，傳遞給查詢的回調，將呈現 **book\_list**(.pug) 模板，將標題 `title` 和`book_list`（包含作者的書本列表）作為變量傳遞。

View 視圖
-------

創建 **/views/book\_list.pug** 並複制底下的文字。

```js
extends layout

block content
  h1= title

  ul
    each book in book_list
      li
        a(href=book.url) #{book.title}
        |  (#{book.author.name})

    else
      li There are no books.
```

這個視圖擴展了 **layout.pug** 基本模板，並覆蓋了名為 '**content**' 的 `block` 區塊 。它顯示我們從控制器傳入的標題 `title`（通過 `render()`方法），然後使用 `each`-`in`-`else` 語法，遍歷 `book_list`變量。為每本圖書創建一個列表項，以顯示書名，並作為書的詳細信息頁面的鏈接，後面跟著作者姓名。如果 `book_list`中沒有書，則執行 `else` 子句，並顯示文字 「沒有書本」 'There are no books'。

**備註：** 我們使用 `book.url`，為每本書提供詳細記錄鏈接（我們已經實現了此路由，但尚未實現此頁面）。這是 `Book` 模型的一個虛擬屬性，它使用模型實例的 `_id` 字段，生成唯一的 URL 路徑。

在這裡，我們感興趣的是，每本書被定義為兩行，第二行使用管道（上面高亮顯示）。這種方法是必要的，因為如果作者姓名位於上一行，那麼它將成為超鏈接的一部分。

它看起來像是?
-------

運行本應用 (參見 測試路由 有相關的命令) ，並打開你的瀏覽器，訪問 http://localhost:3000/。然後選擇所有書本連結 *All books*。如果每樣東西都設定正確了，你的網站看起來應該像底下的截圖。

![Book List Page - Express Local Library site](/zh-TW/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page/new_book_list.png)

下一步
---

* 回到 Express 教學 5: 呈現圖書館資料
* 繼續教學 5 下個部分: 書本實例清單頁面


書本實例清單頁面 - 學習該如何開發 Web

書本實例清單頁面
========

接下來，我們將實作圖書館中所有書本實例 (`BookInstance`) 的列表頁面。這個頁面需要包含與每個 `BookInstance` (鏈接到其詳細信息頁面) 關聯的書本 `Book` 標題，以及 `BookInstance`模型中的其他信息，包含每個副本的狀態，印記和唯一 ID。唯一 ID 的文字，應該鏈接到 `BookInstance` 詳細信息頁面。

Controller 控制器
--------------

`BookInstance`列表控制器函數，需要獲取所有書本實例的列表，填充關聯的書本信息，然後將列表傳遞給模板以進行呈現。

打開 **/controllers/bookinstanceController.js**。找到導出的 `bookinstance_list()`控制器方法，並用以下代碼替換它（更改後的代碼以**粗體**顯示）。

```js
// Display list of all BookInstances.
exports.bookinstance\_list = function (req, res, next) {
  BookInstance.find()
    .populate("book")
    .exec(function (err, list\_bookinstances) {
      if (err) {
        return next(err);
      }
      // Successful, so render
      res.render("bookinstance\_list", {
        title: "Book Instance List",
        bookinstance\_list: list_bookinstances,
      });
    });
};
```

此方法使用模型的`find()`函數，返回所有 `BookInstance`對象。然後它將一個調用，以菊花鏈方式連接到 `populate()`，附加書本 `book`欄位字段，這將使用完整的 `Book`文檔，替換每個 `BookInstance`儲存的書本 ID。

成功時，傳遞給查詢的回調，會呈現 **bookinstance\_list** (.pug)模板，並將標題`title`和書籍實例列表 `bookinstance_list`作為變量傳遞。

View 視圖
-------

創建 **/views/bookinstance\_list.pug** ，並複制貼上底下的文字。

```js
extends layout

block content
  h1= title

  ul
    each val in bookinstance_list
      li
        a(href=val.url) #{val.book.title} : #{val.imprint} -
        if val.status=='Available'
          span.text-success #{val.status}
        else if val.status=='Maintenance'
          span.text-danger #{val.status}
        else
          span.text-warning #{val.status}
        if val.status!='Available'
          span  (Due: #{val.due_back} )

    else
      li There are no book copies in this library.
```

這個視圖與其他視圖非常相似。它擴展了佈局，替換內容區塊，顯示從控制器傳入的標題 `title`，並遍歷 `bookinstance_list` 中的所有書籍副本。對於每個副本，我們都會顯示它的狀態（用顏色編碼），如果書本不可用，則顯示其預期返回日期。這裡引入了一個新功能 — 我們可以在標籤之後使用點符號表示法，來指定一個類別。因此，`span.text-success` 將被編譯為 <`span class="text-success"`> (也可以用 Pug 編寫為 `span(class="text-success"`)。

它看起來像是?
-------

運行本應用，打開瀏覽器訪問 http://localhost:3000/，然後選擇 All book-instances 連結。假如每個東西都設定正確了，你的網站看起來應該像是底下的截圖。

![BookInstance List Page - Express Local Library site](/zh-TW/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page/locallibary_express_bookinstance_list.png)

下一步
---

* 回到 Express 教學 5: 呈現圖書館資料
* 繼續教學 5 下一個部分: 格式化日期 － 使用 moment.


書本詳情頁面 - 學習該如何開發 Web

書本詳情頁面
======

*書本細節頁面*需要呈現一本指定書本(`Book`)的信息, 使用它的 `_id` 字段值(自動產生)做為識別，接著是圖書館中書本實例(`BookInstance`)的信息。無論我們在哪裡呈現一個作者、種類、或書本實例，都應該連結到它的細節頁面。

Controller 控制器
--------------

打開 **/controllers/bookController.js.** ，找到 exported `book_detail()` 控制器方法，用底下的代碼置換。

```js
// Display detail page for a specific book.
exports.book\_detail = function (req, res, next) {
  async.parallel(
    {
      book: function (callback) {
        Book.findById(req.params.id)
          .populate("author")
          .populate("genre")
          .exec(callback);
      },
      book\_instance: function (callback) {
        BookInstance.find({ book: req.params.id }).exec(callback);
      },
    },
    function (err, results) {
      if (err) {
        return next(err);
      }
      if (results.book == null) {
        // No results.
        var err = new Error("Book not found");
        err.status = 404;
        return next(err);
      }
      // Successful, so render.
      res.render("book\_detail", {
        title: "Title",
        book: results.book,
        book\_instances: results.book_instance,
      });
    },
  );
};
```

**備註：** 我們不需要用 require 導入 *async* 和 *BookInstance*，當我們實作主頁面控制器的時候，我們就已經引入這些模組。

此處的控制器方法使用 `async.parallel()`，用平行的方式找到 `Book` 以及它的相應複本 (`BookInstances`) 。這樣的處理方式，就跟上面的 *種類細節頁面* 所說明的完全相同。

View 視圖
-------

創建 **/views/book\_detail.pug** 並加入底下文字。

```js
extends layout

block content
  h1 #{title}: #{book.title}

  p #[strong Author:]
    a(href=book.author.url) #{book.author.name}
  p #[strong Summary:] #{book.summary}
  p #[strong ISBN:] #{book.isbn}
  p #[strong Genre:]
    each val, index in book.genre
      a(href=val.url) #{val.name}
      if index < book.genre.length - 1
        |,

  div(style='margin-left:20px;margin-top:20px')
    h4 Copies

    each val in book_instances
      hr
      if val.status=='Available'
        p.text-success #{val.status}
      else if val.status=='Maintenance'
        p.text-danger #{val.status}
      else
        p.text-warning #{val.status}
      p #[strong Imprint:] #{val.imprint}
      if val.status!='Available'
        p #[strong Due back:] #{val.due_back}
      p #[strong Id:]
        a(href=val.url) #{val._id}

    else
      p There are no copies of this book in the library.
```

在這個模板裡，幾乎每個東西都在先前的章節演示過了。

**備註：** 與該書相關的種類列表，在模板中的實作，如以下代碼。除了最後一本書之外，在與本書相關的每個種類之後，都會添加一個逗號。
```

  p #[strong Genre:]
    each val, index in book.genre
      a(href=val.url) #{val.name}
      if index < book.genre.length - 1
        |,
```

它看起來像是?
-------

運行本應用，並打開瀏覽器訪問 http://localhost:3000/。選擇 *All books* 連結，然後選擇其中一本書。如果每個東西都設定正確了，你的頁面看起來應該像是底下的截圖。

![Book Detail Page - Express Local Library site](/zh-TW/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page/locallibary_express_book_detail.png)

下一步
---

* 回到 Express 教學 5: 呈現圖書館資料
* 繼續教學 5 下一個部分: 作者詳情頁面


本地圖書館基礎模板 - 學習該如何開發 Web

本地圖書館基礎模板
=========

現在我們了解如何使用 Pug 拓展模板，讓我們開始項目，創建一個基礎模板。這個模板會有一個側邊欄，連結到本教程中將要創建的各個頁面（例如，呈現並創建書本、種類、作者等等），以及一個主要內容區域，我們將在每個頁面中進行覆寫。

開啟 **/views/layout.pug** ，並以下列代碼，置換其內容。

```pug
doctype html
html(lang='en')
  head
    title= title
    meta(charset='utf-8')
    meta(name='viewport', content='width=device-width, initial-scale=1')
    link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css')
    script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js')
    script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    div(class='container-fluid')
      div(class='row')
        div(class='col-sm-2')
          block sidebar
            ul(class='sidebar-nav')
              li
                a(href='/catalog') Home
              li
                a(href='/catalog/books') All books
              li
                a(href='/catalog/authors') All authors
              li
                a(href='/catalog/genres') All genres
              li
                a(href='/catalog/bookinstances') All book-instances
              li
                hr
              li
                a(href='/catalog/author/create') Create new author
              li
                a(href='/catalog/genre/create') Create new genre
              li
                a(href='/catalog/book/create') Create new book
              li
                a(href='/catalog/bookinstance/create') Create new book instance (copy)

        div(class='col-sm-10')
          block content
```

此模板使用（並包含）來自 Bootstrap 的 JavaScript 和 CSS ，以改進 HTML 頁面的佈局和呈現方式。使用 Bootstrap 或其它客戶端網頁框架，是一種快速的方式，可以創建吸引人的網頁，能夠良好地適應不同的瀏覽器尺寸，並且允許我們處理頁面的呈現，而不需要糾纒於任何不同尺寸的細節—此處我們只想專注於伺服端代碼！

佈局的安排應該相當明白，假如你已經閱讀了之前的 模板入門。注意，使用 `block content` 當做定位符號，放到頁面內容將要放置的地方。

基礎模板也參考了一個本地 css 檔 (**style.css**) ，此檔提供了一些額外的樣式。打開 **/public/stylesheets/style.css** ，並用底下的 CSS 代碼，取代它的內容:

```css
.sidebar-nav {
  margin-top: 20px;
  padding: 0;
  list-style: none;
}
```

當我們開始運行網站時，我們應該看到側邊欄出現！在本教程的下個部分，我們將使用以上的佈局，來定義各個頁面。

下一步
---

* 回到 Express Tutorial Part 5: Displaying library data.
* 繼續教學 5 下個章節: Home page (en-US).


書本實例詳情頁面與自我挑戰 - 學習該如何開發 Web

書本實例詳情頁面與自我挑戰
=============

書本實例詳情頁面
--------

`BookInstance` 詳情頁面，需要呈現每一個 `BookInstance` 的信息，用 `_id` 欄位字段值（自動產生）做識別。它包含了 `Book` 名稱 (也是一個連結，連到 *書本細節*頁面)，接著是紀錄中的其它的信息。

### Controller 控制器

打開 **/controllers/bookinstanceController.js**. 找到 exported `bookinstance_detail()` 控制器方法，並用底下代碼置換。

```js
// Display detail page for a specific BookInstance.
exports.bookinstance\_detail = function (req, res, next) {
  BookInstance.findById(req.params.id)
    .populate("book")
    .exec(function (err, bookinstance) {
      if (err) {
        return next(err);
      }
      if (bookinstance == null) {
        // No results.
        var err = new Error("Book copy not found");
        err.status = 404;
        return next(err);
      }
      // Successful, so render.
      res.render("bookinstance\_detail", {
        title: "Book:",
        bookinstance: bookinstance,
      });
    });
};
```

該方法使用從 URL（使用路由）中提取的特定書本實例的 ID，調用`BookInstance.findById()`，並通過請求參數（`req.params.id`），在控制器中訪問。然後調用`populate()`來獲取相關 `Book` 的詳細信息。

### View 視圖

創建 **/views/bookinstance\_detail.pug** ，並複製下面的內容。

```js
extends layout

block content

  h1 ID: #{bookinstance._id}

  p #[strong Title:]
    a(href=bookinstance.book.url) #{bookinstance.book.title}
  p #[strong Imprint:] #{bookinstance.imprint}

  p #[strong Status:]
    if bookinstance.status=='Available'
      span.text-success #{bookinstance.status}
    else if bookinstance.status=='Maintenance'
      span.text-danger #{bookinstance.status}
    else
      span.text-warning #{bookinstance.status}

  if bookinstance.status!='Available'
    p #[strong Due back:] #{bookinstance.due_back}
```

本模組中的所有東西，都在先前的章節演示過了。

### 它看起來像是?

運行本應用，並打開瀏覽器訪問 http://localhost:3000//。選擇 *All book-instances* 連結，然後選擇其中一本。如果每個東西都設定正確了，你的網站看起來應該像是底下的截圖。

![BookInstance Detail Page - Express Local Library site](/zh-TW/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge/locallibary_express_bookinstance_detail.png)

自我挑戰
----

目前，我們網站上顯示的大多數日期，都使用默認的 JavaScript 格式（例如 *Tue Dec 06 2016 15:49:58 GMT+1100* （AUS 東部夏令時間））。本文的挑戰，是改善作者`Author`生命週期日期顯示的外觀信息（死亡/誔生日期）和*BookInstance 詳細信息*頁面，使用格式：December 6th, 2016。

**備註：** 你可以使用與我們用於 *Book Instance List* 的相同方法（將生命週期的虛擬屬性，添加到`Author`模型，並使用 moment 來設置日期字符串的格式）。

這個挑戰的要求：

1. 用 BookInstance 詳細信息頁面中的 `due_back_formatted` 替換 `due_back`。
2. 更新作者模塊以添加壽命虛擬屬性。壽命應該有兩個值： *date\_of\_birth - date\_of\_death*，這兩個值的格式與 `BookInstance.due_back_formatted`的日期格式相同。
3. 在當前使用 `date_of_birth` 和 `date_of_death`的所有視圖中，使用 `Author.lifespan` 。
下一步
---

* 回到 Express 教學 5: 呈現圖書館資料