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.
*/
```