小编典典

ExpressJS 如何构建应用程序?

all

我正在为 NodeJS 使用 ExpressJS Web 框架。

使用 ExpressJS 的人将他们的环境(开发、生产、测试​​......)、他们的路线等放在app.js.
我认为这不是一个漂亮的方式,因为当你有一个大的应用程序时,app.js 太大了!

我想要这个目录结构:

| my-application
| -- app.js
| -- config/
     | -- environment.js
     | -- routes.js

这是我的代码:

应用程序.js

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

require('./config/environment.js')(app, express);
require('./config/routes.js')(app);

app.listen(3000);

配置/环境.js

module.exports = function(app, express){
    app.configure(function() {
    app.use(express.logger());
    });

    app.configure('development', function() {
    app.use(express.errorHandler({
        dumpExceptions: true,
        showStack: true
    }));
    });

    app.configure('production', function() {
    app.use(express.errorHandler());
    });
};

配置/routes.js

module.exports = function(app) {
    app.get('/', function(req, res) {
    res.send('Hello world !');
    });
};

我的代码运行良好,我认为目录的结构很漂亮。但是,必须修改代码,我不确定它是否好/漂亮。

使用我的目录结构并调整代码还是只使用一个文件(app.js)更好?

感谢您的建议!


阅读 106

收藏
2022-03-08

共1个答案

小编典典

好的,已经有一段时间了,这是一个流行的问题,所以我继续创建了一个脚手架 github 存储库,其中包含 JavaScript
代码和一个关于我喜欢如何构建中型 express.js 应用程序的长自述文件。

focusaurus/express_code_structure是带有最新代码的仓库。欢迎拉取请求。

这是自述文件的快照,因为 stackoverflow 不喜欢只是一个链接的答案。我会做一些更新,因为这是一个我会继续更新的新项目,但最终 github
存储库将是此信息的最新位置。


快速代码结构

该项目是如何组织中型 express.js Web 应用程序的示例。

当前至少表达 v4.14 December 2016

构建状态

js-
标准样式

你的应用程序有多大?

Web 应用程序并不完全相同,在我看来,不存在应该应用于所有 express.js 应用程序的单一代码结构。

如果您的应用程序很小,则不需要此处示例的如此深的目录结构。只需保持简单并将少量.js文件粘贴到存储库的根目录中即可。脿。

如果您的应用程序很大,有时您需要将其分解为不同的 npm 包。一般来说,node.js 方法似乎有利于许多小包,至少对于库而言,并且您应该使用多个 npm
包来构建您的应用程序,因为这开始变得有意义并证明开销是合理的。因此,随着您的应用程序增长并且某些部分代码在您的应用程序之外变得明显可重用或者是一个明确的子系统,请将其移动到它自己的
git 存储库并使其成为一个独立的 npm 包。

所以 这个项目的重点是说明一个中型应用程序的可行结构。

你的整体架构是什么

构建 Web 应用程序的方法有很多,例如

  • Ruby on Rails 的服务器端 MVC
  • 单页应用程序样式,如 MongoDB/Express/Angular/Node (MEAN)
  • 带有一些表格的基本网站
  • 模型/操作/视图/事件风格的MVC 已经死了,是时候继续前进了
  • 以及许多其他当前和历史的

这些中的每一个都很好地适合不同的目录结构。出于本示例的目的,它只是一个脚手架,而不是一个完全工作的应用程序,但我假设以下关键架构点:

  • 该网站有一些传统的静态页面/模板
  • 网站的“应用程序”部分被开发为单页应用程序样式
  • 应用程序向浏览器公开 REST/JSON 样式 API
  • 该应用程序模拟了一个简单的业务领域,在这种情况下,它是一个汽车经销商应用程序

那么 Ruby on Rails 呢?

Ruby on Rails
中体现的许多想法以及他们采用的“约定优于配置”决策将成为整个项目的一个主题,虽然被广泛接受和使用,但实际上并不是很有帮助,有时与这个存储库的相反建议。

我的主要观点是组织代码有一些基本原则,基于这些原则,Ruby on Rails 约定(大部分)对 Ruby on Rails
社区有意义。然而,只是漫不经心地模仿这些惯例,就没有抓住重点。一旦您掌握了基本原则,您的所有项目都将井然有序且清晰:shell
脚本、游戏、移动应用程序、企业项目,甚至您的主目录。

对于 Rails 社区,他们希望能够让一个 Rails 开发人员从一个应用程序切换到另一个应用程序,并且每次都熟悉和适应它。如果您是 37 个信号或
Pivotal Labs,这很有意义,并且有好处。在服务器端的 JavaScript
世界中,整体的精神是更加狂野的西部任何事情都发生了,我们对此并没有真正的问题。我们就是这样滚动的。我们已经习惯了。即使在 express.js 中,它也是
Sinatra 的近亲,而不是 Rails,并且从 Rails 中获取约定通常无济于事。我什至会说 Principles over Convention
over Configuration

