feat: add 'back/front end' in curriculum (#42596)

* chore: rename APIs and Microservices to include "Backend" (#42515)

* fix typo

* fix typo

* undo change

* Corrected grammar mistake

Corrected a grammar mistake by removing a comma.

* change APIs and Microservices cert title

* update title

* Change APIs and Microservices certi title

* Update translations.json

* update title

* feat(curriculum): rename apis and microservices cert

* rename folder structure

* rename certificate

* rename learn Markdown

* apis-and-microservices -> back-end-development-and-apis

* update backend meta

* update i18n langs and cypress test

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>

* fix: add development to front-end libraries (#42512)

* fix: added-the-word-Development-to-front-end-libraries

* fix/added-the-word-Development-to-front-end-libraries

* fix/added-word-development-to-front-end-libraries-in-other-related-files

* fix/added-the-word-Development-to-front-end-and-all-related-files

* fix/removed-typos-from-last-commit-in-index.md

* fix/reverted-changes-that-i-made-to-dependecies

* fix/removed xvfg

* fix/reverted changes that i made to package.json

* remove unwanted changes

* front-end-development-libraries changes

* rename backend certSlug and README

* update i18n folder names and keys

* test: add legacy path redirect tests

This uses serve.json from the client-config repo, since we currently use
that in production

* fix: create public dir before moving serve.json

* fix: add missing script

* refactor: collect redirect tests

* test: convert to cy.location for stricter tests

* rename certificate folder to 00-certificates

* change crowdin config to recognise new certificates location

* allow translations to be used

Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>

* add forwards slashes to path redirects

* fix cypress path tests again

* plese cypress

* fix: test different challenge

Okay so I literally have no idea why this one particular challenge
fails in Cypress Firefox ONLY. Tom and I paired and spun a full build
instance and confirmed in Firefox the page loads and redirects as
expected. Changing to another bootstrap challenge passes Cypress firefox
locally. Absolutely boggled by this.

AAAAAAAAAAAAAAA

* fix: separate the test

Okay apparently the test does not work unless we separate it into
a different `it` statement.

>:( >:( >:( >:(

Co-authored-by: Sujal Gupta <55016909+heysujal@users.noreply.github.com>
Co-authored-by: Noor Fakhry <65724923+NoorFakhry@users.noreply.github.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>
This commit is contained in:
Shaun Hamilton
2021-08-14 03:57:13 +01:00
committed by GitHub
parent 4df2a0c542
commit c2a11ad00d
1215 changed files with 790 additions and 449 deletions

View File

@@ -0,0 +1,255 @@
---
id: 5a8b073d06fa14fcfde687aa
title: Exercise 追蹤器
challengeType: 4
forumTopicId: 301505
dashedName: exercise-tracker
---
# --description--
構建一個 JavaScript 的全棧應用,在功能上與這個應用相似: <https://exercise-tracker.freecodecamp.rocks/>。 可以採用下面的一種方式完成這個挑戰:
- 克隆 [GitHub 倉庫](https://github.com/freeCodeCamp/boilerplate-project-exercisetracker/) 並在本地完成你的項目。
- 使用[我們的 Replit 初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-project-exercisetracker)來完成你的項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。 此外,還可以將項目的源碼提交到 `GitHub Link` 中。
# --hints--
提交自己的項目,而不是示例的 URL。
```js
(getUserInput) => {
const url = getUserInput('url');
assert(
!/.*\/exercise-tracker\.freecodecamp\.rocks/.test(getUserInput('url'))
);
};
```
可以將表單裏的 `username` 通過 `POST` 請求發送到 `/api/users`,以創建一個新的用戶。 返回的響應內容是一個帶有 `username``_id` 的對象
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `username=fcc_test_${Date.now()}`.substr(0, 29)
});
if (res.ok) {
const { _id, username } = await res.json();
assert.exists(_id);
assert.exists(username);
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
可以發送 `GET` 請求到 `/api/users`,以獲取一個所有用戶的數組, 數組裏的每個元素都是一個包含 `username``_id` 的用戶對象。
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/users');
if (res.ok) {
const data = await res.json();
assert.isArray(data);
assert.isString(data[0].username);
assert.isString(data[0]._id);
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
你能用表單裏的 `description``duration``date`(可選)發送 `POST` 請求到 `/api/users/:_id/exercises`。 如果沒有傳入 date默認採用當前日期。 響應內容是包含 exercise 表單內容的 user 對象。
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `username=fcc_test_${Date.now()}`.substr(0, 29)
});
if (res.ok) {
const { _id, username } = await res.json();
const expected = {
username,
description: 'test',
duration: 60,
_id,
date: 'Mon Jan 01 1990'
};
const addRes = await fetch(url + `/api/users/${_id}/exercises`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `description=${expected.description}&duration=${expected.duration}&date=1990-01-01`
});
if (addRes.ok) {
const actual = await addRes.json();
assert.deepEqual(actual, expected);
} else {
throw new Error(`${addRes.status} ${addRes.statusText}`);
}
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
可以發送 `GET` 請求到 `/api/users/:_id/logs`,以獲取任何用戶的完整 exercise 日誌。 響應內容是一個 user 對象,它帶有一個 `log` 屬性,該屬性的值是所有被添加的 exercises 表單記錄組成的數組, 每一個 log 數組裏的元素應該是一個含有 `description``duration``date` 等屬性的對象。
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `username=fcc_test_${Date.now()}`.substr(0, 29)
});
if (res.ok) {
const { _id, username } = await res.json();
const expected = {
username,
description: 'test',
duration: 60,
_id,
date: new Date().toDateString()
};
const addRes = await fetch(url + `/api/users/${_id}/exercises`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `description=${expected.description}&duration=${expected.duration}`
});
if (addRes.ok) {
const logRes = await fetch(url + `/api/users/${_id}/logs`);
if (logRes.ok) {
const { log } = await logRes.json();
assert.isArray(log);
assert.equal(1, log.length);
} else {
throw new Error(`${logRes.status} ${logRes.statusText}`);
}
} else {
throw new Error(`${addRes.status} ${addRes.statusText}`);
}
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
用戶日誌請求(`/api/users/:_id/logs`)返回一個帶有 `count` 屬性的對象,該屬性反映 exercises 表單的成功提交次數(譯者注:即 log 屬性元素的個數)。
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `username=fcc_test_${Date.now()}`.substr(0, 29)
});
if (res.ok) {
const { _id, username } = await res.json();
const expected = {
username,
description: 'test',
duration: 60,
_id,
date: new Date().toDateString()
};
const addRes = await fetch(url + `/api/users/${_id}/exercises`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `description=${expected.description}&duration=${expected.duration}`
});
if (addRes.ok) {
const logRes = await fetch(url + `/api/users/${_id}/logs`);
if (logRes.ok) {
const { count } = await logRes.json();
assert(count);
} else {
throw new Error(`${logRes.status} ${logRes.statusText}`);
}
} else {
throw new Error(`${addRes.status} ${addRes.statusText}`);
}
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
可以把 `from``to``limit` 參數添加到一個 `/api/users/:_id/logs` 請求,以查詢該用戶的部分 exercise 表單提交記錄, `from``to``yyyy-mm-dd` 形式的日期, `limit` 是希望返回的 log 數量。
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `username=fcc_test_${Date.now()}`.substr(0, 29)
});
if (res.ok) {
const { _id, username } = await res.json();
const expected = {
username,
description: 'test',
duration: 60,
_id,
date: new Date().toDateString()
};
const addExerciseRes = await fetch(url + `/api/users/${_id}/exercises`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `description=${expected.description}&duration=${expected.duration}&date=1990-01-01`
});
const addExerciseTwoRes = await fetch(url + `/api/users/${_id}/exercises`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `description=${expected.description}&duration=${expected.duration}&date=1990-01-02`
});
if (addExerciseRes.ok && addExerciseTwoRes.ok) {
const logRes = await fetch(
url + `/api/users/${_id}/logs?from=1989-12-31&to=1990-01-03`
);
if (logRes.ok) {
const { log } = await logRes.json();
assert.isArray(log);
assert.equal(2, log.length);
} else {
throw new Error(`${logRes.status} ${logRes.statusText}`);
}
const limitRes = await fetch(
url + `/api/users/${_id}/logs?limit=1`
);
if (limitRes.ok) {
const { log } = await limitRes.json();
assert.isArray(log);
assert.equal(1, log.length);
} else {
throw new Error(`${limitRes.status} ${limitRes.statusText}`);
}
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,88 @@
---
id: bd7158d8c443edefaeb5bd0f
title: 文件元數據微服務
challengeType: 4
forumTopicId: 301506
dashedName: file-metadata-microservice
---
# --description--
構建一個 JavaScript 的全棧應用,在功能上與這個應用相似:<https://file-metadata-microservice.freecodecamp.rocks/>。 可以採用下面的一種方式完成這個挑戰:
- 克隆 [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-filemetadata/) 並在本地完成項目。
- 使用[我們的 Replit 初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-project-filemetadata)來完成你的項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。 此外,還可以將項目的源碼提交到 `GitHub Link` 中。
# --instructions--
** 提示:** 可以使用 `multer` npm 包來處理上傳文件
# --hints--
提交自己的項目,而不是示例的 URL。
```js
(getUserInput) => {
assert(
!/.*\/file-metadata-microservice\.freecodecamp\.rocks/.test(
getUserInput('url')
)
);
};
```
可以提交一個包含上傳文件的表單。
```js
async (getUserInput) => {
const site = await fetch(getUserInput('url'));
const data = await site.text();
const doc = new DOMParser().parseFromString(data, 'text/html');
assert(doc.querySelector('input[type="file"]'));
};
```
表單的文件上傳標籤的 `name` 屬性設置成 `upfile`
```js
async (getUserInput) => {
const site = await fetch(getUserInput('url'));
const data = await site.text();
const doc = new DOMParser().parseFromString(data, 'text/html');
assert(doc.querySelector('input[name="upfile"]'));
};
```
當提交一個文件時,在 JSON 響應中收到文件的 `name``type``size`(以 bytes字節爲單位
```js
async (getUserInput) => {
const formData = new FormData();
const fileData = await fetch(
'https://cdn.freecodecamp.org/weather-icons/01d.png'
);
const file = await fileData.blob();
formData.append('upfile', file, 'icon');
const data = await fetch(getUserInput('url') + '/api/fileanalyse', {
method: 'POST',
body: formData
});
const parsed = await data.json();
assert.property(parsed, 'size');
assert.equal(parsed.name, 'icon');
assert.equal(parsed.type, 'image/png');
};
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,77 @@
---
id: bd7158d8c443edefaeb5bdff
title: 請求頭解析器微服務
challengeType: 4
forumTopicId: 301507
dashedName: request-header-parser-microservice
---
# --description--
構建一個 JavaScript 的全棧應用,在功能上與這個應用相似:<https://request-header-parser-microservice.freecodecamp.rocks/>。 可以採用下面的一種方式完成這個挑戰:
- 克隆 [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-headerparser/) 並在本地完成項目。
- 使用[我們的 Replit 初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-project-headerparser)來完成你的項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。 此外,還可以將項目的源碼提交到 `GitHub Link` 中。
# --hints--
提交自己的項目,而不是示例的 URL。
```js
(getUserInput) => {
assert(
!/.*\/request-header-parser-microservice\.freecodecamp\.rocks/.test(
getUserInput('url')
)
);
};
```
`/api/whoami` 發送請求,返回一個 JSON 對象這個JSON 對象應該含有存放 IP 地址的 `ipaddress` 鍵中。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/whoami').then(
(data) => assert(data.ipaddress && data.ipaddress.length > 0),
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
`/api/whoami` 發送請求,返回一個 JSON 對象,這個 JSON 對象應該含有存放語言首選項的 `language` 鍵。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/whoami').then(
(data) => assert(data.language && data.language.length > 0),
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
`/api/whoami` 發送請求,返回一個 JSON 對象,這個 JSON 對象應該含有存放(發送請求的)軟件的 `software` 鍵。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/whoami').then(
(data) => assert(data.software && data.software.length > 0),
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,154 @@
---
id: bd7158d8c443edefaeb5bdef
title: 時間戳微服務
challengeType: 4
forumTopicId: 301508
dashedName: timestamp-microservice
---
# --description--
構建一個 JavaScript 的全棧應用,在功能上與這個應用相似:<https://timestamp-microservice.freecodecamp.rocks/>。 可以採用下面的任意一種方式完成這個挑戰:
- 克隆 [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-timestamp/) 並在本地完成項目。
- 使用[我們的 Replit 初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-project-timestamp)來完成你的項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。 此外,還可以將項目的源碼提交到 `GitHub Link` 中。
# --hints--
提交自己的項目,而不是示例的 URL。
```js
(getUserInput) => {
assert(
!/.*\/timestamp-microservice\.freecodecamp\.rocks/.test(getUserInput('url'))
);
};
```
`/api/:date?` 發送一個帶有有效日期的請求,應該很快(在幾毫秒內)返回一個 JSON 對象,在這個 JSON 對象內有一個包含輸入日期的 Unix 時間戳的 `unix` 鍵。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/2016-12-25').then(
(data) => {
assert.equal(
data.unix,
1482624000000,
'Should be a valid unix timestamp'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
`/api/:date?` 發送一個帶有有效日期的請求,應該返回一個 JSON 對象,在這個 JSON 對象內有一個包含如 `Thu, 01 Jan 1970 00:00:00 GMT` 格式的輸入日期的 `utc` 鍵。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/2016-12-25').then(
(data) => {
assert.equal(
data.utc,
'Sun, 25 Dec 2016 00:00:00 GMT',
'Should be a valid UTC date string'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
`/api/1451001600000` 發送請求,應該返回 `{ unix: 1451001600000, utc: "Fri, 25 Dec 2015 00:00:00 GMT" }`
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/1451001600000').then(
(data) => {
assert(
data.unix === 1451001600000 &&
data.utc === 'Fri, 25 Dec 2015 00:00:00 GMT'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
程序能成功處理能被 `new Date(date_string)` 解析的日期。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/05 October 2011').then(
(data) => {
assert(
data.unix === 1317772800000 &&
data.utc === 'Wed, 05 Oct 2011 00:00:00 GMT'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
如果傳入的日期是無效的,將返回一個帶有結構體 `{ error : "Invalid Date" }` 的對象。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api/this-is-not-a-date').then(
(data) => {
assert.equal(data.error.toLowerCase(), 'invalid date');
},
(xhr) => {
assert(xhr.responseJSON.error.toLowerCase() === 'invalid date');
}
);
```
如果傳入的參數是空日期,將返回一個包含當前時間的 `unix` 鍵的 JSON 對象。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api').then(
(data) => {
var now = Date.now();
assert.approximately(data.unix, now, 20000);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
如果傳入的參數是空日期,將返回一個包含當前時間的 `utc` 鍵的 JSON 對象。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/api').then(
(data) => {
var now = Date.now();
var serverTime = new Date(data.utc).getTime();
assert.approximately(serverTime, now, 20000);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,119 @@
---
id: bd7158d8c443edefaeb5bd0e
title: 短網址微服務
challengeType: 4
forumTopicId: 301509
dashedName: url-shortener-microservice
---
# --description--
構建一個 JavaScript 的全棧應用,在功能上與這個應用相似:<https://url-shortener-microservice.freecodecamp.rocks/>。 可以採用下面的任意一種方式完成這個挑戰:
- 克隆 [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-filemetadata/) 並在本地完成項目。
- 使用[我們的 Replit 初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-project-urlshortener)來完成你的項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。 此外,還可以將項目的源碼提交到 `GitHub Link` 中。
# --instructions--
**提示:** 請使用 body parsing 中間件來處理 POST 請求, 也可以使用 `dns` 核心模塊中的 `dns.lookup(host, cb)` 函數驗證提交的 URL。
# --hints--
提交自己的項目,而不是示例的 URL。
```js
(getUserInput) => {
assert(
!/.*\/url-shortener-microservice\.freecodecamp\.rocks/.test(
getUserInput('url')
)
);
};
```
可以通過 POST 請求給 `/api/shorturl` 發送一個 URL並返回一個帶有 `original_url``short_url` 屬性的 JSON 響應。 例如:`{ original_url : 'https://freeCodeCamp.org', short_url : 1}`
```js
async (getUserInput) => {
const url = getUserInput('url');
const urlVariable = Date.now();
const fullUrl = `${url}/?v=${urlVariable}`
const res = await fetch(url + '/api/shorturl', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `url=${fullUrl}`
});
if (res.ok) {
const { short_url, original_url } = await res.json();
assert.isNotNull(short_url);
assert.strictEqual(original_url, `${url}/?v=${urlVariable}`);
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
當訪問 `/api/shorturl/<short_url>` 時, 將重定向到原來的 URL。
```js
async (getUserInput) => {
const url = getUserInput('url');
const urlVariable = Date.now();
const fullUrl = `${url}/?v=${urlVariable}`
let shortenedUrlVariable;
const postResponse = await fetch(url + '/api/shorturl', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `url=${fullUrl}`
});
if (postResponse.ok) {
const { short_url } = await postResponse.json();
shortenedUrlVariable = short_url;
} else {
throw new Error(`${postResponse.status} ${postResponse.statusText}`);
}
const getResponse = await fetch(
url + '/api/shorturl/' + shortenedUrlVariable
);
if (getResponse) {
const { redirected, url } = getResponse;
assert.isTrue(redirected);
assert.strictEqual(url,fullUrl);
} else {
throw new Error(`${getResponse.status} ${getResponse.statusText}`);
}
};
```
如果傳入一個沒有遵循如 `http://www.example.com` 的無效 URL則返回包含 `{ error: 'invalid url' }` 的 JSON 響應。
```js
async (getUserInput) => {
const url = getUserInput('url');
const res = await fetch(url + '/api/shorturl', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `url=ftp:/john-doe.org`
});
if (res.ok) {
const { error } = await res.json();
assert.isNotNull(error);
assert.strictEqual(error.toLowerCase(), 'invalid url');
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
};
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,79 @@
---
id: 587d7fb1367417b2b2512bf4
title: 通過鏈式調用中間件來創建時間服務
challengeType: 2
forumTopicId: 301510
dashedName: chain-middleware-to-create-a-time-server
---
# --description--
使用 `app.METHOD(path, middlewareFunction)` 可以在指定的路由掛載中間件, 也可以在路由定義中鏈式調用中間件。
請看以下示例:
```js
app.get('/user', function(req, res, next) {
req.user = getTheUserSync(); // Hypothetical synchronous operation
next();
}, function(req, res) {
res.send(req.user);
});
```
此方法可用於將服務操作拆分爲較小的單元, 這可以讓應用擁有更好的結構,也便於在不同的位置上覆用代碼; 此方法還可用於對數據執行某些驗證。 可以在每一箇中間件堆棧中,阻止當前鏈的執行,並將控制權傳遞給專門設計用於處理錯誤的函數; 或者可以將控制權傳遞給下一個匹配的路由,以處理特殊情況, 我們將在高級 Express 章節中看到這些內容。
# --instructions--
在路由 `app.get('/now', ...)` 中鏈式調用中間件函數,並在最後處理。 在中間件函數中給請求對象中的 `req.time` 添加到當前時間, 可以使用 `new Date().toString()` 在處理函數中,使用 `{time: req.time}` 結構的 JSON 對象來響應請求。
**注意:** 如果不鏈式調用中間件,測試將不能通過。 如果將中間件函數掛載在其他地方,即使輸出結果正確,測試也會失敗。
# --hints--
/now 接口應該已經掛載了中間件
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/chain-middleware-time').then(
(data) => {
assert.equal(
data.stackLength,
2,
'"/now" route has no mounted middleware'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
/now 接口應該返回一個現在時間 +/-20 秒的時間
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/chain-middleware-time').then(
(data) => {
var now = new Date();
assert.isAtMost(
Math.abs(new Date(data.time) - now),
20000,
'the returned time is not between +- 20 secs from now'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,78 @@
---
id: 587d7fb2367417b2b2512bf8
title: 從 POST 請求中獲取數據
challengeType: 2
forumTopicId: 301511
dashedName: get-data-from-post-requests
---
# --description--
在路徑 `/name` 掛載一個 POST 處理方法, 和前面一樣, 我們已經在 html 首頁準備了一份表單, 它將提交與練習 10 相同的數據(查詢字符串), 如果 body-parser 正確配置好了,那麼就可以在 `req.body` 對象中找到請求的參數。 來看看一個常規的例子:
<blockquote>路由POST '/library'<br>URL 編碼的請求正文userId=546&#x26;bookId=6754<br>req.body{userId: '546', bookId: '6754'}</blockquote>
響應和前面一樣的 JSON 對象 `{name: 'firstname lastname'}`。 你可以使用首頁應用提供的 html 表單,來測試你的 API 是否正常工作。
提示:除了 GET 和 POST還有其他幾種 http 方法。 按照慣例http 動詞和在服務端執行的某種操作之間有對應關係, 這種對應關係通常如下:
POST有時候是 PUT- 使用請求發送信息,以創建新資源;
GET- 讀取不用修改的已存在的資源;
PUT 或者 PATCH有時候是 POST- 發送數據,以更新資源;
DELETE=> 刪除一個資源。
還有其他兩種方法常用於與服務進行交互。 除了 GET 之外,上面列出的所有方法都可以負載數據(即數據都能放到消息正文中), 這些方法也可以使用 body-parser 中間件。
# --hints--
測試 1你的 API 接口應該使用正確的名字來響應
```js
(getUserInput) =>
$.post(getUserInput('url') + '/name', { first: 'Mick', last: 'Jagger' }).then(
(data) => {
assert.equal(
data.name,
'Mick Jagger',
'Test 1: "POST /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
測試 2你的 API 接口應該使用正確的名字來響應
```js
(getUserInput) =>
$.post(getUserInput('url') + '/name', {
first: 'Keith',
last: 'Richards'
}).then(
(data) => {
assert.equal(
data.name,
'Keith Richards',
'Test 2: "POST /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,67 @@
---
id: 587d7fb2367417b2b2512bf6
title: 從客戶端獲取輸入的查詢參數
challengeType: 2
forumTopicId: 301512
dashedName: get-query-parameter-input-from-the-client
---
# --description--
從客戶端獲取輸入的另一種常見方式是使用查詢字符串對路由路徑中的數據進行編碼, 查詢字符串使用標記(?)分隔,並且包含鍵值對 field=value 每對鍵值使用連字號(&)分隔。 Express 能夠從查詢字符串中解析這些數據,並且把它放到 `req.query` 對象中。 有些字符(如百分號(%))不能在出現在 URL 中,它們在發送前必須以不同的格式進行編碼。 如果使用 JavaScript 的 API可以用特定的方法來編碼/解碼這些字符。
<blockquote>路由地址:'/library'<br> 實際請求 URL'/library?userId=546&#x26;bookId=6754'<br>req.query{userId: '546', bookId: '6754'}</blockquote>
# --instructions--
構建一個 API 接口,使用路由掛載在 `GET /name` 上, 使用一個 JSON 文件來響應,它的結構是這樣的:`{ name: 'firstname lastname'}` 名字first name和姓氏last name參數應該編碼在查詢參數中例如`?first=firstname&last=lastname`
**注意:** 在後面的練習中,我們將向相同的路由路徑 `/name` 發送 POST 請求來接收數據。 如果願意,可以使用`app.route(path).get(handler).post(handler)`這中寫法, 這種語法允許在同一路徑路由上鍊式調用不同的請求方法, 可以節省一點打字時間,也可以讓代碼看起來更清晰。
# --hints--
測試 1你的 API 應該用正確的名字來響應
```js
(getUserInput) =>
$.get(getUserInput('url') + '/name?first=Mick&last=Jagger').then(
(data) => {
assert.equal(
data.name,
'Mick Jagger',
'Test 1: "GET /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
測試 2你的 API 應該用正確的名字來響應
```js
(getUserInput) =>
$.get(getUserInput('url') + '/name?last=Richards&first=Keith').then(
(data) => {
assert.equal(
data.name,
'Keith Richards',
'Test 2: "GET /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,65 @@
---
id: 587d7fb2367417b2b2512bf5
title: 從客戶端獲取輸入的路由參數
challengeType: 2
forumTopicId: 301513
dashedName: get-route-parameter-input-from-the-client
---
# --description--
在構建 API 時,要讓用戶告訴我們他們想從服務中獲取什麼。 舉個例子,如果客戶請求數據庫中存儲的用戶信息,他們需要一種方法讓我們知道他們對哪個用戶感興趣, 使用路由參數可以實現這個需求。 路由參數是由斜槓(/)分隔的 URL 命名段, 每一小段能捕獲與其位置匹配的 URL 部分的值, 捕獲的值能夠在 `req.params` 對象中找到。
<blockquote>路由地址:'/user/:userId/book/:bookId'<br> 實際請求 URL'/user/546/book/6754'<br> req.params{userId: '546', bookId: '6754'}</blockquote>
# --instructions--
在路由 `GET /:word/echo` 中構建一個響應服務, 響應一個採用 `{echo: word}` 結構的 JSON 對象。 可以在 `req.params.word` 中找到要重複的單詞, 可以在瀏覽器的地址欄測試你的路由,訪問一些匹配的路由,比如:`your-app-rootpath/freecodecamp/echo`
# --hints--
測試 1你的 echo 服務應該正確地重複單詞
```js
(getUserInput) =>
$.get(getUserInput('url') + '/eChOtEsT/echo').then(
(data) => {
assert.equal(
data.echo,
'eChOtEsT',
'Test 1: the echo server is not working as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
測試 2你的 echo 服務應該正確地重複單詞
```js
(getUserInput) =>
$.get(getUserInput('url') + '/ech0-t3st/echo').then(
(data) => {
assert.equal(
data.echo,
'ech0-t3st',
'Test 2: the echo server is not working as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,57 @@
---
id: 587d7fb1367417b2b2512bf3
title: 實現一個根級的請求記錄中間件
challengeType: 2
forumTopicId: 301514
dashedName: implement-a-root-level-request-logger-middleware
---
# --description--
前面我們介紹了 `express.static()` 中間件函數, 現在是時候更詳細地瞭解什麼是中間件了。 中間件函數是一個接收 3 個參數的函數,這 3 個參數分別是:請求對象、響應對象和在應用的請求-響應循環中的下一個函數。 中間件函數執行一些可能對應用程序產生一些效果的代碼,通常還會在請求對象或者響應對象裏添加一些信息, 它們也可以在滿足某些條件時通過發送響應來結束循環, 如果在它們完成時沒有發送響應,那麼就會開始執行堆棧中的下一個函數, `next()` 將觸發調用第 3 個參數。
看看下面的例子:
```js
function(req, res, next) {
console.log("I'm a middleware...");
next();
}
```
假設在某個路由上安裝了這個中間件函數, 當一個請求與路由匹配時它會顯示字符串“Im a middleware…”然後它執行堆棧中的下一個函數。 在這個練習中,我們將構建根級中間件。 正如我們在挑戰 4 中看到的,要在根層級安裝中間件函數,我們可以使用 `app.use(<mware-function>)` 方法。 在這種情況下,該函數將對所有請求執行,但也可以設置更具體的條件來執行, 例如,如果你希望某個函數只針對 POST 請求執行,可以使用 `app.post(<mware-function>)` 方法。 所有的 HTTP 動詞GET、DELETE、PUT……都存在類似的方法。
# --instructions--
構建一個簡單的日誌記錄器。 對於每個請求,它應該在控制檯中打印一個採用以下格式的字符串:`method path - ip` 例如:`GET /json - ::ffff:127.0.0.1`。 注意 `method``path` 之間有一個空格,並且 `path``ip` 中間的破折號的兩邊都有空格。 可以使用 `req.method``req.path``req.ip` 從請求對象中分別獲取請求方法http 動詞)、路由相對路徑和請求者的 ip 信息。 當你完成時,記得要調用 `next()`,否則服務器將一直處於掛起狀態。 請確保“Logs”是打開的觀察一下當一些請求到達時會發生什麼事情。
**注意:**Express 按照函數在代碼中出現的順序來執行, 中間件也是如此。 如果你想讓中間件函數適用於所有路由,那麼應該在路由之前配置好中間件。
# --hints--
應該激活根級記錄器中間件
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/root-middleware-logger').then(
(data) => {
assert.isTrue(
data.passed,
'root-level logger is not working as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,53 @@
---
id: 587d7fb0367417b2b2512bed
title: 認識 Node 的控制檯
challengeType: 2
forumTopicId: 301515
dashedName: meet-the-node-console
---
# --description--
可以採用下面的任意一種方式完成這些挑戰:
- 克隆 [這個 GitHub 倉庫](https://github.com/freeCodeCamp/boilerplate-express/) 並在本地完成項目。
- 使用[我們的 Repl.it 上的初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-express)來完成項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。
在開發過程中,能夠隨時看到代碼的運行結果是非常重要的。
Node 只是一個 JavaScript 環境。 與客戶端 JavaScript 一樣,你可以使用控制檯顯示有用的調試信息。 在本地計算機上,你可以在終端中輸出調試信息。 在 Replit 上,右側邊欄會默認打開一個終端。
我們建議在做這些挑戰題時保持終端打開的狀態。 通過這些終端的輸出,你可能會發現這些錯誤的本質原因。
# --instructions--
修改 `myApp.js` 文件,在控制檯打印出 “Hello World”。
# --hints--
控制檯應該輸出 `"Hello World"`
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/hello-console').then(
(data) => {
assert.isTrue(data.passed, '"Hello World" is not in the server console');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,51 @@
---
id: 587d7fb0367417b2b2512bef
title: 提供 HTML 文件服務
challengeType: 2
forumTopicId: 301516
dashedName: serve-an-html-file
---
# --description--
通過 `res.sendFile(path)` 方法給請求響應一個文件, 可以把它放到路由處理 `app.get('/', ...)` 中。 在後臺,這個方法會根據你想發送的文件的類型,設置適當的消息頭信息來告訴瀏覽器如何處理它, 然後讀取併發送文件, 此方法需要文件的絕對路徑。 建議使用 Node. js 的全局變量 `__dirname` 來計算出這個文件的絕對路徑:
```js
absolutePath = __dirname + relativePath/file.ext
```
# --instructions--
發送文件 `/views/index.html` 作爲 `/` 的 GET 請求的響應。 如果實時查看應用,你會看到一個大的 HTML 標題(以及我們稍後將使用的表單……),目前它們還沒有任何樣式。
**注意:** 你可以編輯上一個挑戰的解題代碼,或者創建一個新的代碼片段。 如果你創建一個新的代碼片段,請記住 Express 會從上到下匹配路由,並執行第一個匹配的處理程序, 你必須註釋掉前面的代碼,否則服務器還是響應之前的字符串。
# --hints--
應用應該響應 views/index.html 文件
```js
(getUserInput) =>
$.get(getUserInput('url')).then(
(data) => {
assert.match(
data,
/<h1>.*<\/h1>/,
'Your app does not serve the expected HTML'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,47 @@
---
id: 587d7fb1367417b2b2512bf1
title: 在指定路由上提供 JSON 服務
challengeType: 2
forumTopicId: 301517
dashedName: serve-json-on-a-specific-route
---
# --description--
HTML 服務器提供 HTML 服務,而 API 提供數據服務。 <dfn>REST</dfn>REpresentational State TransferAPI 允許以簡單的方式進行數據交換,對於客戶端不必要知道服務器的細節。 客戶只需要知道資源在哪裏URL以及想執行的動作動詞。 GET 動詞常被用來獲取無需修改的信息。 如今,網絡上的移動數據首選格式是 JSON 簡而言之JSON 是一種可以方便地用字符串表示 JavaScript 對象的方式,因此它很容易傳輸。
我們來創建一個簡單的 API創建一個路徑爲 `/json` 且返回數據是 JSON 格式的路由, 可以像之前那樣用 `app.get()` 方法來做。 然後在路由處理部分使用 `res.json()` 方法,並傳入一個對象作爲參數, 這個方法會結束請求響應循環request-response loop然後返回數據。 原來,一個有效的 JavaScript 對象會轉化爲字符串,然後會設置適當的消息頭來告訴瀏覽器:“這是一個 JSON 數據”,最後將數據返回給客戶端。 一個有效的對象通常是這種結構:`{key: data}` `data` 可以是數字、字符串、嵌套對象或數組, `data` 也可以是變量或者函數返回值,在這種情況下,它們先求值再轉成字符串。
# --instructions--
當向路由 `/json` 發送 GET 請求,將對象 `{"message": "Hello json"}` 以 JSON 格式返回給客戶端, 瀏覽器訪問 `your-app-url/json` 時,應該在屏幕上看到這個消息。
# --hints--
訪問端口 `/json` 應該返回一個 json 對象 `{"message": "Hello json"}`
```js
(getUserInput) =>
$.get(getUserInput('url') + '/json').then(
(data) => {
assert.equal(
data.message,
'Hello json',
"The '/json' endpoint does not serve the right data"
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,51 @@
---
id: 587d7fb0367417b2b2512bf0
title: 提供靜態資源服務
challengeType: 2
forumTopicId: 301518
dashedName: serve-static-assets
---
# --description--
HTML 服務器通常有一個或多個用戶可以訪問的目錄。 你可以將應用程序所需的靜態資源 (樣式表、腳本、圖片) 放在那裏。
在 Express 中可以使用中間件 `express.static(path)` 來設置此功能,它的參數 `path` 就是包含靜態資源文件的絕對路徑。
如果你不知道什麼是中間件……別擔心,我們將在後面詳細討論。 其實,中間件就是一個攔截路由處理方法並在裏面添加一些信息的函數。 使用 `app.use(path, middlewareFunction)` 方法來加載一箇中間件, 它的第一個參數 `path` 是可選的, 如果沒設置第一個參數,那麼所有的請求都會經過這個中間件處理。
# --instructions--
使用 `app.use()` 爲路徑 `/public` 的請求安裝 `express.static()` 中間件, 靜態資源的絕對路徑是 `__dirname + /public`
現在應用應該能提供 CSS 樣式表, 請注意, `/public/style.css` 文件被項目模板的 `/views/index.html` 引用, 首頁應該更好看了。
# --hints--
應用應該將資源文件從 `/public` 目錄發送到 `/public` 路徑
```js
(getUserInput) =>
$.get(getUserInput('url') + '/public/style.css').then(
(data) => {
assert.match(
data,
/body\s*\{[^\}]*\}/,
'Your app does not serve static assets'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,57 @@
---
id: 587d7fb0367417b2b2512bee
title: 啓動一個 Express 服務
challengeType: 2
forumTopicId: 301519
dashedName: start-a-working-express-server
---
# --description--
`myApp.js` 文件的前兩行中,你可以看到創建一個 Express 應用對象很簡單。 這個對象有幾種方法,在後面的挑戰中將學習到其中的許多部分。 一個基礎的方法是 `app.listen(port)`。 它處於運行狀態時告訴服務器監聽指定的端口。 出於測試的原因,需要應用在後臺運行,所以在 `server.js` 中已經添加了這個方法。
讓我們在服務端輸出第一個字符串! 在 Express 中,路由採用這種結構:`app.METHOD(PATH, HANDLER)` METHOD 是 http 請求方法的小寫形式, PATH 是服務器上的相對路徑(它可以是一個字符串,甚至可以是正則表達式), HANDLER 是匹配路由時 Express 調用的函數, 處理函數採用這種形式:`function(req, res) {...}`,其中 req 是請求對象res 是響應對象, 例如:
```js
function(req, res) {
res.send('Response String');
}
```
將會響應一個字符串“Response String”。
# --instructions--
當 GET 請求 `/`(根路由 )時,使用 `app.get()` 方法響應一個“Hello Express”字符串。 通過查看日誌確保代碼正常運行,如果使用 Replit 可以在預覽中查看結果。
**注意:** 這些課程的所有代碼應該放在開始給出的幾行代碼之間。
# --hints--
應用應該返回字符串“Hello Express”
```js
(getUserInput) =>
$.get(getUserInput('url')).then(
(data) => {
assert.equal(
data,
'Hello Express',
'Your app does not serve the text "Hello Express"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,63 @@
---
id: 587d7fb2367417b2b2512bf7
title: 使用 body-parser 來解析 POST 請求
challengeType: 2
forumTopicId: 301520
dashedName: use-body-parser-to-parse-post-requests
---
# --description--
除了 GET 還有另一個常見的 HTTP 動詞,即 POST。 POST 是使用 HTML 表單發送客戶端數據的默認方法。 在 REST 規範中POST 常用於發送數據以在數據庫中創建新項目(新用戶或新博客文章)。 在這個項目中沒有使用數據庫,但下面將學習如何處理 POST 請求。
在這些類型的請求中,數據不會出現在 URL 中,而是隱藏在請求正文中。 請求正文也是 HTML 請求的一部分,被稱爲負載。 即使數據在 URL 中是不可見的,也不意味着它是私有的。 要了解原因,請觀察 HTTP POST 請求的原始內容:
```http
POST /path/subpath HTTP/1.0
From: john@example.com
User-Agent: someBrowser/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
name=John+Doe&age=25
```
正如你所看到的,正文被編碼成類似查詢字符串的形式, 這是 HTML 表單使用的默認格式。 我們還可以通過 Ajax 使用 JSON 來處理具有更復雜結構的數據。 還有另一種類型的編碼multipart/form-data 它被用來上傳二進制文件。 在本練習中,我們將使用 URL 編碼請求正文。 要解析來自 POST 請求的數據,你必須安裝 `body-parser` 包, 這個包包含一套可以解碼不同格式數據的中間件。
# --instructions--
`package.json` 中安裝 `body-parser` 模塊, 然後在文件頂部 `require` 進來, 用變量 `bodyParser` 保存它。 通過中間件的 `bodyParser.urlencoded({extended: false})` 方法處理 URL 編碼數據, 將通過先前的方法調用返回的函數傳遞到 `app.use()`。 中間件通常掛載在所有需要它的路由之前。
**注意:** `extended` 是一個配置選項, 告訴 `body-parser` 需要使用哪個解析。 當 `extended=false` 時,它使用經典編碼 `querystring` 庫。 當 `extended=true`時,它使用 `qs` 庫進行解析。
當使用 `extended=false` 時,值可以只是字符串或數組。 使用 `querystring` 時返回的對象並不繼承的 JavaScript `Object`,這意味着 `hasOwnProperty``toString` 等函數將不可用。 拓展版本的數據更加靈活,但稍遜於 JSON。
# --hints--
應該掛載 “body-parser” 中間件
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/add-body-parser').then(
(data) => {
assert.isAbove(
data.mountedAt,
0,
'"body-parser" is not mounted correctly'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,52 @@
---
id: 587d7fb1367417b2b2512bf2
title: 使用 .env 文件
challengeType: 2
forumTopicId: 301521
dashedName: use-the--env-file
---
# --description--
`.env` 文件是一個用於將環境變量傳給應用程序的隱藏文件, 這是一個除了開發者之外沒人可以訪問的私密文件,它可以用來存儲你想保密或者隱藏的數據, 例如,它可以存儲第三方服務的 API 密鑰或者數據庫 URI 也可以使用它來存儲配置選項, 通過設置配置選項,你可以改變應用程序的行爲,而無需重寫一些代碼。
在應用程序中可以通過 `process.env.VAR_NAME` 訪問到環境變量。 `process.env` 對象是 Node 程序中的一個全局對象,可以給這個變量傳字符串。 習慣上,變量名全部大寫,單詞之間用下劃線分隔。 `.env` 是一個 shell 文件,因此不需要用給變量名和值加引號。 還有一點需要注意,當你給變量賦值時等號兩側不能有空格,例如:`VAR_NAME=value`。 通常來講,每一個變量定義會獨佔一行。
# --instructions--
添加一個環境變量作爲配置選項。
在項目根目錄創建一個 `.env` 文件,並存儲變量 `MESSAGE_STYLE=uppercase`
當向 `/json` 發 GET 請求時,如果 `process.env.MESSAGE_STYLE` 的值爲 `uppercase`,那麼上一次挑戰中的路由處理程序返回的對象的消息則應該大寫。 響應對象應該是 `{"message": "Hello json"}` or `{"message": "HELLO JSON"}`,取決於 `MESSAGE_STYLE` 的值。
**注意:**如果你正在使用 Replit你無法創建一個 `.env` 文件。 相反,使用內置的 <dfn>SECRETS</dfn> 標籤添加變量。
# --hints--
端口 `/json` 響應的值,應該隨着環境變量 `MESSAGE_STYLE` 的變化而改變。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/use-env-vars').then(
(data) => {
assert.isTrue(
data.passed,
'The response of "/json" does not change according to MESSAGE_STYLE'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,52 @@
---
id: 587d7fb3367417b2b2512bfc
title: 給 package.json 添加描述
challengeType: 2
forumTopicId: 301522
dashedName: add-a-description-to-your-package-json
---
# --description--
一個好的 package.json 文件的下一部分就是 `description` 字段——簡短精悍的的項目描述。
如果你計劃將來把這個包發佈到 npm請注意 description 字段的作用是告知用戶這個包的用途,這樣用戶就可以決定是否要安裝你發佈的包。 然而,這並不是使用描述的唯一場景:它也是一種很好的總結項目的方式, 可以幫助其它開發者、維護者甚至自己在未來快速地瞭解項目,對於任何一個 Node.js 項目來說都非常重要。
無論項目計劃是什麼,都建議使用描述。 類似這樣:
```json
"description": "A project that does something awesome",
```
# --instructions--
給項目的 package.json 文件添加描述(`description`)。
**注意:** 請記住使用雙引號(")包裹字段名並且使用逗號(,)分隔字段。
# --hints--
package.json 應該包含一個有效的“description”鍵
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert(packJson.description, '"description" is missing');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,48 @@
---
id: 587d7fb4367417b2b2512bfe
title: 給 package.json 添加許可證
challengeType: 2
forumTopicId: 301523
dashedName: add-a-license-to-your-package-json
---
# --description--
`license` 字段將告知用戶允許他們拿這個項目幹什麼。
開源項目常見的協議有 MIT 和 BSD 等。 許可證信息並不是必須的。 大多數國家的版權法會默認讓你擁有自己創作的作品的所有權。 但是,明確說明用戶可以做什麼和不能做什麼會是一個很好的做法。 這裏有一個 license 字段的例子:
```json
"license": "MIT",
```
# --instructions--
在項目的 package.json 文件中補充合適的 `license` 字段。
# --hints--
package.json 應該包含一個有效的“license”鍵
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert(packJson.license, '"license" is missing');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,46 @@
---
id: 587d7fb4367417b2b2512bff
title: 給 package.json 添加版本號
challengeType: 2
forumTopicId: 301525
dashedName: add-a-version-to-your-package-json
---
# --description--
`version` 是 package.json 文件中必填字段之一, 這個字段描述了當前項目的版本, 如:
```json
"version": "1.2.0",
```
# --instructions--
給 package.json 文件添加項目的版本號(`version`)。
# --hints--
package.json 應該包含一個有效的 “version” 鍵
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert(packJson.version, '"version" is missing');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,84 @@
---
id: 587d7fb4367417b2b2512bfd
title: 給 package.json 添加關鍵詞
challengeType: 2
forumTopicId: 301526
dashedName: add-keywords-to-your-package-json
---
# --description--
`keywords` 字段中可以使用相關的關鍵字描述項目。 例如:
```json
"keywords": [ "descriptive", "related", "words" ],
```
正如你所見的,這個字段的結構是一個由雙引號字符串組成的數組。
# --instructions--
在 package.json 文件中,給 `keywords` 字段添加一個由適當的字符串組成的數組。
“freecodecamp”應該是關鍵詞之一。
# --hints--
package.json 應該有一個有效的“keywords”鍵
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert(packJson.keywords, '"keywords" is missing');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“keywords”字段應該是一個數組
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.isArray(packJson.keywords, '"keywords" is not an array');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“keywords”中應該包含關鍵詞“freecodecamp”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.include(
packJson.keywords,
'freecodecamp',
'"keywords" does not include "freecodecamp"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,77 @@
---
id: 587d7fb4367417b2b2512c00
title: 使用 npm 的外部包擴展項目
challengeType: 2
forumTopicId: 301527
dashedName: expand-your-project-with-external-packages-from-npm
---
# --description--
強大的依賴管理特性是使用包管理器的最大原因之一。 每當在新的計算機上開始一個項目時無需手動npm 會自動安裝所有的依賴項。 但是 npm 如何準確地知道項目需要哪些依賴呢? 來看看 package.json 文件中 `dependencies` 這一部分。
在這部分,你的項目需要按照下面這種格式來存儲依賴包:
```json
"dependencies": {
"package-name": "version",
"express": "4.14.0"
}
```
# --instructions--
在 package.json 文件的 `dependencies` 字段中添加一個版本號爲“2.14.0”的“moment”包。
**注意:** Moment 是一個非常方便地用來處理時間和日期的庫。
# --hints--
“dependencies”應該包含“moment”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'moment',
'"dependencies" does not include "moment"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“moment”的版本應該是“2.14.0”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.match(
packJson.dependencies.moment,
/^[\^\~]?2\.14\.0/,
'Wrong version of "moment" installed. It should be 2.14.0'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,60 @@
---
id: 587d7fb3367417b2b2512bfb
title: '如何使用 package.json ——所有 Node.js 項目或 npm 包的核心'
challengeType: 2
forumTopicId: 301528
dashedName: how-to-use-package-json-the-core-of-any-node-js-project-or-npm-package
---
# --description--
可以採用下面的任意一種方式完成這些挑戰:
- 克隆 [GitHub repo](https://github.com/freeCodeCamp/boilerplate-npm/) 並在本地完成項目。
- 使用[我們的 Replit 上的初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-npm)來完成項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。 此外,還可以將項目的源碼提交到 `GitHub Link` 中。
`package.json` 文件是所有 Node.js 項目和 npm 包的樞紐, 和 HTML 文檔中的 &lt;head> 區域用來描述網頁的配置信息(元數據)一樣,它存儲項目的相關信息。 它由單個 JSON 對象組成,並以鍵值對的形式存儲項目信息, 且至少包含兩個必填字段“name”和“version”——但是最好提供有關項目的其他信息這將對用戶或者維護者有所幫助。
如果能找到項目的文件樹,那麼可以在文件樹的最外層找到 package.json 在接下來的幾個挑戰中將完善這個文件。
在這個文件中最常見的信息之一是 `author` 字段, 它說明了項目的創建者,它可以是字符串,也可以是帶有聯繫人詳細信息的對象。 對於較大的項目,建議使用對象;但是在我們的項目中,一個簡單的字符串就夠了,比如下面的例子:
```json
"author": "Jane Doe",
```
# --instructions--
在項目的 package.json 文件的 `author` 鍵中添加你的名字。
**注意:** 正在修改的是一個 JSON所有的字段名必須用雙引號")包裹,也必須用逗號(,)分割。
# --hints--
package.json 應該有一個有效的“author”鍵
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert(packJson.author, '"author" is missing');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,73 @@
---
id: 587d7fb5367417b2b2512c01
title: 通過語義化版本來管理 npm 依賴關係
challengeType: 2
forumTopicId: 301529
dashedName: manage-npm-dependencies-by-understanding-semantic-versioning
---
# --description--
在 package.json 文件的依賴項中npm 包的 `Versions` 遵循語義化版本SemVerSemantic Versioning它是一種旨在使管理依賴項更加容易的軟件版本控制的行業標準。 在 npm 上發佈的庫、框架或其它工具都應該使用語義化版本,以便讓用戶清晰地知道如果項目升級將帶來哪些改變。
在使用外部依賴項(大多數情況都是這樣)進行軟件開發時,瞭解語義化版本會很有用。 這些數字保存着項目的偶然發生的破壞性改變,不會讓人對項目昨天還正常,今天卻無法運行而百思不解。 根據官網,這是語義化版本的工作方式:
```json
"package": "MAJOR.MINOR.PATCH"
```
當做了不兼容的 API 修改應該增加主版本號MAJOR 當新增了向下兼容的新功能時應該增加次版本號MINOR 當修復了向下兼容的 bug 時應該增加修訂號PATCH。 這意味着修訂號是用來修復錯誤的,次版本號則是添加了新功能,但它們都沒有破壞之前的功能。 主版本號MAJOR是添加了不兼容早期版本的更改。
# --instructions--
在 package.json 文件的依賴項中,修改 moment 的`version`,讓它的主版本是 2次版本號是 10修訂號是 2。
# --hints--
“dependencies”字段應該包含“moment”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'moment',
'"dependencies" does not include "moment"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“moment”的版本號應該是“2.10.2”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.equal(
packJson.dependencies.moment,
'2.10.2',
'Wrong version of "moment". It should be 2.10.2'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,52 @@
---
id: 587d7fb5367417b2b2512c04
title: 從依賴項中刪除依賴包
challengeType: 2
forumTopicId: 301530
dashedName: remove-a-package-from-your-dependencies
---
# --description--
已經嘗試過一些通過項目 package.json 文件中依賴項管理依賴的方式了, 也添加了一些外部的依賴包到項目中,甚至通過一些特殊的字符比如波浪號或者脫字符來告訴 npm 想要的版本類型。
但是,如果想要刪除不再需要的依賴包,該怎麼辦呢? 可能已經猜到了——只需要從依賴項中刪除相應的鍵值對就行了。
同樣的方法也適用於刪除 package.json 中的其它字段。
# --instructions--
從依賴項中刪除 moment 依賴包。
**注意:**刪除依賴包後,確保逗號數量正確。
# --hints--
“dependencies”字段不包含“moment”。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.notProperty(
packJson.dependencies,
'moment',
'"dependencies" still includes "moment"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,75 @@
---
id: 587d7fb5367417b2b2512c03
title: 用脫字符(^)來使用依賴項的最新次要版本
challengeType: 2
forumTopicId: 301531
dashedName: use-the-caret-character-to-use-the-latest-minor-version-of-a-dependency
---
# --description--
和上一個挑戰中我們學到的用波浪號來安裝最新的修訂版依賴一樣,脫字符(`^`)也允許 npm 來安裝功能更新。 它們的不同之處在於:脫字符允許次版本和修訂版更新。
現在項目中的 moment 依賴包的版本應該是“~2.10.2”,這意味着 npm 可以安裝最新的 2.10.x 版的 moment 如果使用脫字符(^)來替換版本號的前綴,那麼 npm 可以將 moment 升級安裝到任何 2.x.x 的版本。
```json
"package": "^1.3.8"
```
這會將依賴包更新到任意的 1.x.x 版本。
# --instructions--
在依賴項中,使用脫字符(`^`)爲 moment 的版本添加前綴,允許 npm 更新依賴包到任意新的次版本。
**注意:**原來的版本號不用更改。
# --hints--
“dependencies”字段中應包含“moment”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'moment',
'"dependencies" does not include "moment"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“moment”的版本應是“^2.x.x”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.match(
packJson.dependencies.moment,
/^\^2\./,
'Wrong version of "moment". It should be ^2.10.2'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,75 @@
---
id: 587d7fb5367417b2b2512c02
title: 用波浪號維持依賴項的最新修訂號
challengeType: 2
forumTopicId: 301532
dashedName: use-the-tilde-character-to-always-use-the-latest-patch-version-of-a-dependency
---
# --description--
在上一個挑戰中npm 只包含特定版本的依賴包。 如果想讓項目各個部分保持相互兼容,鎖定依賴包版本是一個行之有效的辦法。 但是大多數情況下,我們並不希望錯過依賴項的問題修復,因爲它們通常包含重要的安全補丁,而且它們理論上也會兼容我們既有的代碼。
可以在依賴項的版本號前加一個波浪號(`~`),以讓 npm 依賴項更新到最新的修訂版。 這裏有一個允許升級到任何 1.3.x 的例子:
```json
"package": "~1.3.8"
```
# --instructions--
在 package.json 文件中,當前規則是 npm 將 moment 升級到特定版本2.10.2)。 但是現在,要允許使用最新的 2.10.x 版本。
在依賴項中,給 moment 的版本號添加波浪號(`~`)前綴,允許 npm 將其更新爲最新的修訂版。
**注意:**原來的版本號不用更改。
# --hints--
“dependencies”應該包含“moment”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'moment',
'"dependencies" does not include "moment"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“moment”的版本號應該是“~2.10.2”
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.match(
packJson.dependencies.moment,
/^\~2\.10\.2/,
'Wrong version of "moment". It should be ~2.10.2'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,76 @@
---
id: 587d7fb9367417b2b2512c12
title: 通過鏈式調用輔助查詢函數來縮小搜索結果
challengeType: 2
forumTopicId: 301533
dashedName: chain-search-query-helpers-to-narrow-search-results
---
# --description--
如果不給 `Model.find()`(或者別的搜索方法)的最後一個參數傳入回調函數, 查詢將不會執行。 可以將查詢條件存儲在變量中供以後使用, 也可以通過鏈式調用這類變量來構建新的查詢字段。 實際的數據庫操作會在最後調用 `.exec()` 方法時執行。 必須把回調函數傳給最後一個方法。 Mongoose 提供了許多輔助查詢函數, 這裏使用最常見的一種。
# --instructions--
修改 `queryChain` 函數來查詢喜歡 `foodToSearch` 食物的人。 同時,需要將查詢結果按 `name` 屬性排序, 查詢結果應限制在兩個 document 內,並隱藏 age 屬性。 請鏈式調用 `.find()``.sort()``.limit()``.select()`,並在最後調用 `.exec()` 並將 `done(err, data)` 回調函數傳入 `exec()`
# --hints--
應該成功地鏈式調用輔助查詢函數。
```js
(getUserInput) =>
$.ajax({
url: getUserInput('url') + '/_api/query-tools',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify([
{ name: 'Pablo', age: 26, favoriteFoods: ['burrito', 'hot-dog'] },
{ name: 'Bob', age: 23, favoriteFoods: ['pizza', 'nachos'] },
{ name: 'Ashley', age: 32, favoriteFoods: ['steak', 'burrito'] },
{ name: 'Mario', age: 51, favoriteFoods: ['burrito', 'prosciutto'] }
])
}).then(
(data) => {
assert.isArray(data, 'the response should be an Array');
assert.equal(
data.length,
2,
'the data array length is not what expected'
);
assert.notProperty(
data[0],
'age',
'The returned first item has too many properties'
);
assert.equal(
data[0].name,
'Ashley',
'The returned first item name is not what expected'
);
assert.notProperty(
data[1],
'age',
'The returned second item has too many properties'
);
assert.equal(
data[1].name,
'Mario',
'The returned second item name is not what expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,88 @@
---
id: 587d7fb6367417b2b2512c07
title: 創建一個模型Model
challengeType: 2
forumTopicId: 301535
dashedName: create-a-model
---
# --description--
**C**RUD 第一小節——CREATE
首先,我們需要一個 Schema 每一個 Schema 都對應一個 MongoDB 的 collection 並且在相應的 collection 裏定義 documents 的“樣子”。 Schema 用於組成模型Model 我們甚至可以通過嵌套 Schema 來創建複雜的模型。目前我們先從簡。 我們可以根據模型創建實例,模型實例化後的對象稱爲 documents。
Replit 是一個真實的服務器,在其中,通過 handler 函數和數據庫交互。 這些函數會在特定事件(比如有人調用了我們的服務器 API發生時執行。 接下來的挑戰題目即是以此爲基礎。 `done()` 是一個回調函數,它的作用是在一個異步操作(比如對數據庫進行插入、查詢、更新或刪除)執行完成時,通知我們可以繼續執行後續的其它代碼。 這與 Node.js 中的處理方式十分類似,在 Node.js 中,我們會在(異步操作)成功時調用 `done(null, data)`,在失敗時調用 `done(err)`
注意:與遠程服務器進行交互時,我們需要考慮到發生錯誤的可能!
```js
/* Example */
const someFunc = function(done) {
//... do something (risky) ...
if (error) return done(error);
done(null, result);
};
```
# --instructions--
按下面的原型信息創建一個名爲 `personSchema` 的 schema
```markup
- Person Prototype -
--------------------
name : string [required]
age : number
favoriteFoods : array of strings (*)
```
採用 Mongoose 基礎 schema 類型。 你如果還想添加更多的鍵,就請使用 required 或 unique 等簡單的驗證器validators並設置默認值。 詳情請參考 [Mongoose 文檔](http://mongoosejs.com/docs/guide.html)。
請從 `personSchema` 創建一個名爲 `Person` 的 model。
# --hints--
應當成功地通過 Mongoose schema 創建實例
```js
(getUserInput) =>
$.post(getUserInput('url') + '/_api/mongoose-model', {
name: 'Mike',
age: 28,
favoriteFoods: ['pizza', 'cheese']
}).then(
(data) => {
assert.equal(data.name, 'Mike', '"model.name" is not what expected');
assert.equal(data.age, '28', '"model.age" is not what expected');
assert.isArray(
data.favoriteFoods,
'"model.favoriteFoods" is not an Array'
);
assert.include(
data.favoriteFoods,
'pizza',
'"model.favoriteFoods" does not include the expected items'
);
assert.include(
data.favoriteFoods,
'cheese',
'"model.favoriteFoods" does not include the expected items'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,56 @@
---
id: 587d7fb6367417b2b2512c09
title: 創建並保存一條 Model 記錄
challengeType: 2
forumTopicId: 301536
dashedName: create-and-save-a-record-of-a-model
---
# --description--
在這個挑戰中,你需要創建並保存一條模型數據。
# --instructions--
`createAndSavePerson` 函數中,用我們在上一個挑戰中寫好的 `Person` 構造函數創建 document 實例, 將包含 `name``age``favoriteFoods` 的對象傳給構造函數, 這些屬性的數據類型必須符合我們在 `personSchema` 中定義的類型。 然後在返回的 document 實例上調用方法 `document.save()`。 同時,按 Node.js 的方式爲它傳一個回調函數。 這是一種常見模式以下所有CRUD方法都將這樣的回調函數作爲最後一個參數。
```js
/* Example */
// ...
person.save(function(err, data) {
// ...do your stuff here...
});
```
# --hints--
應成功地創建數據並保存一條數據到數據庫
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/create-and-save-person').then(
(data) => {
assert.isString(data.name, '"item.name" should be a String');
assert.isNumber(data.age, '28', '"item.age" should be a Number');
assert.isArray(
data.favoriteFoods,
'"item.favoriteFoods" should be an Array'
);
assert.equal(data.__v, 0, 'The db item should be not previously edited');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,68 @@
---
id: 587d7fb7367417b2b2512c0a
title: 使用 model.create() 創建多條記錄
challengeType: 2
forumTopicId: 301537
dashedName: create-many-records-with-model-create
---
# --description--
在一些情況下,比如進行數據庫初始化,你會需要創建很多 model 實例來用作初始數據。 `Model.create()` 接受一組像 `[{name: 'John', ...}, {...}, ...]` 的數組作爲第一個參數,並將其保存到數據庫。
# --instructions--
修改 `createManyPeople` 方法,使用 `arrayOfPeople` 作爲 `Model.create()` 的參數來創建多個 people 實例。
**注意:** 你可以使用在上一個挑戰中創建的 model 來完成當前挑戰。
# --hints--
應當成功地一次性創建多條數據
```js
(getUserInput) =>
$.ajax({
url: getUserInput('url') + '/_api/create-many-people',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify([
{ name: 'John', age: 24, favoriteFoods: ['pizza', 'salad'] },
{ name: 'Mary', age: 21, favoriteFoods: ['onions', 'chicken'] }
])
}).then(
(data) => {
assert.isArray(data, 'the response should be an array');
assert.equal(
data.length,
2,
'the response does not contain the expected number of items'
);
assert.equal(data[0].name, 'John', 'The first item is not correct');
assert.equal(
data[0].__v,
0,
'The first item should be not previously edited'
);
assert.equal(data[1].name, 'Mary', 'The second item is not correct');
assert.equal(
data[1].__v,
0,
'The second item should be not previously edited'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,57 @@
---
id: 587d7fb8367417b2b2512c11
title: 使用 model.remove() 刪除多個 document
challengeType: 2
forumTopicId: 301538
dashedName: delete-many-documents-with-model-remove
---
# --description--
`Model.remove()` 可以用於刪除符合給定匹配條件的所有 document。
# --instructions--
修改 `removeManyPeople` 函數,使用 `nameToRemove` 刪除所有姓名是變量 `Model.remove()` 的人。 給它傳入一個帶有 `name` 字段的查詢 document 和一個回調函數。
**注意:** `Model.remove()` 不會返回被刪除的 document而是會返回一個包含操作結果以及受影響的數據數量的 JSON 對象。 不要忘記將它傳入 `done()` 回調函數,因爲我們需要在挑戰的測試中調用它。
# --hints--
應一次性成功刪除多條數據
```js
(getUserInput) =>
$.ajax({
url: getUserInput('url') + '/_api/remove-many-people',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify([
{ name: 'Mary', age: 16, favoriteFoods: ['lollipop'] },
{ name: 'Mary', age: 21, favoriteFoods: ['steak'] }
])
}).then(
(data) => {
assert.isTrue(!!data.ok, 'The mongo stats are not what expected');
assert.equal(
data.n,
2,
'The number of items affected is not what expected'
);
assert.equal(data.count, 0, 'the db items count is not what expected');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,53 @@
---
id: 587d7fb8367417b2b2512c10
title: 使用 model.findByIdAndRemove 刪除一個 document
challengeType: 2
forumTopicId: 301539
dashedName: delete-one-document-using-model-findbyidandremove
---
# --description--
`findByIdAndRemove``findOneAndRemove` 類似於我們之前的更新方法, 它們將被刪除的 document 傳給數據庫。 和之前一樣,使用函數參數 `personId` 作爲查詢關鍵字。
# --instructions--
修改 `removeById` 函數,通過 `_id` 刪除一個人的數據, 可以使用 `findByIdAndRemove()``findOneAndRemove()` 方法。
# --hints--
應當成功地刪除一條數據
```js
(getUserInput) =>
$.post(getUserInput('url') + '/_api/remove-one-person', {
name: 'Jason Bourne',
age: 36,
favoriteFoods: ['apples']
}).then(
(data) => {
assert.equal(data.name, 'Jason Bourne', 'item.name is not what expected');
assert.equal(data.age, 36, 'item.age is not what expected');
assert.deepEqual(
data.favoriteFoods,
['apples'],
'item.favoriteFoods is not what expected'
);
assert.equal(data.__v, 0);
assert.equal(data.count, 0, 'the db items count is not what expected');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,85 @@
---
id: 587d7fb6367417b2b2512c06
title: 安裝和設置 Mongoose
challengeType: 2
forumTopicId: 301540
dashedName: install-and-set-up-mongoose
---
# --description--
可以採用下面的任意一種方式完成這些挑戰:
- 克隆 [GitHub repo](https://github.com/freeCodeCamp/boilerplate-mongomongoose/) 並在本地完成項目。
- 使用[我們的 Replit 上的初始化項目](https://replit.com/github/freeCodeCamp/boilerplate-mongomongoose)來完成項目。
- 使用你選擇的網站生成器來完成項目, 並確保包含了我們 GitHub 倉庫的所有文件。
當完成本項目,請確認有一個正常運行的 demo 可以公開訪問。 然後將 URL 提交到 `Solution Link` 中。
在這個挑戰中,你將建立一個 MongoDB Atlas 數據庫並導入連接到它所需的軟件包。
按照<a href='https://www.freecodecamp.org/news/get-started-with-mongodb-atlas/' rel='noopener noreferrer' target='_blank'>這篇教程</a>在 MongoDB Atlas 創建一個託管數據庫。
# --instructions--
`mongodb``mongoose` 添加到項目的 `package.json` 文件中。 然後,在 `myApp.js` 文件中請求 `mongoose`。 創建一個 `.env` 文件,給它添加一個 `MONGO_URI` 變量。 變量的值爲你的 MongoDB Atlas 數據庫 URI。 應用單引號或雙引號包裹 URI。請記住環境變量 `=` 兩邊不能有空格。 例如,`MONGO_URI='VALUE'`。 完成後,使用下面的代碼來連接數據庫。
```js
mongoose.connect(<Your URI>, { useNewUrlParser: true, useUnifiedTopology: true });
```
# --hints--
“mongodb” 應在 package.json 中作爲依賴項定義。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/file/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(packJson.dependencies, 'mongodb');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
“mongoose” 應在 package.json 中作爲依賴項定義。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/file/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(packJson.dependencies, 'mongoose');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
應使用 “mongoose” 連接數據庫。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/is-mongoose-ok').then(
(data) => {
assert.isTrue(data.isMongooseOk, 'mongoose is not connected');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,54 @@
---
id: 587d7fb8367417b2b2512c0e
title: '通過執行查詢、編輯、保存來執行經典更新流程'
challengeType: 2
forumTopicId: 301541
dashedName: perform-classic-updates-by-running-find-edit-then-save
---
# --description--
在過去,如果想要編輯 document 並以某種方式使用它(比如放到服務器的返回數據中),就必須執行查找、編輯和保存。 Mongoose 有一個專用的更新方法 `Model.update()` 它與底層的 mongo 驅動綁定。 通過這個方法,我們可以批量編輯符合特定條件的多個 document。但問題在於這個方法不會返回更新後的 document而是返回狀態信息。 此外,它直接調用 mongo 的底層驅動,讓處理 model 的驗證變得更加棘手。
# --instructions--
在這個挑戰中,請使用參數 `personId` 作爲字段,修改 `findEditThenSave` 方法,以在數據庫中通過 `_id` 找到相應的 person你可以使用之前挑戰中的任何一種方法。 將 `"hamburger"` 添加到它的 `favoriteFoods` 清單中(你可以使用 `Array.push()`)。 然後,在查詢數據庫的方法的回調裏通過 `save()` 方法更新 `Person` 的數據。
**提示:** 如果你在 Schema 中將 `favoriteFoods` 聲明爲一個 Array數組並且沒有指定數組的類型(如 `[String]`) 那麼此時,`favoriteFoods` 就會是默認的 Mixed 類型。如果想編輯它,就必須執行 `document.markModified('edited-field')`。 詳情請參閱 [Mongoose 文檔](https://mongoosejs.com/docs/schematypes.html#Mixed)
# --hints--
應成功地對一條數據進行查找、編輯和更新
```js
(getUserInput) =>
$.post(getUserInput('url') + '/_api/find-edit-save', {
name: 'Poldo',
age: 40,
favoriteFoods: ['spaghetti']
}).then(
(data) => {
assert.equal(data.name, 'Poldo', 'item.name is not what is expected');
assert.equal(data.age, 40, 'item.age is not what expected');
assert.deepEqual(
data.favoriteFoods,
['spaghetti', 'hamburger'],
'item.favoriteFoods is not what expected'
);
assert.equal(data.__v, 1, 'The item should be previously edited');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,58 @@
---
id: 587d7fb8367417b2b2512c0f
title: 在 document 中執行新的更新方式——使用 model.findOneAndUpdate()
challengeType: 2
forumTopicId: 301542
dashedName: perform-new-updates-on-a-document-using-model-findoneandupdate
---
# --description--
最近發佈的 mongoose 版本簡化了 document 的更新方式, 但同時,一些高級功能(如 pre/post hook, 驗證)的使用方式也變得和以前不同。因此,在很多情景下,上一個挑戰中提到的老方法其實更常用。 新方法的加入,可以讓我們使用 `findByIdAndUpdate()` 來進行基於 id 的搜索。
# --instructions--
修改 `findAndUpdate` 函數,通過 `Name` 查詢人,並將查到的人的年齡設爲 `20` 歲, 將函數參數 `personName` 作爲查詢關鍵字。
**提示:** 你需要返回更新後的 document。 你可以把 `findOneAndUpdate()` 的第三個參數設置爲 `{ new: true }` 。 默認情況下,這個方法會返回修改前的數據。
# --hints--
應成功地使用 findOneAndUpdate 更新數據
```js
(getUserInput) =>
$.post(getUserInput('url') + '/_api/find-one-update', {
name: 'Dorian Gray',
age: 35,
favoriteFoods: ['unknown']
}).then(
(data) => {
assert.equal(data.name, 'Dorian Gray', 'item.name is not what expected');
assert.equal(data.age, 20, 'item.age is not what expected');
assert.deepEqual(
data.favoriteFoods,
['unknown'],
'item.favoriteFoods is not what expected'
);
assert.equal(
data.__v,
0,
'findOneAndUpdate does not increment version by design!'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,53 @@
---
id: 587d7fb7367417b2b2512c0b
title: 使用 model.find() 查詢數據庫
challengeType: 2
forumTopicId: 301543
dashedName: use-model-find-to-search-your-database
---
# --description--
我們嘗試一種最簡單的用法,`Model.find()` 接收一個查詢 document一個 JSON 對象)作爲第一個參數,一個回調函數作爲第二個參數, 它會返回由匹配到的數據組成的數組。 這個方法支持很多搜索選項, 詳情請參閱文檔。
# --instructions--
修改 `findPeopleByName` 函數使用 <code>Model.find() -\> [Person]</code> 查詢所有給定名字的人。
請使用函數參數中的 `personName` 作爲搜索條件。
# --hints--
應成功地找到所有符合條件的數據
```js
(getUserInput) =>
$.post(getUserInput('url') + '/_api/find-all-by-name', {
name: 'r@nd0mN4m3',
age: 24,
favoriteFoods: ['pizza']
}).then(
(data) => {
assert.isArray(data, 'the response should be an Array');
assert.equal(
data[0].name,
'r@nd0mN4m3',
'item.name is not what expected'
);
assert.equal(data[0].__v, 0, 'The item should be not previously edited');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,48 @@
---
id: 587d7fb7367417b2b2512c0d
title: 使用 model.findById() 方法,根據 _id 來搜索數據
challengeType: 2
forumTopicId: 301544
dashedName: use-model-findbyid-to-search-your-database-by-id
---
# --description--
在保存 document 的時候MongoDB 會自動爲它添加 `_id` 字段,並給該字段設置一個唯一的僅包含數字和字母的值。 通過 `_id` 搜索是一個十分常見的操作爲此Mongoose 提供了一個專門的方法。
# --instructions--
修改 `findPersonById`,用 `Model.findById() -> Person` 來查詢唯一一個給定 `_id` 的人, 把函數參數 `personId` 作爲查詢鍵。
# --hints--
應成功地根據 Id 找到對應的數據
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/find-by-id').then(
(data) => {
assert.equal(data.name, 'test', 'item.name is not what expected');
assert.equal(data.age, 0, 'item.age is not what expected');
assert.deepEqual(
data.favoriteFoods,
['none'],
'item.favoriteFoods is not what expected'
);
assert.equal(data.__v, 0, 'The item should be not previously edited');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```

View File

@@ -0,0 +1,51 @@
---
id: 587d7fb7367417b2b2512c0c
title: 使用 model.findOne() 從數據庫中返回一個單一匹配的 Document
challengeType: 2
forumTopicId: 301545
dashedName: use-model-findone-to-return-a-single-matching-document-from-your-database
---
# --description--
`Model.findOne()``Model.find()` 十分類似,但就算數據庫中有很多條數據可以匹配查詢條件,它也只返回一個 document而不會返回一個數組 如果查詢條件是聲明爲唯一值的屬性,它會更加適用。
# --instructions--
修改 `findOneByFood` 函數,用 `Model.findOne() -> Person` 來查詢在收藏夾中有某種食物的一個人。 將函數參數中的 `food` 作爲檢索條件。
# --hints--
應成功地找到一個數據
```js
(getUserInput) =>
$.post(getUserInput('url') + '/_api/find-one-by-food', {
name: 'Gary',
age: 46,
favoriteFoods: ['chicken salad']
}).then(
(data) => {
assert.equal(data.name, 'Gary', 'item.name is not what expected');
assert.deepEqual(
data.favoriteFoods,
['chicken salad'],
'item.favoriteFoods is not what expected'
);
assert.equal(data.__v, 0, 'The item should be not previously edited');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```