<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Express - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Express - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 12 Jun 2026 10:38:40 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/express/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何用 OpenAPI 在 Express 中构建更好的 API ]]>
                </title>
                <description>
                    <![CDATA[ 我将在这篇文章中分享在 Express 中构建强大的 REST API 的方法。首先，我会介绍构建 REST API 的一些挑战，然后提出一个使用开放标准的解决方案。 本文并非一篇关于 Node.js [https://nodejs.org/en/]、Express.js [https://expressjs.com/] 或  REST API [/news/rest-apis/] 的介绍。如果你需要复习，请在深入研究本文内容之前查看这些链接。🤿 我喜欢 Node.js 那极具灵活性和易用性的生态。这个社区充满活力，并且你可以用你已经掌握的语言在几分钟内设置一个 REST API。 在应用的前后端使用相同的编程语言是一件很有价值的事。这使我们在浏览代码库时可以减少上下文切换 [https://blog.rescuetime.com/context-switching/]，从而变得更轻松。全栈开发者可以快速切换技术栈，共享代码 [https://betterprogramming.pub/sharing-logic-components-between-frontend-and- ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-build-explicit-apis-with-openapi/</link>
                <guid isPermaLink="false">641fbc4ee32a7606487d581c</guid>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ HeZean ]]>
                </dc:creator>
                <pubDate>Wed, 29 Mar 2023 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/03/6065d95e9618b008528ab4a6.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-build-explicit-apis-with-openapi/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build Better APIs in Express with OpenAPI</a>
      </p><!--kg-card-begin: markdown--><p>我将在这篇文章中分享在 Express 中构建强大的 REST API 的方法。首先，我会介绍构建 REST API 的一些挑战，然后提出一个使用开放标准的解决方案。</p>
<p>本文并非一篇关于 <a href="https://nodejs.org/en/">Node.js</a>、<a href="https://expressjs.com/">Express.js</a> 或 <a href="https://chinese.freecodecamp.org/news/rest-apis/">REST API</a> 的介绍。如果你需要复习，请在深入研究本文内容之前查看这些链接。🤿</p>
<p>我喜欢 Node.js 那极具灵活性和易用性的生态。这个社区充满活力，并且你可以用你已经掌握的语言在几分钟内设置一个 REST API。</p>
<p>在应用的前后端使用相同的编程语言是一件很有价值的事。这使我们在浏览代码库时可以减少<a href="https://blog.rescuetime.com/context-switching/">上下文切换</a>，从而变得更轻松。全栈开发者可以快速切换技术栈，<a href="https://betterprogramming.pub/sharing-logic-components-between-frontend-and-backend-repositories-6fdc1f9cb850">共享代码</a>也变得轻而易举。</p>
<p>尽管如此，随着 MVP 成长为成熟的生产环境应用程序和开发团队规模的扩大，这种灵活性也带来了挑战。</p>
<h2 id="restapi">使用 REST API 的挑战</h2>
<p>无论你使用哪种技术栈，当代码库和团队规模增长时，都会面临许多挑战。</p>
<p>在本文中，我将描述通过 REST API 暴露业务逻辑的 Express.js 应用程序所带来的挑战，以小见大。</p>
<p>无论 API 消费者的性质如何（网页、移动应用、第三方后端），随着他们的成长，他们都可能面临以下一个（或多个）挑战：</p>
<h3 id="1">1. ⚠️ 更难做出改变</h3>
<p>在文档不够明确时，在 REST API 的任何一方进行修改都变得更加困难。</p>
<p>举个例子，假设你有一个 REST 端点，可以返回一个特定的用户的名字。在即将新增的功能中，你可能需要修改这个 API 使其返回年龄。这可能会潜在地破坏网络应用和移动应用。</p>
<p>你可以设置集成测试来一定程度上避免这个问题，但你仍然会严重依赖开发人员来手动覆盖所有的边界情况。这需要大量的时间和精力，而且你永远无法 100% 确定这些变化不会破坏应用程序。</p>
<h3 id="2">2. 📜 缺少（及时更新的）文档</h3>
<p>文档是构建 REST API 时的另一个敏感话题。我坚信在大多数情况下，代码本身应该足以代替一部分文档。</p>
<p>也就是说，REST API 在开发中会变得越来越复杂，检查代码中每个端点的安全性、参数和可能的响应也随之变得繁琐且耗时。这就减慢了开发的速度，也给 bug 进入系统留下了隐患。</p>
<p>即使团队致力于在一个独立于代码的文档中手动保持文档的更新，也很难 100% 确保它反映了代码的情况。</p>
<h3 id="3api">3. 📢 公共 API</h3>
<p>这并不适用于所有的应用程序，但在某些情况下，一个应用程序可能需要向第三方暴露一系列的功能。对于这种情况，第三方有可能会在我们暴露的 API 之上构建核心功能。</p>
<p>这意味着我们不能以更新我们的私有 API 的同样速度来修改这些公共 API。一旦修改了公共 API，第三方应用程序可能会因此崩溃，而这正是我们应该不惜一切代价避免的事情。</p>
<p>公共 API 所暴露的内容应该是明确的，并且可以简单地进行开发，以限制内部和外部开发团队之间所需的来回沟通的数量。</p>
<h3 id="4">4. ✍️ 手动集成测试</h3>
<p>当应用程序的开发没有与之匹配的周密计划时，很有可能 API 所提供的内容和 API 消费者期望的内容被深埋在代码中。</p>
<p>对于仅有少量的内部端点的系统来说，这并不是一个大问题。但随着 API 接口数量的增长，修改现有的端点需要在整个系统中遵循面包屑，以确保消费者期望得到的东西与提供的东西是相等的。</p>
<p>这个问题可以通过对系统的不同部分之间进行集成测试来缓解。但是人工完成这件事的工作量非常巨大的，并且如果没做好的话，可能会在系统实际上不能正常工作的时候让开发人员误以为系统状态良好。</p>
<h2 id="">提出的解决方案</h2>
<p>我们已经看到了构建 REST API 所带来的固有挑战。在下一节中，我们将使用开放标准构建一个示例 Express 项目，以解决这些挑战。</p>
<h3 id="api">API 标准规范</h3>
<p>前面部分描述的挑战已经存在很长时间了，所以面对这个问题，我们最好查看现有的解决方案，而不是重新发明轮子。</p>
<p>许多标准尝试对 REST API 进行规范化定义（<a href="https://raml.org/">RAML</a>、<a href="https://jsonapi.org/">JsonAPI</a>、<a href="https://www.openapis.org/">OpenAPI</a>......）。这些项目的共同目标是使开发人员更容易定义他们的 API 行为，以便跨多种语言的服务器和客户端能够“共说一种语言”。</p>
<p>有了某种形式的 API 规范，可以解决许多挑战，因为在许多情况下，可以从这些规范自动生成客户端 SDK、测试、模拟服务器和文档。</p>
<p>一种我最喜欢的规范是 OpenAPI（原名 Swagger）。它有一个很大的社区，并且有很多用于 Express 的工具。这可能不是所有 REST API 项目中的最佳工具，因此请在为你自己的项目选择规范之前进行额外的研究，以确保该规范的工具和支持对你的项目有帮助。</p>
<h3 id="">示例的背景</h3>
<p>在这个示例中，假设我们正在构建一个待办事项列表管理应用。用户可以通过访问一个 web 应用来获取、创建、编辑和删除待办事项，这些待办事项被保存在后端。</p>
<p>在这个例子中，后端使用一个 Express.js 应用程序，它将通过 REST API 暴露以下功能：</p>
<ul>
<li>获取待办事项: <strong>[GET] /todos</strong></li>
<li>创建待办事项：<strong>[POST] /todos</strong></li>
<li>编辑待办事项：<strong>[PUT] /todos/:id</strong></li>
<li>删除待办事项：<strong>[DELETE] /todos/:id</strong></li>
</ul>
<p>对于一个真实的待办事项管理应用来说，上面的功能有点过度简化，但这有助于展示我们如何在实际情况下克服上面提出的挑战。</p>
<h3 id="">实现</h3>
<p>很好，现在我们已经介绍了 API 定义的开放标准和背景，让我们来实现一个 Express 待办事项应用，演示怎么解决前面的挑战。</p>
<p>我们将使用 Express 库 <a href="https://github.com/kogosoftwarellc/open-api/tree/master/packages/express-openapi"><strong>express-openapi</strong></a> 的 OpenAPI。请注意，这个库提供的高级功能（响应验证、认证、中间件设置......）超出了本文的范围。</p>
<p>你可以在<a href="https://github.com/aperkaz/express-open-api">这个仓库</a>中找到演示的完整代码。</p>
<ol>
<li>初始化一个 Express 框架，并初始化一个 Git 仓库：</li>
</ol>
<pre><code class="language-bash">npx express-generator --no-view --git todo-app
cd ./todo-app
git init
git add .; git commit -m "Initial commit";
</code></pre>
<ol start="2">
<li>将 <strong><a href="https://github.com/kogosoftwarellc/open-api/tree/master/packages/express-openapi">express-openapi</a></strong> 引入我们的程序：</li>
</ol>
<p><code>npm i express-openapi -s</code></p>
<pre><code class="language-javascript">// ./app.js

...

app.listen(3030);

...

// OpenAPI routes
initialize({
  app,
  apiDoc: require("./api/api-doc"),
  paths: "./api/paths",
});

module.exports = app;
</code></pre>
<ol start="3">
<li>添加 OpenAPI 基础模型。</li>
</ol>
<p>请注意，模型中定义了 <strong>Todo</strong> 的类型，将在路由处理程序中引用。</p>
<pre><code class="language-javascript">// ./api/api-doc.js

const apiDoc = {
  swagger: "2.0",
  basePath: "/",
  info: {
    title: "Todo app API.",
    version: "1.0.0",
  },
  definitions: {
    Todo: {
      type: "object",
      properties: {
        id: {
          type: "number",
        },
        message: {
          type: "string",
        },
      },
      required: ["id", "message"],
    },
  },
  paths: {},
};

module.exports = apiDoc;
</code></pre>
<ol start="4">
<li>添加路由<a href="https://github.com/kogosoftwarellc/open-api/tree/master/packages/express-openapi#getting-started">处理程序</a>。</li>
</ol>
<p>每个处理程序都声明它支持哪些操作（GET、POST ...），对每个操作的回调，以及该处理程序的 <strong>apiDoc</strong> OpenAPI 模型。</p>
<pre><code class="language-javascript">// ./api/paths/todos/index.js
module.exports = function () {
  let operations = {
    GET,
    POST,
    PUT,
    DELETE,
  };

  function GET(req, res, next) {
    res.status(200).json([
      { id: 0, message: "First todo" },
      { id: 1, message: "Second todo" },
    ]);
  }

  function POST(req, res, next) {
    console.log(`About to create todo: ${JSON.stringify(req.body)}`);
    res.status(201).send();
  }

  function PUT(req, res, next) {
    console.log(`About to update todo id: ${req.query.id}`);
    res.status(200).send();
  }

  function DELETE(req, res, next) {
    console.log(`About to delete todo id: ${req.query.id}`);
    res.status(200).send();
  }

  GET.apiDoc = {
    summary: "Fetch todos.",
    operationId: "getTodos",
    responses: {
      200: {
        description: "List of todos.",
        schema: {
          type: "array",
          items: {
            $ref: "#/definitions/Todo",
          },
        },
      },
    },
  };

  POST.apiDoc = {
    summary: "Create todo.",
    operationId: "createTodo",
    consumes: ["application/json"],
    parameters: [
      {
        in: "body",
        name: "todo",
        schema: {
          $ref: "#/definitions/Todo",
        },
      },
    ],
    responses: {
      201: {
        description: "Created",
      },
    },
  };

  PUT.apiDoc = {
    summary: "Update todo.",
    operationId: "updateTodo",
    parameters: [
      {
        in: "query",
        name: "id",
        required: true,
        type: "string",
      },
      {
        in: "body",
        name: "todo",
        schema: {
          $ref: "#/definitions/Todo",
        },
      },
    ],
    responses: {
      200: {
        description: "Updated ok",
      },
    },
  };

  DELETE.apiDoc = {
    summary: "Delete todo.",
    operationId: "deleteTodo",
    consumes: ["application/json"],
    parameters: [
      {
        in: "query",
        name: "id",
        required: true,
        type: "string",
      },
    ],
    responses: {
      200: {
        description: "Delete",
      },
    },
  };

  return operations;
};
</code></pre>
<ol start="5">
<li>添加自动生成的文档，<strong><a href="https://github.com/scottie1984/swagger-ui-express">swagger-ui-express</a></strong>：</li>
</ol>
<pre><code class="language-bash">npm i swagger-ui-express -s
</code></pre>
<pre><code class="language-javascript">// ./app.js

...

// OpenAPI UI
app.use(
  "/api-documentation",
  swaggerUi.serve,
  swaggerUi.setup(null, {
    swaggerOptions: {
      url: "http://localhost:3030/api-docs",
    },
  })
);

module.exports = app;
</code></pre>
<p>这就是我们最终获得的效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-23.png" alt="image-23" width="600" height="400" loading="lazy"></p>
<p>这个 SwaggerUi 是自动生成的，你可以在 <a href="http://localhost:3030/api-documentation">http://localhost:3030/api-documentation</a> 访问它。</p>
<p>🎉 <strong>恭喜！</strong></p>
<p>当你进行到文章的这里时，你应该创建好了一个完全可运行的 Express 应用程序，其与 OpenAPI 完全集成。</p>
<p>现在，通过使用在 <em><a href="http://localhost:3030/api-docs">http://localhost:3030/api-docs</a></em> 中定义的模型，我们可以轻松生成<a href="https://nordicapis.com/generating-web-api-tests-from-an-openapi-specification/">测试</a>、<a href="https://github.com/stoplightio/prism">模拟服务器</a>、<a href="https://github.com/drwpow/openapi-typescript">类型</a>，甚至<a href="https://phrase.com/blog/posts/using-openapi-to-generate-api-client-code/">客户端</a>！</p>
<h2 id="">总结</h2>
<p>我们只是浅浅涉猎了 OpenAPI 所能做到的事情。但是我希望这篇文章能够让你了解标准 API 定义模式是如何在可见性、测试、文档和整体置信度方面帮助构建 REST API 的。</p>
<p>谢谢你看到最后！</p>
<p>我目前正在构建 <a href="https://taggr.ai/"><strong><strong>taggr</strong></strong></a>，这是一个跨平台的桌面应用程序，它在帮助用户<strong>重新发现</strong>他们的数字记忆的同时保持他们的<strong>隐私</strong>。</p>
<p>Linux、Windows 和 macOS 平台上的 alpha 版本即将推出。请查看<a href="https://taggr.ai/">网页</a>并<a href="https://taggr.us18.list-manage.com/subscribe/post?u=482d473aa1e4dedadc89fb3e2&amp;id=aa6a10c164">登记</a>，以免错过！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Express + Node.js 手册——面向初学者的 Express JavaScript 框架（2022 版） ]]>
                </title>
                <description>
                    <![CDATA[ 什么是 Express？ Express 是一个基于 Node.js 的 Web 框架。 Node.js 是一种搭建网络服务和应用的实用工具。 Express 搭建在 Node.js 之上，提供易于使用的功能来满足 Web 服务器的用例需求。它开源、免费、易于扩展并且非常高效。 可以使用各种各样的预构建的包来处理应用中的各项内容。 点击获取 Express 手册的 PDF 和 ePub 版本 [https://thevalleyofcode.com/download/express/]。 目录  * 如何安装 Express  * 第一个 "Hello, World"  * 请求参数  * 如何向客户端发送响应  * 如何发送一个 JSON 响应  * 如何管理 Cookies ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-express-handbook/</link>
                <guid isPermaLink="false">63a03e6fa7bffa07c7441952</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Mon, 19 Dec 2022 04:36:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/12/pexels-paul-ijsendoorn-1181202.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/the-express-handbook/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">The Express +  Node.js Handbook – Learn the Express JavaScript Framework for Beginners</a>
      </p><!--kg-card-begin: markdown--><h2 id="express">什么是 Express？</h2>
<p>Express 是一个基于 Node.js 的 Web 框架。</p>
<p>Node.js 是一种搭建网络服务和应用的实用工具。</p>
<p>Express 搭建在 Node.js 之上，提供易于使用的功能来满足 Web 服务器的用例需求。它开源、免费、易于扩展并且非常高效。</p>
<p>可以使用各种各样的预构建的包来处理应用中的各项内容。</p>
<p><a href="https://thevalleyofcode.com/download/express/">点击获取 Express 手册的 PDF 和 ePub 版本</a>。</p>
<h2 id="">目录</h2>
<ul>
<li><a href="#how-to-install-express">如何安装 Express</a></li>
<li><a href="#the-first-hello-world-example">第一个 "Hello, World"</a></li>
<li><a href="#request-parameters">请求参数</a></li>
<li><a href="#how-to-send-a-response-to-the-client">如何向客户端发送响应</a></li>
<li><a href="#how-to-send-a-json-response">如何发送一个 JSON 响应</a></li>
<li><a href="#how-to-manage-cookies">如何管理 Cookies</a></li>
<li><a href="#how-to-work-with-http-headers">如何处理 HTTP 标头</a></li>
<li><a href="#how-to-handle-redirects">如何处理重定向</a></li>
<li><a href="#routing-in-express">Express 中的路由</a></li>
<li><a href="#templates-in-express">Express 中的模版</a></li>
<li><a href="#express-middleware">Express 中间件</a></li>
<li><a href="#how-to-serve-static-assets-with-express">如何使用 Express 提供静态资源</a></li>
<li><a href="#how-to-send-files-to-the-client">如何向客户端发送文件</a></li>
<li><a href="#sessions-in-express">Express 中的会话</a></li>
<li><a href="#how-to-validate-input-in-express">如何在 Express 中验证输入</a></li>
<li><a href="#how-to-sanitize-input-in-express">如何在 Express 中清理输入</a></li>
<li><a href="#how-to-handle-forms-in-express">如何在 Express 中处理表单</a></li>
<li><a href="#how-to-handle-file-uploads-in-forms-in-express">如何在 Express 中处理表单中的文件上传</a></li>
</ul>
<h2 id="how-to-install-express">如何安装 Express</h2>
<p>你可以使用 npm 在任何项目中安装 Express。</p>
<p>如果是一个空文件夹，首先使用命令创建一个新的 Node.js 项目：</p>
<pre><code>npm init -y
</code></pre>
<p>然后执行：</p>
<pre><code>npm install express
</code></pre>
<p>在项目中安装 Express。</p>
<h2 id="the-first-hello-world-example">第一个 "Hello World"</h2>
<p>我们要创建的第一个示例是一个简单的 Express Web 服务器：</p>
<p>请复制以下代码：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.get('/', (req, res) =&gt; res.send('Hello World!'));
app.listen(3000, () =&gt; console.log('Server ready'));
</code></pre>
<p>并将其保存在项目根文件夹的 <code>index.js</code> 文件中，然后通过以下命令启动服务器：</p>
<pre><code>node index.js
</code></pre>
<p>你可以打开浏览器并通过 localhost 导航到 port 3000，就会看到 <code>Hello World!</code> 信息。</p>
<p>上面四行代码在幕后做了很多工作：</p>
<p>首先我们通过 <code>express</code> 变量引用 <code>express</code> 包。</p>
<p>通过调用 <code>express()</code> 方法来实例化一个应用。</p>
<p>一旦创建了应用对象，就使用 <code>get()</code> 方法监听来自 <code>/</code> 路径的 GET 请求。</p>
<p>每一种 HTTP 方法都有一个对应的<strong>动词</strong>：<code>get()</code>、<code>post()</code>、<code>put()</code>、<code>delete()</code>和 <code>patch()</code>：</p>
<pre><code class="language-js">app.get('/', (req, res) =&gt; {
  /* */
});
app.post('/', (req, res) =&gt; {
  /* */
});
app.put('/', (req, res) =&gt; {
  /* */
});
app.delete('/', (req, res) =&gt; {
  /* */
});
app.patch('/', (req, res) =&gt; {
  /* */
});
</code></pre>
<p>这些方法接受一个回调函数（当请求开始时调用），我们需要处理回调函数。</p>
<p>可以在回调中传入一个箭头函数：</p>
<pre><code class="language-js">(req, res) =&gt; res.send('Hello World!');
</code></pre>
<p>Express 在回调中发送两个对象：<code>req</code> 和<code>res</code>，分别代表了请求（Request）和响应（Response）对象。</p>
<p>更多标准可以参考<a href="https://developer.mozilla.org/en-US/docs/Web/API/Request">这里</a>和<a href="https://developer.mozilla.org/en-US/docs/Web/API/Response">这里</a>。</p>
<p>请求是一个 HTTP 请求，它包含了所有请求信息：包括请求参数、标头、请求体等。</p>
<p>响应是 HTTP 响应对象，会返回给客户端。</p>
<p>在这个回调示例中，我们通过<code>Response.send()</code>方法发送 "Hello World!" 字符串给客户端。</p>
<p>这个方法将字符串作为请求体，传输完毕后关闭连接。</p>
<p>Hello World 示例中最后一行代码启动服务器，并且告诉它在 port<code>3000</code>监听。我们传入一个回调，当服务器准备好接受新请求时调用该回调。</p>
<h2 id="request-parameters">请求参数</h2>
<p>我介绍了 Request 对象是如何持有 HTTP 请求信息的。</p>
<p>以下是主要的属性：</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>介绍</th>
</tr>
</thead>
<tbody>
<tr>
<td>.app</td>
<td>持有对 Express app 对象的引用</td>
</tr>
<tr>
<td>.baseUrl</td>
<td>app 响应的基本路径</td>
</tr>
<tr>
<td>.body</td>
<td>包含在请求体提交的数据（必须手动解析和填充后才能访问）</td>
</tr>
<tr>
<td>.cookies</td>
<td>包含由请求发送的 cookies（需要 <code>cookie-parser</code>中间件）</td>
</tr>
<tr>
<td>.hostname</td>
<td><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host">Host HTTP 标头</a>值定义的主机名</td>
</tr>
<tr>
<td>.ip</td>
<td>客户端 IP</td>
</tr>
<tr>
<td>.method</td>
<td>使用的 HTTP 方法</td>
</tr>
<tr>
<td>.params</td>
<td>路由命名参数</td>
</tr>
<tr>
<td>.path</td>
<td>URL 路径</td>
</tr>
<tr>
<td>.protocol</td>
<td>请求协议</td>
</tr>
<tr>
<td>.query</td>
<td>包含请求中使用的所有查询字符串的对象</td>
</tr>
<tr>
<td>.secure</td>
<td>请求是安全时（使用 HTTPS）为 true</td>
</tr>
<tr>
<td>.signedCookies</td>
<td>包含由请求发送的签名 cookies（需要 <code>cookie-parser</code> 中间件）</td>
</tr>
<tr>
<td>.xhr</td>
<td>请求为 <a href="https://www.freecodecamp.org/news/xhr/">XMLHttpRequest</a>时为 true</td>
</tr>
</tbody>
</table>
<h2 id="how-to-send-a-response-to-the-client">如何向客户端发送响应</h2>
<p>在 Hello World 示例中， 我们使用响应对象的<code>send()</code>方法来将一个简单的字符串作为响应，之后关闭连接。</p>
<pre><code class="language-js">(req, res) =&gt; res.send('Hello World!');
</code></pre>
<p>如果你传入一个字符串，它将<code>Content-Type</code>标头设置为<code>text/html</code>。</p>
<p>如果你传入的是对象或者数组，它将<code>Content-Type</code>标头设置为<code>application/json</code>，并将传入的对象或数组解析为<a href="https://www.freecodecamp.org/news/json/">JSON</a>。</p>
<p>之后<code>send()</code>关闭连接。</p>
<p><code>send()</code>自动设置 <code>Content-Length</code> HTTP 响应标头，不像 <code>end()</code> 需要你手动设置。</p>
<h3 id="end">如何使用 end() 发送空响应</h3>
<p>另一个发送响应的方式，是使用<code>Response.end()</code>方法，这种方法不发送任何响应体：</p>
<pre><code class="language-js">res.end();
</code></pre>
<h3 id="http">如何设置 HTTP 响应状态</h3>
<p>在响应对象使用 <code>status()</code> 方法：</p>
<pre><code class="language-js">res.status(404).end();
</code></pre>
<p>或者</p>
<pre><code class="language-js">res.status(404).send('File not found');
</code></pre>
<p><code>sendStatus()</code> 是快捷方式。</p>
<pre><code class="language-js">res.sendStatus(200);
// === res.status(200).send('OK')

res.sendStatus(403);
// === res.status(403).send('Forbidden')

res.sendStatus(404);
// === res.status(404).send('Not Found')

res.sendStatus(500);
// === res.status(500).send('Internal Server Error')
</code></pre>
<h2 id="how-to-send-a-json-response">如何发送一个 JSON 响应</h2>
<p>当你在 Express 中监听路由上的连接时，回调函数将在每次网络调用时被调用，并带有一个 Request 对象实例和一个 Response 对象实例。</p>
<p>示例：</p>
<pre><code class="language-js">app.get('/', (req, res) =&gt; res.send('Hello World!'));
</code></pre>
<p>我们在这里使用 <code>Response.send()</code> 方法，接受任意字符串。</p>
<p>你可以使用<a href="https://www.freecodecamp.org/news/json/">JSON</a>，即使用 <code>Response.json()</code>发送到客户端。</p>
<p>它接受一个对象或者数组，并在发送前将其转换为 JSON 格式：</p>
<pre><code class="language-js">res.json({ username: 'Flavio' });
</code></pre>
<h2 id="how-to-manage-cookies">如何管理 cookies</h2>
<p>使用<code>Response.cookie()</code>方法来控制 cookies。</p>
<p>示例：</p>
<pre><code class="language-js">res.cookie('username', 'Flavio');
</code></pre>
<p>这个方法还接受第三个参数，这个参数包含各种选项：</p>
<pre><code class="language-js">res.cookie('username', 'Flavio', {
  domain: '.flaviocopes.com',
  path: '/administrator',
  secure: true
});

res.cookie('username', 'Flavio', {
  expires: new Date(Date.now() + 900000),
  httpOnly: true
});
</code></pre>
<p>一些你可以设置的有用的参数包括：</p>
<table>
<thead>
<tr>
<th>值</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>domain</code></td>
<td><a href="https://www.freecodecamp.org/news/cookies/#set-a-cookie-domain">cookie 的域名</a></td>
</tr>
<tr>
<td><code>expires</code></td>
<td>设置 <a href="https://www.freecodecamp.org/news/cookies/#set-a-cookie-expiration-date">cookie 过期日期</a>。 如果未设置或者为 0，这个 cookie 将作为会话 cookie。</td>
</tr>
<tr>
<td><code>httpOnly</code></td>
<td>设置 cookie 仅被 web 服务器访问。 具体可查看 <a href="https://www.freecodecamp.org/news/cookies/#httponly">HttpOnly</a></td>
</tr>
<tr>
<td><code>maxAge</code></td>
<td>设置相对于当前时间的过期时间，以毫秒为单位</td>
</tr>
<tr>
<td><code>path</code></td>
<td><a href="https://www.freecodecamp.org/news/cookies/#set-a-cookie-path">cookie 路径</a>。默认值为 '/'</td>
</tr>
<tr>
<td><code>secure</code></td>
<td>标记为<a href="https://www.freecodecamp.org/news/cookies/#secure">cookie HTTPS only</a></td>
</tr>
<tr>
<td><code>signed</code></td>
<td>设置需要签名的 cookie</td>
</tr>
<tr>
<td><code>sameSite</code></td>
<td><a href="https://www.freecodecamp.org/news/cookies/#samesite"><code>SameSite</code></a>的值</td>
</tr>
</tbody>
</table>
<p>清除 cookie 可以使用：</p>
<pre><code class="language-js">res.clearCookie('username');
</code></pre>
<h2 id="how-to-work-with-http-headers">如何处理 HTTP 标头</h2>
<h3 id="http">如何通过请求获取 HTTP 标头值</h3>
<p>可以使用 <code>Request.headers</code> 属性访问所有 HTTP 标头的值：</p>
<pre><code class="language-js">app.get('/', (req, res) =&gt; {
  console.log(req.headers);
});
</code></pre>
<p>使用<code>Request.header()</code> 方法获取单个请求标头的值：</p>
<pre><code class="language-js">app.get('/', (req, res) =&gt; {
  req.header('User-Agent');
});
</code></pre>
<h3 id="http">如何为响应改变 HTTP 标头</h3>
<p>可以使用 <code>Response.set()</code>改变 HTTP 标头值：</p>
<pre><code class="language-js">res.set('Content-Type', 'text/html');
</code></pre>
<p>Content-Type 标头的便捷方式是：</p>
<pre><code class="language-js">res.type('html');
// =&gt; 'text/html'

res.type('json');
// =&gt; 'application/json'

res.type('application/json');
// =&gt; 'application/json'

res.type('png');
// =&gt; image/png:
</code></pre>
<h2 id="how-to-handle-redirects">如何处理重定向</h2>
<p>在 Web 开发中重定向很常见，可以使用 <code>Response.redirect()</code>实现：</p>
<pre><code class="language-js">res.redirect('/go-there');
</code></pre>
<p>这样就创建了一个 302 重定向。</p>
<p>可以使用以下方法创建一个 301 重定向：</p>
<pre><code class="language-js">res.redirect(301, '/go-there');
</code></pre>
<p>你可以指定绝对路径（<code>/go-there</code>）、绝对 URL（<code>https://anothersite.com</code>）、相对路径（<code>go-there</code>）或者 <code>..</code> 返回上一层：</p>
<pre><code class="language-js">res.redirect('../go-there');
res.redirect('..');
</code></pre>
<p>你还可以重定向回 Referrer HTTP 标头值（如果未设置默认值为 <code>/</code>）：</p>
<pre><code class="language-js">res.redirect('back');
</code></pre>
<h2 id="routing-in-express">Express 中的路由</h2>
<p>路由是确定调用 URL 时应该发生什么的过程，或者应用程序的哪些部分应该处理特定的传入请求。</p>
<p>在 Hello World 示例中，我们使用了这段代码：</p>
<pre><code class="language-js">app.get('/', (req, res) =&gt; {
  /* */
});
</code></pre>
<p>这里创建一个路由，访问根域 URL<code>/</code>，并使用 HTTP GET 方法映射到我们需要的响应。</p>
<h3 id="">命名参数</h3>
<p>如果想监听自定义请求怎么办？也许我们想创建一个接受字符串并将其作为大写字母返回的服务——我们不希望参数作为查询字符串发送，而是作为 URL 的一部分发送。在这种情况下，我们使用命名参数：</p>
<pre><code class="language-js">app.get('/uppercase/:theValue', (req, res) =&gt;
  res.send(req.params.theValue.toUpperCase())
);
</code></pre>
<p>如果发送请求到 <code>/uppercase/test</code>，我们会在响应体中得到 <code>TEST</code>。</p>
<p>你可以在同一个 URL 中使用多个命名参数，它们都将存储在 <code>req.params</code>.</p>
<h3 id="">如何使用正则表达式匹配路径</h3>
<p>可以通过一条<a href="https://flaviocopes.com/javascript-regular-expressions/">正则表达式</a> 来匹配多个路径：</p>
<pre><code class="language-js">app.get(/post/, (req, res) =&gt; {
  /* */
});
</code></pre>
<p>以上代码将匹配 <code>/post</code>, <code>/post/first</code>, <code>/thepost</code>, <code>/posting/something</code>等路径。</p>
<h2 id="templates-in-express">Express 中的模版</h2>
<p>Express 能够处理服务器端模板引擎。</p>
<p>模板引擎允许我们向视图添加数据，并动态生成 HTML。</p>
<p>Express 默认使用 Jade。Jade 是 Pug 的旧版本，特指 Pug 1.0。</p>
<p>请注意，由于商标问题，该项目在 2016 年发布第二版时，名称从 Jade 改为 Pug。你仍然可以使用 Jade，又称 Pug 1.0，但往后最好使用 Pug 2.0。</p>
<p>尽管 Jade 的最后一个版本已经陈旧了，但出于向后兼容的原因，它仍然是 Express 中的默认版本。</p>
<p>你可以在任何新项目中使用 Pug 或你选择的引擎。Pug 的官方网站是<a href="https://pugjs.org/">https://pugjs.org/</a>。</p>
<p>可以使用许多不同的模板引擎，包括 Pug、Handlebars、Mustache、EJS 等。</p>
<p>要使用 Pug，我们必须先安装它：</p>
<pre><code class="language-bash">npm install pug
</code></pre>
<p>在初始化 Express 应用程序时，我们需要设置它：</p>
<pre><code class="language-js">const express = require('express');
const app = express();
app.set('view engine', 'pug');
</code></pre>
<p>然后就可以在 <code>.pug</code> 文件中编写模板。</p>
<p>创建一个 about 视图：</p>
<pre><code class="language-js">app.get('/about', (req, res) =&gt; {
  res.render('about');
});
</code></pre>
<p>模板路径为<code>views/about.pug</code>:</p>
<pre><code>p Hello from Flavio
</code></pre>
<p>该模板创建一个 <code>p</code> 标签，内容为 <code>Hello from Flavio</code>。</p>
<p>你也可以使用以下代码插入变量：</p>
<pre><code class="language-js">app.get('/about', (req, res) =&gt; {
  res.render('about', { name: 'Flavio' });
});
</code></pre>
<pre><code>p Hello from #{name}
</code></pre>
<p>更多使用 Pug 的信息，可以查看 <a href="https://flaviocopes.com/pug">Pug 指南</a>。</p>
<p>推荐你使用这个在线 HTML 到 Pug 转换器 <a href="https://html-to-pug.com/">https://html-to-pug.com/</a>。</p>
<h2 id="express-middleware">Express 中间件</h2>
<p>中间件是一个挂钩到路由过程中的函数，在链中的某个点执行任意操作（取决于我们想要它做什么）。</p>
<p>它通常用于编辑请求或响应对象，或者在请求到达路由处理程序代码之前终止请求。</p>
<p>可以通过如下方法将中间件添加到执行栈：</p>
<pre><code class="language-js">app.use((req, res, next) =&gt; {
  /* */
});
</code></pre>
<p>这和定义路由类似，但是在 Request 和 Response 实例之后，我们还引用了 <em>next</em> 中间件函数，并分配给了<code>next</code>变量。</p>
<p>我们总是在中间件函数末尾调用<code>next()</code>以便将执行传递给下一个处理程序。除非我们想提前结束响应并将其发送回客户端。</p>
<p>通常我们通过 <code>npm</code> 包来使用预制的中间件。你可以在<a href="https://expressjs.com/en/resources/middleware.html">这里</a>找到中间件列表。</p>
<p>其中一个预制中间件示例就是 <code>cookie-parser</code>，它可以将 cookie 解析为 <code>req.cookies</code>对象。 你可以使用 <code>npm install cookie-parser</code> 命令安装并使用：</p>
<pre><code class="language-js">const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');

app.get('/', (req, res) =&gt; res.send('Hello World!'));

app.use(cookieParser());
app.listen(3000, () =&gt; console.log('Server ready'));
</code></pre>
<p>我们还可以将中间件函数设置为仅针对特定路由运行（而不是针对所有路由），方法是将其作为路由定义的第二个参数：</p>
<pre><code class="language-js">const myMiddleware = (req, res, next) =&gt; {
  /* ... */
  next();
};

app.get('/', myMiddleware, (req, res) =&gt; res.send('Hello World!'));
</code></pre>
<p>如果需要存储中间件生成的数据，并传递给后续中间件函数或请求处理程序，你可以使用<code>Request.locals</code>对象。它将该数据附加到当前请求：</p>
<pre><code class="language-js">req.locals.name = 'Flavio';
</code></pre>
<h2 id="how-to-serve-static-assets-with-express">如何使用 Express 提供静态资源</h2>
<p>通常图片、CSS 被存储在 <code>public</code>子文件夹，并暴露给根目录：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.use(express.static('public'));

/* ... */

app.listen(3000, () =&gt; console.log('Server ready'));
</code></pre>
<p>如果在 <code>public/</code>有一个<code>index.html</code>文件，当你访问根域 URL（<code>http://localhost:3000</code>）时，就会提供静态资源。</p>
<h2 id="how-to-send-files-to-the-client">如何向客户端发送文件</h2>
<p>Express 提供了一个简便的方法将文件转换为附件传输： <code>Response.download()</code>。</p>
<p>一旦用户点击使用此方法发送文件的路由，浏览器将提示用户下载。</p>
<p><code>Response.download()</code> 方法允许发送附加到请求的文件，浏览器不会在页面中显示它，而是将其保存到磁盘。</p>
<pre><code class="language-js">app.get('/', (req, res) =&gt; res.download('./file.pdf'));
</code></pre>
<p>在应用上下文中的示例：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.get('/', (req, res) =&gt; res.download('./file.pdf'));
app.listen(3000, () =&gt; console.log('Server ready'));
</code></pre>
<p>你可以将文件设置为使用自定义文件名发送：</p>
<pre><code class="language-js">res.download('./file.pdf', 'user-facing-filename.pdf');
</code></pre>
<p>此方法提供了一个回调函数，你可以使用它在文件发送后执行代码：</p>
<pre><code class="language-js">res.download('./file.pdf', 'user-facing-filename.pdf', (err) =&gt; {
  if (err) {
    //handle error
    return;
  } else {
    //do something
  }
});
</code></pre>
<h2 id="sessions-in-express">Express 中的会话</h2>
<p>默认情况下，Express 请求是有顺序的，但请求之间没有相互链接，所以没有办法知道这个请求是否来自之前已经执行过请求的客户端。</p>
<p>除非使用某种识别机制，否则用户无法识别请求。</p>
<p>会话就应运而生。</p>
<p>使用会话后，你的 API 或网站的每个用户都将被分配一个唯一的会话，就可以存储用户的状态。</p>
<p>我们将使用 <code>express-session</code> 模块来演示，它由 Express 团队维护。</p>
<p>可以使用以下命令安装：</p>
<pre><code class="language-bash">npm install express-session
</code></pre>
<p>安装完毕后，可以通过以下代码实例化：</p>
<pre><code class="language-js">const session = require('express-session');
</code></pre>
<p>它是一个中间件，所以你使用以下代码在 Express 中 <em>安装</em> 它：</p>
<pre><code class="language-js">const express = require('express');
const session = require('express-session');

const app = express();
app.use(
  session({
    secret: '343ji43j4n3jn4jk3n'
  })
);
</code></pre>
<p>编写完毕后，所有应用路由都使用会话。</p>
<p><code>secret</code>是唯一的必填参数，还有许多可选参数。<code>secret</code>必须为一个唯一的随机字符串。</p>
<p>会话被添加到请求，所以可以通过 <code>req.session</code>访问：</p>
<pre><code class="language-js">app.get('/', (req, res, next) =&gt; {
  // req.session
}
</code></pre>
<p>该对象可用于从会话中获取数据，也可用于设置数据：</p>
<pre><code class="language-js">req.session.name = 'Flavio';
console.log(req.session.name); // 'Flavio'
</code></pre>
<p>此数据在存储时被序列化为 <a href="https://www.freecodecamp.org/news/json/">JSON</a>，所以可以安全使用嵌套对象。</p>
<p>你可以使用会话将数据传递给稍后执行的中间件，或者稍后根据后续请求检索数据。</p>
<p>会话数据存储在哪里？这取决于你如何设置<code>express-session</code>模块。</p>
<p>会话数据可被存储在：</p>
<ul>
<li><strong>内存</strong>，不适用于生产</li>
<li><strong>数据库</strong>，如 MySQL 或者 Mongo</li>
<li><strong>内存缓存</strong>，如 Redis 或者 Memcached</li>
</ul>
<p>在 <a href="https://github.com/expressjs/session">https://github.com/expressjs/session</a> 中有一个巨大的第三方包列表，可以实现不同兼容性的缓存存储。</p>
<p>所有解决方案都将会话 ID 存储在 cookie 中，并将数据保存在服务器端。客户端将在 cookie 中接收会话 ID，并将它与每个 HTTP 请求一起发送。</p>
<p>我们将引用该服务器端以将会话 ID 与本地存储的数据相关联。</p>
<p>内存是默认设置，不需要你进行特殊操作。这样很便捷，但它仅用于开发目的。</p>
<p>最好是选择 Redis 之类的内存缓存，需要为其设置自己的基础架构。</p>
<p>另一个常用管理会话的包是<code>cookie-session</code>，与 Redis 巨大的不同是将数据存储在客户端的 cookie。</p>
<p>我不建议这样做，因为将数据存储在 cookie 中意味着它存储在客户端，并在用户发出的每个请求中来回发送。它的大小也有限制，因为它只能存储 4 KB 的数据。</p>
<p>Cookie 也需要受到保护，但默认情况它不受保护，安全 Cookie 可以在 HTTPS 站点上使用，如果你使用代理上网，则需要配置它。</p>
<h2 id="how-to-validate-input-in-express">如何在 Express 中验证输入</h2>
<p>让我们看看如何验证作为输入进入 Express 端点的任何数据。</p>
<p>假设你有一个接受 name、email 和 age 参数的 POST 端点：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.use(express.json());

app.post('/form', (req, res) =&gt; {
  const name = req.body.name;
  const email = req.body.email;
  const age = req.body.age;
});
</code></pre>
<p>如何对这些结果执行服务器端验证以确保：</p>
<ul>
<li>name 是包含至少 3 个字符的字符串？</li>
<li>email 是真正的邮箱地址？</li>
<li>age 为 0 到 110 之间的数字？</li>
</ul>
<p>在 Express 中处理来自外部的任何输入的验证的最佳方法是使用 <a href="https://express-validator.github.io"><code>express-validator</code> 包</a>：</p>
<pre><code class="language-bash">npm install express-validator
</code></pre>
<p>引用包中的<code>check</code>和<code>validationResult</code>对象：</p>
<pre><code class="language-js">const { check, validationResult } = require('express-validator');
</code></pre>
<p>在<code>post()</code>调用中，我们将包含<code>check()</code>调用的数组作为第二个参数传入。每一个 <code>check()</code> 都接受参数名作为实参。最后调用<code>validationResult()</code>来验证是否有验证报错，如果有就告知客户端：</p>
<pre><code class="language-js">app.post(
  '/form',
  [
    check('name').isLength({ min: 3 }),
    check('email').isEmail(),
    check('age').isNumeric()
  ],
  (req, res) =&gt; {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(422).json({ errors: errors.array() });
    }

    const name = req.body.name;
    const email = req.body.email;
    const age = req.body.age;
  }
);
</code></pre>
<p>注意我使用了：</p>
<ul>
<li><code>isLength()</code></li>
<li><code>isEmail()</code></li>
<li><code>isNumeric()</code></li>
</ul>
<p>还有更多方法，都来自 <a href="https://github.com/chriso/validator.js#validators">validator.js</a>，包括：</p>
<ul>
<li><code>contains()</code>, 检查是否包含指定值</li>
<li><code>equals()</code>, 检查是否与指定值相等</li>
<li><code>isAlpha()</code></li>
<li><code>isAlphanumeric()</code></li>
<li><code>isAscii()</code></li>
<li><code>isBase64()</code></li>
<li><code>isBoolean()</code></li>
<li><code>isCurrency()</code></li>
<li><code>isDecimal()</code></li>
<li><code>isEmpty()</code></li>
<li><code>isFQDN()</code>, 检查是否为完全合格的域名</li>
<li><code>isFloat()</code></li>
<li><code>isHash()</code></li>
<li><code>isHexColor()</code></li>
<li><code>isIP()</code></li>
<li><code>isIn()</code>, 检查值是否属于允许值数组</li>
<li><code>isInt()</code></li>
<li><code>isJSON()</code></li>
<li><code>isLatLong()</code></li>
<li><code>isLength()</code></li>
<li><code>isLowercase()</code></li>
<li><code>isMobilePhone()</code></li>
<li><code>isNumeric()</code></li>
<li><code>isPostalCode()</code></li>
<li><code>isURL()</code></li>
<li><code>isUppercase()</code></li>
<li><code>isWhitelisted()</code>, 检查输入是否在白名单内</li>
</ul>
<p>你也可以使用<code>matches()</code>来进行正则表达式验证。</p>
<p>日期可以通过以下方式验证：</p>
<ul>
<li><code>isAfter()</code>, 检查输入的日期是否在你传入的日期之后</li>
<li><code>isBefore()</code>, 检查输入的日期是否在你传入的日期之前</li>
<li><code>isISO8601()</code></li>
<li><code>isRFC3339()</code></li>
</ul>
<p>更多如何使用验证器的方法，<a href="https://github.com/chriso/validator.js#validators">可以参考这份文档</a>。</p>
<p>所有上述验证都可以串联起来：</p>
<pre><code class="language-js">check('name').isAlpha().isLength({ min: 10 });
</code></pre>
<p>如果有任何错误，服务器会自动发送一个响应来传达错误。例如，如果电子邮件无效，将返回以下内容：</p>
<pre><code class="language-js">{
  "errors": [{
    "location": "body",
    "msg": "Invalid value",
    "param": "email"
  }]
}
</code></pre>
<p>可以使用 <code>withMessage()</code>覆盖默认报错：</p>
<pre><code class="language-js">check('name')
  .isAlpha()
  .withMessage('Must be only alphabetical chars')
  .isLength({ min: 10 })
  .withMessage('Must be at least 10 chars long');
</code></pre>
<p>如果你想要编写自定义验证器，可以使用 <code>custom</code> 验证器。</p>
<p>在回调函数中，你可以通过抛出异常或返回被拒绝的 promise 来拒绝验证：</p>
<pre><code class="language-js">app.post(
  '/form',
  [
    check('name').isLength({ min: 3 }),
    check('email').custom((email) =&gt; {
      if (alreadyHaveEmail(email)) {
        throw new Error('Email already registered');
      }
    }),
    check('age').isNumeric()
  ],
  (req, res) =&gt; {
    const name = req.body.name;
    const email = req.body.email;
    const age = req.body.age;
  }
);
</code></pre>
<p>自定义验证器：</p>
<pre><code class="language-js">check('email').custom((email) =&gt; {
  if (alreadyHaveEmail(email)) {
    throw new Error('Email already registered');
  }
});
</code></pre>
<p>也可改写为：</p>
<pre><code class="language-js">check('email').custom((email) =&gt; {
  if (alreadyHaveEmail(email)) {
    return Promise.reject('Email already registered');
  }
});
</code></pre>
<h2 id="how-to-sanitize-input-in-express">如何在 Express 清理输入</h2>
<p>你已经了解了如何验证从外部世界到 Express 应用程序的输入。</p>
<p>当运行面向公众的服务器时，你很快就会学到一件事：永远不要相信输入。</p>
<p>即使在客户端你已经预做了一遍清理，确保人们不会输入奇怪的内容，你仍然会受到人们使用工具（甚至只是浏览器开发工具）直接 POST 到端点的影响。</p>
<p>或者机器人尝试人类已知的每一种可能的攻击组合。</p>
<p>需要做的是在服务器端清理输入。</p>
<p><a href="https://express-validator.github.io"><code>express-validator</code> 包</a>除了可以验证输入以外也可以清理输入。</p>
<p>假设你有一个接受 name、email 和 age 参数的 POST 端点：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.use(express.json());

app.post('/form', (req, res) =&gt; {
  const name = req.body.name;
  const email = req.body.email;
  const age = req.body.age;
});
</code></pre>
<p>你可以这样验证输入：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.use(express.json());

app.post(
  '/form',
  [
    check('name').isLength({ min: 3 }),
    check('email').isEmail(),
    check('age').isNumeric()
  ],
  (req, res) =&gt; {
    const name = req.body.name;
    const email = req.body.email;
    const age = req.body.age;
  }
);
</code></pre>
<p>你可以通过在验证方法之后串联清理方法来添加清理：</p>
<pre><code class="language-js">app.post(
  '/form',
  [
    check('name').isLength({ min: 3 }).trim().escape(),
    check('email').isEmail().normalizeEmail(),
    check('age').isNumeric().trim().escape()
  ],
  (req, res) =&gt; {
    //...
  }
);
</code></pre>
<p>在代码中我使用的清理方法包括：</p>
<ul>
<li><code>trim()</code>修剪字符串开头和结尾的字符（默认为空格）</li>
<li><code>escape()</code>将 <code>&lt;</code>, <code>&gt;</code>, <code>&amp;</code>, <code>'</code>, <code>"</code>和 <code>/</code>替换成它们对应的 HTML 实体</li>
<li><code>normalizeEmail()</code>规范化电子邮件地址，它接受小写邮件地址或者子地址的选项。（如 <code>flavio+newsletters@gmail.com</code>）</li>
</ul>
<p>其他的清理方法包括：</p>
<ul>
<li><code>blacklist()</code> 删除出现在黑名单中的字符</li>
<li><code>whitelist()</code> 删除未出现在白名单中的字符</li>
<li><code>unescape()</code> 将 HTML 编码的实体替换为<code>&lt;</code>, <code>&gt;</code>, <code>&amp;</code>, <code>'</code>, <code>"</code> 和 <code>/</code></li>
<li><code>ltrim()</code> 类似于 trim()，但只修剪字符串开头的字符</li>
<li><code>rtrim()</code> 类似于 trim()， 但只修剪字符串末尾的字符</li>
<li><code>stripLow()</code>删除通常不可见的 ASCII 控制字符</li>
</ul>
<p>强制转换格式：</p>
<ul>
<li><code>toBoolean()</code> 将输入字符串转换为布尔值。除了 '0'、'false' 和 '' 之外的所有内容都返回 true。在严格模式下，只有 '1' 和 'true' 返回 true。</li>
<li><code>toDate()</code> 将输入字符串转换为日期，如果输入不是日期，则为 null。</li>
<li><code>toFloat()</code> 将输入字符串转换为浮点数，如果输入不是浮点数，则转换为 NaN。</li>
<li><code>toInt()</code>将输入字符串转换为整数，如果输入不是整数，则转换为 NaN。</li>
</ul>
<p>与自定义验证器一样，你可以创建自定义清理器。</p>
<p>在回调函数中，你只需返回清理后的值：</p>
<pre><code class="language-js">const sanitizeValue = (value) =&gt; {
  //sanitize...
};

app.post(
  '/form',
  [
    check('value').customSanitizer((value) =&gt; {
      return sanitizeValue(value);
    })
  ],
  (req, res) =&gt; {
    const value = req.body.value;
  }
);
</code></pre>
<h2 id="how-to-handle-forms-in-express">如何在 Express 中处理表单</h2>
<p>以下是一个 HTML 表单示例：</p>
<pre><code class="language-html">&lt;form method="POST" action="/submit-form"&gt;
  &lt;input type="text" name="username" /&gt;
  &lt;input type="submit" /&gt;
&lt;/form&gt;
</code></pre>
<p>当用户按下提交按钮时，浏览器会自动向页面同源的<code>/submit-form</code> URL 发出 <code>POST</code>请求。浏览器发送表单包含的数据，编码为 <code>application/x-www-form-urlencoded</code>。在此特定示例中，表单数据包含<code>username</code>输入字段值。</p>
<p>表单也可以通过 <code>GET</code> 方法发送数据，但是大多数情况为<code>POST</code>。</p>
<p>表单数据将在 POST 请求体中发送。</p>
<p>可以使用<code>express.urlencoded()</code>中间件提取：</p>
<pre><code class="language-js">const express = require('express');
const app = express();

app.use(
  express.urlencoded({
    extended: true
  })
);
</code></pre>
<p>现在，需要在<code>/submit-form</code> 路由上创建一个 <code>POST</code> 端点，任何数据都可以在 <code>Request.body</code> 访问：</p>
<pre><code class="language-js">app.post('/submit-form', (req, res) =&gt; {
  const username = req.body.username;
  //...
  res.end();
});
</code></pre>
<p>别忘记提前使用<code>express-validator</code>验证数据。</p>
<h2 id="how-to-handle-file-uploads-in-forms-in-express">如何在 Express 中处理表单中的文件上传</h2>
<p>以下代码是允许用户上传文件的 HTML 表单示例：</p>
<pre><code class="language-html">&lt;form method="POST" action="/submit-form" enctype="multipart/form-data"&gt;
  &lt;input type="file" name="document" /&gt;
  &lt;input type="submit" /&gt;
&lt;/form&gt;
</code></pre>
<p>别忘记在表单添加<code>enctype="multipart/form-data"</code>，否则表单不会被上传。</p>
<p>当用户按下提交按钮时，浏览器会自动向页面同源的<code>/submit-form</code> URL 发出 <code>POST</code> 请求。浏览器发送表单包含的数据，但表单未编码为普通表单 <code>application/x-www-form-urlencoded</code>，而是 <code>multipart/form-data</code>。</p>
<p>在服务器端，处理多部分数据可能很棘手且容易出错，因此我们将使用一个名为 <strong>formidable</strong> 的库。<a href="https://github.com/felixge/node-formidable">这里是它的 GitHub 仓库</a> – 拥有超过 4000 颗星，并且维护良好。</p>
<p>可以通过以下命令安装：</p>
<pre><code class="language-bash">npm install formidable
</code></pre>
<p>然后引用到 Node.js 文件：</p>
<pre><code class="language-js">const express = require('express');
const app = express();
const formidable = require('formidable');
</code></pre>
<p>现在，在 <code>/submit-form</code> 路由的 <code>POST</code> 端点中，我们使用 <code>formidable.IncomingForm()</code> 实例化一个新的 Formidable 表单：</p>
<pre><code class="language-js">app.post('/submit-form', (req, res) =&gt; {
  new formidable.IncomingForm();
});
</code></pre>
<p>这样做之后，我们需要解析表单。我们可以通过回调来同步执行此操作，这意味着所有文件都已处理。一旦 formidable 完成，文件就可以被访问：</p>
<pre><code class="language-js">app.post('/submit-form', (req, res) =&gt; {
  new formidable.IncomingForm().parse(req, (err, fields, files) =&gt; {
    if (err) {
      console.error('Error', err);
      throw err;
    }
    console.log('Fields', fields);
    console.log('Files', files);
    for (const file of Object.entries(files)) {
      console.log(file);
    }
  });
});
</code></pre>
<p>或者可以使用事件而不是回调。例如，当每个文件被解析时，或其他事件（例如文件处理完成、接收非文件字段或发生错误）时，都会收到通知：</p>
<pre><code class="language-js">app.post('/submit-form', (req, res) =&gt; {
  new formidable.IncomingForm()
    .parse(req)
    .on('field', (name, field) =&gt; {
      console.log('Field', name, field);
    })
    .on('file', (name, file) =&gt; {
      console.log('Uploaded file', name, file);
    })
    .on('aborted', () =&gt; {
      console.error('Request aborted by the user');
    })
    .on('error', (err) =&gt; {
      console.error('Error', err);
      throw err;
    })
    .on('end', () =&gt; {
      res.end();
    });
});
</code></pre>
<p>无论选择哪种方式，你都将获得一个或多个 Formidable.File 对象，这些对象为你提供有关已上传文件的信息。这些是可以调用的一些方法：</p>
<ul>
<li><code>file.size</code>, 以字节为单位的文件大小</li>
<li><code>file.path</code>, 文件写入的路径</li>
<li><code>file.name</code>, 文件名</li>
<li><code>file.type</code>, 文件的 MIME 类型</li>
</ul>
<p>路径默认为临时文件夹，如果监听 <code>fileBegin</code> 事件可以修改：</p>
<pre><code class="language-js">app.post('/submit-form', (req, res) =&gt; {
  new formidable.IncomingForm()
    .parse(req)
    .on('fileBegin', (name, file) =&gt; {
      file.path = __dirname + '/uploads/' + file.name;
    })
    .on('file', (name, file) =&gt; {
      console.log('Uploaded file', name, file);
    });
  //...
});
</code></pre>
<h2 id="">感谢阅读！</h2>
<p>这就是手册的全部内容。别忘了，如果需要的话，<a href="https://thevalleyofcode.com/download/express/">你可以下载该手册的 PDF 或者 ePub 版本</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 用 React 和 Node/Express 开发全栈应用入门 ]]>
                </title>
                <description>
                    <![CDATA[ 在这篇文章里，我将引导你创建简单的 React 应用和简单的 Node / Express API，并将两者连接起来。 这篇教程中提到的各种技术，我不会详细介绍如何使用它们。但是我会留下链接，你可以了解更多信息， 你可以在我为教程制作的仓库 [https://github.com/Joao-Henrique/React_Express_App_Medium_Tutorial]中找到所有代码。 教程的目的是为你提供有关如何设置和连接前端 client 和后端 API 的实用指南。 在开始之前，请确保你的电脑正在运行 Node.js。 创建主项目目录 在你的终端中，导航到你要保存项目的目录。现在为你的项目创建一个新目录，并导航到其中： mkdir my_awesome_project cd my_awesome_project 创建一个 React [https://reactjs.org/] App 这个过程真的很简单。 我将使用 Facebook 的 create-react-app [https://github.com/facebook/create-react-ap ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together/</link>
                <guid isPermaLink="false">6038bd05c354c605689ea910</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Fri, 26 Feb 2021 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/02/1614334666774.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在这篇文章里，我将引导你创建简单的 React 应用和简单的 Node / Express API，并将两者连接起来。</p><p>这篇教程中提到的各种技术，我不会详细介绍如何使用它们。但是我会留下链接，你可以了解更多信息，</p><p>你可以在我为教程制作的<a href="https://github.com/Joao-Henrique/React_Express_App_Medium_Tutorial">仓库</a>中找到所有代码。</p><p>教程的目的是为你提供有关如何设置和连接<strong>前端 client </strong>和<strong>后端 API</strong> 的实用指南。<br>在开始之前，请确保你的电脑正在运行 Node.js。</p><h4 id="-"><strong><strong>创建主项目目录</strong></strong></h4><p>在你的终端中，导航到你要保存项目的目录。现在为你的项目创建一个新目录，并导航到其中：</p><pre><code class="language-bash">mkdir my_awesome_project
cd my_awesome_project</code></pre><h4 id="-react-app"><strong><strong>创建一个</strong> </strong><a href="https://reactjs.org/" rel="noopener"><strong><strong>React</strong></strong></a><strong><strong> App</strong></strong></h4><p>这个过程真的很简单。</p><p>我将使用 Facebook 的 <a href="https://github.com/facebook/create-react-app" rel="noopener">create-react-app</a> 来……你猜对了，轻松创建一个名为<strong> client</strong> 的 react 应用：</p><pre><code class="language-bash">npx create-react-app client
cd client
npm start</code></pre><p>让我们看看我做了什么：</p><ul><li>使用 npm 的 npx 来创建 react 应用并将其命名为 client。</li><li>cd（change directory 更改目录）进入 client 目录。</li><li>启动应用程序。</li></ul><p>在浏览器中，导航到 <a href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。</p><p>如果一切正常，你将看到 react 欢迎页面。恭喜你！这意味着你现在已经在本地计算机上运行了一个基本的 react 应用程序。容易吧？</p><p>要停止运行你的 react 应用，只需在终端中按 <strong><strong><code>Ctrl + c</code></strong></strong>。</p><h4 id="-express-app"><strong><strong>创建一个 </strong></strong><a href="https://expressjs.com/" rel="noopener"><strong><strong>Express</strong></strong></a><strong><strong> App</strong></strong></h4><p>好的，这将与前面的示例一样简单。不要忘记导航到你的项目顶部文件夹。</p><p>我将使用 <a href="https://expressjs.com/en/starter/generator.html">Express 应用生成器</a>快速创建应用程序框架，并将其命名为 <strong>api</strong>：</p><pre><code class="language-bash">npx express-generator api
cd api
npm install
npm start</code></pre><p>让我们看看我做了什么：</p><ul><li>使用 npm 的 npx 来全局安装 express-generator。</li><li>使用 express-generator 创建一个 Express 应用并将其命名为 api。</li><li>cd 进入 API 目录。</li><li>安装所有依赖项。</li><li>启动应用程序。</li></ul><p>在浏览器中，导航到 <a href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。</p><p>如果一切正常，你将看到 <a href="https://expressjs.com/" rel="noopener"><strong><strong>Express</strong></strong></a><strong> </strong>欢迎页面。恭喜你！ 这意味着你现在已经在本地计算机上运行了基本的 Express 应用程序。容易吧？</p><p>要停止运行你的 react 应用，只需在终端中按 <code><strong><strong>Ctrl + c</strong></strong></code>。</p><h4 id="-express-api-route">在 Express API 中配置新 <a href="https://expressjs.com/en/guide/routing.html" rel="noopener">route</a></h4><p>我们开始吧。打开你喜欢的代码编辑器（我正在使用 VS Code），导航到你的项目文件夹。</p><p>如果你<strong>将 react app 命名为 client</strong>，<strong>将 express app 命名为 api</strong>，则会找到两个主文件夹：<strong>client</strong> 和 <strong>api</strong>。</p><p>1、在 <strong>API</strong> 目录中，转到 <strong>bin/www</strong>，然后将第 15 行的端口号从 3000 更改为 9000。稍后我们将同时运行两个应用程序，因此这样做可以避免出现问题。结果应该是这样的：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/m7e3LsFolz6988HgYjgdDuP3DPY1Ix3F8u5u" class="kg-image" alt="m7e3LsFolz6988HgYjgdDuP3DPY1Ix3F8u5u" width="600" height="400" loading="lazy"><figcaption>my_awesome_project/api/bin/www</figcaption></figure><p>2、在 <strong><strong>api/routes</strong> </strong>上，创建一个 <strong><strong>testAPI.js</strong></strong> 文件，粘贴以下代码：</p><pre><code class="language-js">var express = require(“express”);
var router = express.Router();

router.get(“/”, function(req, res, next) {
    res.send(“API is working properly”);
});

module.exports = router;</code></pre><p>3、在 <strong><strong>api/app.js</strong></strong> 文件中，在第 24 行插入新的 route：</p><pre><code class="language-js">app.use("/testAPI", testAPIRouter);</code></pre><p>4、你正在“告诉” express 使用该 rout。但是，你仍然要求它。让我们在第 9 行进行操作：</p><pre><code class="language-js">var testAPIRouter = require("./routes/testAPI");</code></pre><p>唯一的变化是在第 9 行和第 25 行。它应该是这样的：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/hG-IcH7kyM8nj1VO43Mgo1nZGI0hsVhvAfFi" class="kg-image" alt="hG-IcH7kyM8nj1VO43Mgo1nZGI0hsVhvAfFi" width="600" height="400" loading="lazy"><figcaption>my_awesome_project/api/app.js</figcaption></figure><p>5、恭喜你！你已经创建了一个新的 <a href="https://expressjs.com/en/guide/routing.html" rel="noopener">route</a>。</p><p>如果你启动 API 应用程序（在终端中，导航到 API 目录并运行 “<strong>npm start</strong>”），然后在浏览器中访问 <a href="http://localhost:9000/testAPI" rel="noopener">http://localhost:9000/testAPI</a>，你将看到以下消息：<strong><strong><em><em>API is working properly</em></em></strong><em>（API 正常运行中）</em></strong>。</p><p><strong>将 React Client 连接到 Express API</strong></p><ul><li>在你的代码编辑器上，找到 <strong><strong>client</strong> </strong>目录，打开位于 <strong><strong>my_awesome_project/client/app.js</strong> </strong>中的 <strong><strong>app.js</strong> </strong>文件。</li><li>在这里，我将使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" rel="noopener"><strong><strong>Fetch API</strong></strong></a><strong> </strong>从 <strong><strong>API</strong> </strong>检索数据。只需在 Class 声明之后和 render 方法之前粘贴以下代码：</li></ul><pre><code class="language-jsx">constructor(props) {
    super(props);
    this.state = { apiResponse: "" };
}

callAPI() {
    fetch("http://localhost:9000/testAPI")
        .then(res =&gt; res.text())
        .then(res =&gt; this.setState({ apiResponse: res }));
}

componentWillMount() {
    this.callAPI();
}</code></pre><ul><li>在 render 方法内部，你将找到一个 &lt;; p&gt; 标记。让我们对其进行更改，以使其渲染 apiResponse：</li></ul><pre><code class="language-jsx">&lt;p className="App-intro"&gt;;{this.state.apiResponse}&lt;/p&gt;</code></pre><p>最后，这个文件应该是这样的：</p><figure class="kg-card kg-image-card"><img src="https://cdn-media-1.freecodecamp.org/images/dswag4EEuA3vcVmZ9cs7rxJwOayb-SW6HiI8" class="kg-image" alt="dswag4EEuA3vcVmZ9cs7rxJwOayb-SW6HiI8" width="600" height="400" loading="lazy"></figure><p>我知道内容有点多！你可以复制粘贴代码，但是你必须了解自己在做什么。让我们看看我在这里做了什么：</p><ul><li>在第 6 至 9 行中，我们插入了一个构造函数，用于初始化默认状态。</li><li>在第 11 至 16 行，我们插入了方法 <em><em><strong><strong>callAPI()</strong></strong></em></em>，该方法将从 API 中获取数据并将响应存储在 <em><em><strong><strong>this.state.apiResponse</strong></strong></em><strong> </strong></em>上。</li><li>在第 18 至 20 行，我们插入了一个 react 生命周期方法 <em><em><strong><strong>componentDidMount()</strong></strong></em></em>，该方法将在组件安装后执行 <em><em><strong><strong>callAPI()</strong></strong></em><strong> </strong></em>方法。</li><li>最后，在第 29 行，我使用&lt;; p&gt;标记在 client 页面上显示了一个段落，其中包含我们从 API 中检索到的文本。</li></ul><h4 id="-cors-"><strong><strong>什么</strong>？<strong><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener">CORS</a></strong>？</strong></h4><p>我们快完成了！ 但是，如果我们同时启动应用程序（client 和 API），并导航至<a href="http://localhost:3000/">http：// localhost：3000 /</a>，页面仍然不会显示预期结果。如果你打开 chrome 开发工具，就会找到原因。在控制台中，你将看到此错误：</p><blockquote>Failed to load <a href="http://localhost:9000/testAPI" rel="noopener">http://localhost:9000/testAPI</a>: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘<a href="http://localhost:3000'/" rel="noopener">http://localhost:3000'</a> is therefore not allowed access. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.</blockquote><p>这很容易解决。我们只需将 CORS 添加到我们的 API 中，即可允许跨域请求。你可以在<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">此处</a>查看有关 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener">CORS</a> 的更多信息。</p><ul><li>在你的终端中，导航到 API 目录，并安装 <strong><strong>CORS</strong> </strong>软件包：</li></ul><pre><code class="language-bash">npm install --save cors</code></pre><ul><li>在你的代码编辑器中，访问 API 目录，打开 <strong><strong>my_awesome_project/api/app.js</strong></strong> 文件。</li><li>在第 6 行，require <strong><strong>CORS</strong></strong>：</li></ul><pre><code class="language-js">var cors = require("cors");</code></pre><ul><li>在第 8 行，“告诉” express 使用 <strong><strong>CORS</strong></strong>：</li></ul><pre><code class="language-js">app.use(cors());</code></pre><p>现在，API <strong><strong>app.js</strong></strong> 文件应该是这样：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/NOuexIhM99Tn-eKYJ0wJjRJUCbIwww8Lyr61" class="kg-image" alt="NOuexIhM99Tn-eKYJ0wJjRJUCbIwww8Lyr61" width="600" height="400" loading="lazy"><figcaption>my_awesome_project/api/app.js</figcaption></figure><h4 id="--1"><strong><strong>很棒</strong>，我们完成了！</strong></h4><p>现在，使用 <strong><strong>npm start</strong> </strong>命令在两个不同的终端中启动你的应用程序（client 和 API）。</p><p>如果你在浏览器中导航到 <a href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>，则应该看到类似的内容：</p><figure class="kg-card kg-image-card"><img src="https://cdn-media-1.freecodecamp.org/images/Fwq4HE7hMM2Z5Um3U9xuwCVzlZSAeSvr9U1c" class="kg-image" alt="Fwq4HE7hMM2Z5Um3U9xuwCVzlZSAeSvr9U1c" width="600" height="400" loading="lazy"></figure><p>当然，这个项目没有实现很多，但它是全栈应用的开始。你可以在<a href="https://github.com/Joao-Henrique/React_Express_App_Medium_Tutorial">这个仓库</a>中找到我为教程创建的所有代码。</p><p>接下来，我将研究一些补充教程，例如如何将其连接到 MongoDB 数据库，甚至如何在 Docker 容器中运行它们。</p><p>正如我的一个好朋友说的：</p><blockquote>Be Strong and Code On!!!</blockquote><p>每天都要变得更好哦;)</p><p>原文：<a href="https://www.freecodecamp.org/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together-c5798926047c/">How to create a React frontend and a Node/Express backend and connect them</a>，作者：João Henrique</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