基本原则和动机

  • 精神可控
    • 大脑一次只能处理和思考少量相关的事情。这就是我们使用目录的原因。它通过关注小部分来帮助我们处理复杂性。
  • 大小合适
    • 不要创建“Mansion 目录”,其中只有 1 个文件,只有 3 个目录。您可以在Ansible 最佳实践中看到这种情况,当 1 个包含 3 个文件的目录更合适时,它羞辱小项目创建 10+ 个目录来保存 10+ 个文件。您不会开车去上班(除非您是公交车司机,但即便如此,您也不会开着公交车上班),所以不要创建不符合其中实际文件的文件系统结构.
  • 模块化但务实
    • 节点社区总体上偏爱小模块。任何可以从你的应用程序中完全分离出来的东西都应该被提取到一个模块中供内部使用或在 npm 上公开发布。但是,对于此处范围内的中型应用程序,其开销可能会增加您的工作流程的乏味而没有相应的价值。因此,当您有一些代码被分解但不足以证明完全独立的 npm 模块的合理 性时,只需将其视为“原型模块 ”,并期望当它超过某个大小阈值时,它将被提取出来。
    • @hij1nx等一些人甚至包含一个app/node_modules目录并在 proto-module*package.json目录中有文件,以促进这种转换并起到提醒作用。 *
  • 易于定位代码
    • 给定要构建的功能或要修复的错误,我们的目标是开发人员可以毫不费力地找到所涉及的源文件。
    • 名称有意义且准确
    • 粗鲁的代码被完全删除,没有留在孤立文件中或只是被注释掉
  • 易于搜索
    • 所有第一方源代码都在app目录中,因此您可以cd运行 find/grep/xargs/ag/ack/etc 而不会被第三方匹配分心
  • 使用简单明了的命名
    • npm 现在似乎需要全小写的包名。我觉得这很糟糕,但我必须随波逐流,因此文件名应该使用,kebab-case即使 JavaScript 中的变量名必须是camelCase因为在 JavaScript-中是减号。
    • 变量名称匹配模块路径的基本名称,但kebab-case转换为camelCase
  • 按耦合分组,而不是按功能分组
    • 这与 Ruby on Rails 的app/viewsapp/controllersapp/models等约定有很大不同。
    • 功能被添加到完整堆栈中,因此我想专注于与我的功能相关的完整文件堆栈。当我在用户模型中添加电话号码字段时,我不关心除用户控制器之外的任何控制器,也不关心除用户模型之外的任何模型。
    • 因此,不是编辑各自位于各自目录中的 6 个文件并忽略这些目录中的大量其他文件,而是组织了这个存储库,以便我构建功能所需的所有文件都位于同一位置
    • 根据 MVC 的性质,用户视图与用户控制器耦合,用户控制器与用户模型耦合。因此,当我更改用户模型时,这三个文件通常会一起更改,但交易控制器或客户控制器是解耦的,因此不涉及。这同样适用于非 MVC 设计。
    • 仍然鼓励 MVC 或 MOVE 风格的解耦,即哪些代码进入哪个模块,但是将 MVC 文件分散到同级目录中只是烦人的。
    • 因此,我的每个路由文件都有它拥有的部分路由。如果您想了解应用程序中所有路线的概览,则使用 rails 样式的routes.rb文件非常方便,但在实际构建功能和修复错误时,您只关心与您正​​在更改的部分相关的路线。
  • 在代码旁边存储测试
    • 这只是“通过耦合分组”的一个实例,但我想特别指出它。我编写了许多项目,其中测试位于名为“tests”的并行文件系统下,现在我已经开始将测试与其相应代码放在同一目录中,我再也不会回去了。这更模块化,更容易在文本编辑器中使用,并减轻了很多“../../..”路径的废话。如果您有疑问,请尝试几个项目并自己决定。除此之外,我不会做任何事情来说服你它更好。
  • 减少与事件的交叉耦合
    • 很容易想到“好的,每当创建新交易时,我想向所有销售人员发送一封电子邮件”,然后只需将发送这些电子邮件的代码放在创建交易的路线中即可。
    • 然而,这种耦合最终会将你的应用程序变成一个巨大的泥球。
    • 相反,DealModel 应该只触发一个“创建”事件,并且完全不知道系统可能会针对该事件做出什么反应。
    • 当您以这种方式编写代码时,将所有与用户相关的代码放入其中变得更有可能,app/users因为没有到处都是耦合业务逻辑的老鼠巢,污染了用户代码库的纯度。
  • 代码流可遵循
    • 不要做魔法的事情。不要从文件系统中的魔法目录自动加载文件。不要成为 Rails。该应用程序从开始,app/server.js:1您可以按照代码查看它加载和执行的所有内容。
    • 不要为您的路线制作 DSL。不要在不需要时进行愚蠢的元编程。
    • 如果您的应用程序如此之大,以至于通过 3 个基本的 , , , 调用magicRESTRouter.route(somecontroller, {except: 'POST'})对您来说是一个巨大的胜利,那么您可能正在构建一个太大而无法有效工作的单体应用程序。看中大胜利,而不是将 3 条简单的线转换为 1 条复杂的线。app.get``app.put``app.del
  • 使用小写的kebab文件名

    • 这种格式避免了跨平台的文件系统区分大小写问题
    • npm 禁止在新包名中使用大写字母,这很好用

express.js 细节

  • 不要使用app.configure. 它几乎完全没用,你只是不需要它。由于盲目的copypasta,它在很多样板中。

  • 中间件和路由的顺序!

    • 我在 stackoverflow 上看到的几乎每个路由问题都是无序的 express 中间件
    • 一般来说,您希望您的路线解耦并且不那么依赖订单
    • 如果您真的只需要 2 条路由的中间件,请不要app.use用于整个应用程序(我在看着您,body-parser
    • 确保一切都说完了,你有这个订单:
    • 任何超级重要的应用程序范围的中间件
    • 您所有的路线和各种路线中间件
    • THEN 错误处理程序
  • 可悲的是,受 sinatra 启发,express.js 主要假设您的所有路线都将在server.js其中,并且它们的排序方式将很清楚。对于中型应用程序,将事物分解为单独的路由模块很好,但它确实引入了乱序中间件的危险

应用程序符号链接技巧

社区在“更好的 Node.js 本地 require()
路径”
中详细概述和讨论了许多方法。我可能很快会决定选择“只处理大量
../../../..”或使用requireFrom模块。但是,目前,我一直在使用下面详述的符号链接技巧。

因此,避免项目内需要使用烦人的相对路径的一种方法require("../../../config")是使用以下技巧:

  • 在 node_modules 下为您的应用创建一个符号链接
    • cd node_modules && ln -nsf ../app
  • 仅将 node_modules/app 符​​号链接本身 ,而不是整个 node_modules 文件夹添加到 git
    • git add -f node_modules/app
    • 是的,您的.gitignore文件中仍然应该有“node_modules”
    • 不,您不应该将“node_modules”放入您的 git 存储库。有些人会建议你这样做。他们是不正确的。
  • 现在您可以使用此前缀要求项目内模块
    • var config = require("app/config");
    • var DealModel = require("app/deals/deal-model");
  • 基本上,这使得项目内需求的工作与外部 npm 模块的需求非常相似。
  • 抱歉,Windows 用户,您需要坚持使用父目录的相对路径。

配置

通常代码模块和类只期望options传入一个基本的 JavaScript
对象。只app/server.js应该加载app/config.js模块。从那里它可以合成小options对象以根据需要配置子系统,但是将每个子系统耦合到一个充满额外信息的大型全局配置模块是不好的耦合。

尝试集中创建数据库连接并将其传递给子系统,而不是传递连接参数并让子系统自己建立传出连接。

NODE_ENV

这是从 Rails 继承而来的另一个诱人但可怕的想法。您的应用程序中应该恰好有 1
个位置app/config.js查看NODE_ENV环境变量。其他一切都应采用显式选项作为类构造函数参数或模块配置参数。

如果电子邮件模块有关于如何发送电子邮件的选项(SMTP、登录到标准输出、放入队列等),它应该采用类似{deliver: 'stdout'}但绝对不应该检查的选项NODE_ENV

测试

我现在将我的测试文件与其对应的代码保存在同一目录中,并使用文件扩展名命名约定来区分测试和生产代码。

  • foo.js有模块“foo”的代码
  • foo.tape.js对 foo 进行基于节点的测试,并位于同一个目录中
  • foo.btape.js可用于需要在浏览器环境中执行的测试

我根据需要使用文件系统 glob 和find . -name '*.tape.js'命令来访问我的所有测试。

如何在每个.js模块文件中组织代码

这个项目的范围主要是关于文件和目录的去向,我不想添加太多其他范围,但我只想提一下,我将我的代码组织成 3 个不同的部分。

  1. CommonJS 的打开块需要调用状态依赖项
  2. 纯 JavaScript 的主要代码块。这里没有 CommonJS 污染。不要引用导出、模块或要求。
  3. 关闭 CommonJS 块以设置导出
2022-03-08