ExpressJS如何构建应用程序? [英] ExpressJS How to structure an application?

查看:114
本文介绍了ExpressJS如何构建应用程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



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



我想有这个目录结构:

  |我的应用程序
| - app.js
| - config /
| - environment.js
| - routes.js

这是我的代码:



app.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);

config / environment.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());
});
};

config / routes.js

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

我的代码工作正常,我认为目录的结构是美丽的。但是,代码必须进行调整,我不确定它是好的/美丽的。



最好使用我的目录结构并适应代码使用一个文件(app.js)?



感谢您的建议!

解决方案

可以,这是一个很受欢迎的问题,所以我已经开始创建了一个脚手架github仓库,其中包含JavaScript代码和一个长的README,关于我喜欢如何构建一个中等大小的express.js应用程序。







您的应用程序有多大?



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



如果您的应用程序很小,则不需要如此所示的深层目录结构。只要保持简单,并在存储库的根目录中保存少量 .js 文件,你就完成了。 Voilà。



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



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



你的整体是什么架构



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




  • 服务器端MVC一个Ruby on Rails

  • 单页应用程序样式a a MongoDB / Express / Angular / Node(MEAN)

  • 基本网站与一些表单

  • 模型/操作/视图/事件样式a la MVC已经死了,现在是时候移动

  • 和许多其他的当前和历史



这些都适合于不同的目录结构。为了这个例子的目的,它只是脚手架,而不是一个完全正常的应用程序,但我假设以下关键体系结构点:




  • 该网站有一些传统的静态页面/模板

  • 该网站的应用程序部分被开发为单页应用程序样式

  • 该应用程序公开一个REST / JSON样式的API到浏览器

  • 该应用程序建立一个简单的业务域,在这种情况下,它是一个汽车经销商应用程序



Ruby on Rails怎么样?



在这个项目中将是一个主题,Ruby on Rails中体现的许多想法尽管被广泛接受和使用的公约超过配置的决定实际上并不是非常有用的,有时与本版本库建议的相反。



我的这里的重点在于有组织代码的基本原则,并以这些原则为基础对于Ruby on Rails社区,Ruby on Rails约定对于Ruby on Rails社区来说是有意义的(大多数情况下)。然而,只是毫不犹豫地对这些公约表示怀疑。一旦您了解了基本原则,您的所有项目将被组织清晰:shell脚本,游戏,移动应用程序,企业项目,甚至您的主目录。



对于Rails社区,他们希望能够将一个Rails开发人员从应用程序切换到应用程序,并且每次都熟悉和舒适。如果您是37个信号或Pivotal Labs,并且具有优势,这是非常有意义的。在服务器端的JavaScript世界中,整体的精神状态只会让西方变得更加狂野,而且我们并没有真正的问题。这就是我们滚动的方式。我们习惯了即使在express.js中,它是Sinatra的关系,而不是Rails,并且从Rails获取约定通常不会有任何帮助。



相关原则和动机




  • 精神上可管理


    • 大脑只能一次处理和思考少量相关事情。这就是为什么我们使用目录。


  • 大小合适


  • 模块化但务实


  • 很容易找到代码


    • 鉴于构建功能或修复错误,我们的目标是开发人员无法找到所涉及的源文件。

    • 名称有意义且准确无误

    • crufty代码已完全删除,不会遗留在孤儿档案中,或刚刚注解掉


  • 所有第一方都是搜索友好的


    • 我们的代码在应用程序目录中,所以你可以 cd 有运行find / grep / xargs / ag / ack /等等,不要被第三方比赛分心


  • 使用简单明了的命名


    • npm现在似乎需要全小写的包名称。我觉得这很可怕,但是我必须遵循这个牛群,所以文件名应该使用 kebab-case ,尽管JavaScript中的变量名必须是 camelCase 因为 - 是JavaScript中的减号。

    • 变量名称与模块路径的基本名称相匹配,但是将 kebab-case 转换为 camelCase


  • 通过耦合组合,而不是按功能组合


    • 这是与Ruby on Rails约定 app / views app / controllers app / models

    • 功能被添加到一个完整的堆栈,所以我想集中在一堆与我的功能相关的文件。当我向用户模型添加电话号码字段时,我不在乎用户控制器以外的任何控制器,我不在乎任何型号,而不是用户型号。

    • 因此,不是编辑自己目录中的6个文件,而忽略了这些目录中的其他文件,这个存储库被组织起来,使得我需要构建一个功能的所有文件都被共享

    • 根据MVC的性质,用户视图耦合到耦合到用户模型的用户控制器。所以当我改变用户模型时,这3个文件通常会一起改变,但是交易控制器或客户控制器是分离的,因此不涉及。对于非MVC设计,通常也是如此。

    • MVC或MOVE样式的解耦方式在哪个模块仍然受到鼓励,但将MVC文件扩展到同级目录中是只是烦人的。

    • 因此,我的每个路由文件都有它拥有的路由部分。如果您想要概述应用程序中的所有路由,但是在实际构建功能和修复错误时,您只需要关心路由,那么rails-style routes.rb


  • 在代码旁边存储测试


    • 这只是一个通过耦合组合的实例,但是我想特别提出来。我已经编写了许多项目,测试在一个称为测试的并行文件系统中,现在我已经开始将测试与相应的代码放在同一个目录中,我永远不会回去。这更模块化,更容易在文本编辑器中使用,并减轻了很多../../ ..路径废话。如果您有疑问,请尝试一些项目,并为自己决定。


  • 减少与事件的交叉耦合


    • 很容易想到确定,每当创建一个新的交易,我想发送一封电子邮件给所有销售人员,然后只是把代码发送给

    • 然而,这种耦合最终会将你的应用程序变成一个巨大的泥巴。

    • 相反,DealModel应该只是启动一个创建事件,完全不知道系统可以做什么来做出回应。

    • 当你用这种方法编写代码时,把所有的用户相关的代码转换为 app / users ,因为没有一个鼠标的耦合业务逻辑的巢,污染了用户代码库的纯度。


  • 代码流是可追踪的


    • 不要做魔术。不要从文件系统中的魔术目录自动加载文件。不要Rails该应用程序从 app / server.js:1 开始,您可以按照代码查看加载和执行的所有内容。

    • Don不要为你的路线做DSL。如果您的应用程序如此大,那么在 magicRESTRouter.route(somecontroller,{except:'POST')之前,请勿执行愚蠢的元编程。

    • < })
      是超过3个基本的 app.get app.put app.del ,电话,你可能正在构建一个太大的单片应用程序,无法有效地工作。获取BIG胜利,而不是将3条简单的线条转换为1条复杂的线条。

  • 使用较低的kebab案例文件名




    • 此格式避免跨平台的文件系统区分大小写问题

    • npm禁止新的包名称中的大写字母,这对于

      express.js具体细节



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


  • 中间件和路由在明确的事项的订单!


    • 几乎我在stackoverflow上看到的所有路由问题都是乱序的中间件

    • 一般来说,你想要你的路线解耦,而不是依赖订单这么多

    • 如果你真的只需要,不要使用 app.use 那个中间件有2条路线(我在看你, body-parser

    • 有这样的订单:


      1. 任何超级重要的应用程序范围的中间件

      2. 所有的路由和各种路由中间件

      3. THEN错误处理程序



  • 被sinatra启发,express.js主要假设所有的路由将在 server.js 中,它们将被清楚如何被排序。对于中等规模的应用程序,将事情分解成单独的路由模块是不错的,但它确实引入了无序中间件的危险。



< h2>应用程序symlink技巧

有很多方法在社区中概述和讨论,这个伟大的想法更好的Node.js的本地require()路径。我可能很快决定更喜欢只处理很多../../../ ..或使用 requireFrom modlue。然而,目前我一直在使用下面详细叙述的符号技巧。



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




  • 在您的应用程序的node_modules下创建一个符号链接


    • cd node_modules&& ln -nsf ../ app


  • 添加只是node_modules / app符号链接本身,而不是整个node_modules文件夹,git


    • git add -f node_modules / app

    • 是的,你还应该有node_modules您的 .gitignore 文件

    • 不,您不应该将node_modules放入您的git存储库。有些人会建议你这样做。它们不正确。


  • 现在您可以使用此前缀要求项目内模块


    • var config = require(app / config);

    • var DealModel = require(app / deals / deal-model);


  • 项目需要非常类似的工作,需要外部的npm模块。

  • 对不起,Windows用户需要使用父目录相对路径。



配置



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



尝试集中创建数据库连接并将其传递到子系统,而不是传递连接参数,并使子系统自身进行传出连接。



NODE_ENV



这是从Rails继承的另一个诱人但可怕的想法。应用程序中应该只有一个位置, app / config.js ,查看 NODE_ENV 环境变量。所有其他应该采取明确的选项作为类构造函数参数或模块配置参数。



如果电子邮件模块有一个选项,如何提供电子邮件(SMTP,登录stdout,放入队列等),它应该采取一个选项,如 {deliver:'stdout'} ,但绝对不要检查 NODE_ENV



测试



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




  • foo.js 有模块foo的代码

  • foo.tape.js 具有基于节点的foo测试和生活在相同的目录

  • foo.btape.js 可用于需要在浏览器环境中执行的测试



我使用filesystem globs和 find。 -name'* .tape.js'命令,以便在必要时访问我所有的测试。



如何在每个 .js 模块文件



此项目的范围主要是文件和目录的位置,我不想添加很多其他的范围,但我只是提到我组织我的代码到3个不同的部分。


  1. 打开的CommonJS块要求调用状态依赖关系

  2. 纯JavaScript的主要代码块。这里没有CommonJS污染。不要引用导出,模块或要求。

  3. 关闭用于设置导出的CommonJS块


I'm using the ExpressJS web framework for NodeJS.

People using ExpressJS put their environments (development, production, test...), their routes etc on the app.js. I think that it's not a beautiful way because when you have a big application, app.js is too big!

I would like to have this directory structure:

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

Here's my code:

app.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);

config/environment.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());
    });
};

config/routes.js

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

My code works well and I think that the structure of the directories is beautiful. However, the code had to be adapted and I'm not sure that it's good/beautiful.

Is it better to use my structure of directories and adapt the code or simply use one file (app.js)?

Thanks for your advices!

解决方案

OK, it's been a while and this is a popular question, so I've gone ahead and created a scaffolding github repository with JavaScript code and a long README about how I like to structure a medium-sized express.js application.

focusaurus/express_code_structure is the repo with the latest code for this. Pull requests welcome.

Here's a snapshot of the README since stackoverflow doesn't like just-a-link answers. I'll make some updates as this is a new project that I'll continue updating, but ultimately the github repo will be the up-to-date place for this information.


Express Code Structure

This project is an example of how to organize a medium-sized express.js web application.

Current to at least express v4.14 December 2016

How big is your application?

Web applications are not all the same, and there's not, in my opinion, a single code structure that should be applied to all express.js applications.

If your application is small, you don't need such a deep directory structure as exemplified here. Just keep it simple and stick a handful of .js files in the root of your repository and you're done. Voilà.

If your application is huge, at some point you need to break it up into distinct npm packages. In general the node.js approach seems to favor many small packages, at least for libraries, and you should build your application up by using several npm packages as that starts to make sense and justify the overhead. So as your application grows and some portion of the code becomes clearly reusable outside of your application or is a clear subsystem, move it to it's own git repository and make it into a standalone npm package.

So the focus of this project is to illustrate a workable structure for a medium-sized application.

What is your overall architecture

There are many approaches to building a web application, such as

  • Server Side MVC a la Ruby on Rails
  • Single Page Application style a la MongoDB/Express/Angular/Node (MEAN)
  • Basic web site with some forms
  • Models/Operations/Views/Events style a la MVC is dead, it's time to MOVE on
  • and many others both current and historical

Each of these fits nicely into a different directory structure. For the purposes of this example, it's just scaffolding and not a fully working app, but I'm assuming the following key architecture points:

  • The site has some traditional static pages/templates
  • The "application" portion of the site is developed as a Single Page Application style
  • The application exposes a REST/JSON style API to the browser
  • The app models a simple business domain, in this case, it's a car dealership application

And what about Ruby on Rails?

It will be a theme throughout this project that many of the ideas embodied in Ruby on Rails and the "Convention over Configuration" decisions they have adopted, though widely accepted and used, are not actually very helpful and sometimes are the opposite of what this repository recommends.

My main point here is that there are underlying principles to organizing code, and based on those principles, the Ruby on Rails conventions make sense (mostly) for the Ruby on Rails community. However, just thoughtlessly aping those conventions misses the point. Once you grok the basic principles, ALL of your projects will be well-organized and clear: shell scripts, games, mobile apps, enterprise projects, even your home directory.

For the Rails community, they want to be able to have a single Rails developer switch from app to app to app and be familiar and comfortable with it each time. This makes great sense if you are 37 signals or Pivotal Labs, and has benefits. In the server-side JavaScript world, the overall ethos is just way more wild west anything goes and we don't really have a problem with that. That's how we roll. We're used to it. Even within express.js, it's a close kin of Sinatra, not Rails, and taking conventions from Rails is usually not helping anything. I'd even say Principles over Convention over Configuration.

Underlying Principles and Motivations

  • Be mentally manageable
    • The brain can only deal with and think about a small number of related things at once. That's why we use directories. It helps us deal with complexity by focusing on small portions.
  • Be size-appropriate
    • Don't create "Mansion Directories" where there's just 1 file all alone 3 directories down. You can see this happening in the Ansible Best Practices that shames small projects into creating 10+ directories to hold 10+ files when 1 directory with 3 files would be much more appropriate. You don't drive a bus to work (unless you're a bus driver, but even then your driving a bus AT work not TO work), so don't create filesystem structures that aren't justified by the actual files inside them.
  • Be modular but pragmatic
    • The node community overall favors small modules. Anything that can cleanly be separated out from your app entirely should be extracted into a module either for internal use or publicly published on npm. However, for the medium-sized applications that are the scope here, the overhead of this can add tedium to your workflow without commensurate value. So for the time when you have some code that is factored out but not enough to justify a completely separate npm module, just consider it a "proto-module" with the expectation that when it crosses some size threshold, it would be extracted out.
    • Some folks such as @hij1nx even include an app/node_modules directory and have package.json files in the proto-module directories to facilitate that transition and act as a reminder.
  • Be easy to locate code
    • Given a feature to build or a bug to fix, our goal is that a developer has no struggle locating the source files involved.
    • Names are meaningful and accurate
    • crufty code is fully removed, not left around in an orphan file or just commented out
  • Be search-friendly
    • all first-party source code is in the app directory so you can cd there are run find/grep/xargs/ag/ack/etc and not be distracted by third party matches
  • Use simple and obvious naming
    • npm now seems to require all-lowercase package names. I find this mostly terrible but I must follow the herd, thus filenames should use kebab-case even though the variable name for that in JavaScript must be camelCase because - is a minus sign in JavaScript.
    • variable name matches the basename of the module path, but with kebab-case transformed to camelCase
  • Group by Coupling, Not by Function
    • This is a major departure from the Ruby on Rails convention of app/views, app/controllers, app/models, etc
    • Features get added to a full stack, so I want to focus on a full stack of files that are relevant to my feature. When I'm adding a telephone number field to the user model, I don't care about any controller other than the user controller, and I don't care about any model other than the user model.
    • So instead of editing 6 files that are each in their own directory and ignoring tons of other files in those directories, this repository is organized such that all the files I need to build a feature are colocated
    • By the nature of MVC, the user view is coupled to the user controller which is coupled to the user model. So when I change the user model, those 3 files will often change together, but the deals controller or customer controller are decoupled and thus not involved. Same applies to non-MVC designs usually as well.
    • MVC or MOVE style decoupling in terms of which code goes in which module is still encouraged, but spreading the MVC files out into sibling directories is just annoying.
    • Thus each of my routes files has the portion of the routes it owns. A rails-style routes.rb file is handy if you want an overview of all routes in the app, but when actually building features and fixing bugs, you only care about the routes relevant to the piece you are changing.
  • Store tests next to the code
    • This is just an instance of "group by coupling", but I wanted to call it out specifically. I've written many projects where the tests live under a parallel filesystem called "tests" and now that I've started putting my tests in the same directory as their corresponding code, I'm never going back. This is more modular and much easier to work with in text editors and alleviates a lot of the "../../.." path nonsense. If you are in doubt, try it on a few projects and decide for yourself. I'm not going to do anything beyond this to convince you that it's better.
  • Reduce cross-cutting coupling with Events
    • It's easy to think "OK, whenever a new Deal is created, I want to send an email to all the Salespeople", and then just put the code to send those emails in the route that creates deals.
    • However, this coupling will eventually turn your app into a giant ball of mud.
    • Instead, the DealModel should just fire a "create" event and be entirely unaware of what else the system might do in response to that.
    • When you code this way, it becomes much more possible to put all the user related code into app/users because there's not a rat's nest of coupled business logic all over the place polluting the purity of the user code base.
  • Code flow is followable
    • Don't do magic things. Don't autoload files from magic directories in the filesystem. Don't be Rails. The app starts at app/server.js:1 and you can see everything it loads and executes by following the code.
    • Don't make DSLs for your routes. Don't do silly metaprogramming when it is not called for.
    • If your app is so big that doing magicRESTRouter.route(somecontroller, {except: 'POST'}) is a big win for you over 3 basic app.get, app.put, app.del, calls, you're probably building a monolithic app that is too big to effectively work on. Get fancy for BIG wins, not for converting 3 simple lines to 1 complex line.
  • Use lower-kebab-case filenames

    • This format avoids filesystem case sensitivity issues across platforms
    • npm forbids uppercase in new package names, and this works well with that

      express.js specifics

  • Don't use app.configure. It's almost entirely useless and you just don't need it. It is in lots of boilerplate due to mindless copypasta.

  • THE ORDER OF MIDDLEWARE AND ROUTES IN EXPRESS MATTERS!!!
    • Almost every routing problem I see on stackoverflow is out-of-order express middleware
    • In general, you want your routes decoupled and not relying on order that much
    • Don't use app.use for your entire application if you really only need that middleware for 2 routes (I'm looking at you, body-parser)
    • Make sure when all is said and done you have EXACTLY this order:

      1. Any super-important application-wide middleware
      2. All your routes and assorted route middlewares
      3. THEN error handlers

  • Sadly, being sinatra-inspired, express.js mostly assumes all your routes will be in server.js and it will be clear how they are ordered. For a medium-sized application, breaking things out into separate routes modules is nice, but it does introduce peril of out-of-order middleware

The app symlink trick

There are many approaches outlined and discussed at length by the community in the great gist Better local require() paths for Node.js. I may soon decide to prefer either "just deal with lots of ../../../.." or use the requireFrom modlue. However, at the moment, I've been using the symlink trick detailed below.

So one way to avoid intra-project requires with annoying relative paths like require("../../../config") is to use the following trick:

  • create a symlink under node_modules for your app
    • cd node_modules && ln -nsf ../app
  • add just the node_modules/app symlink itself, not the entire node_modules folder, to git
    • git add -f node_modules/app
    • Yes, you should still have "node_modules" in your .gitignore file
    • No, you should not put "node_modules" into your git repository. Some people will recommend you do this. They are incorrect.
  • Now you can require intra-project modules using this prefix
    • var config = require("app/config");
    • var DealModel = require("app/deals/deal-model");
  • Basically, this makes intra-project requires work very similarly to requires for external npm modules.
  • Sorry, Windows users, you need to stick with parent directory relative paths.

Configuration

Generally code modules and classes to expect only a basic JavaScript options object passed in. Only app/server.js should load the app/config.js module. From there it can synthesize small options objects to configure subsystems as needed, but coupling every subsystem to a big global config module full of extra information is bad coupling.

Try to centralize creation of DB connections and pass those into subsystems as opposed to passing connection parameters and having subsystems make outgoing connections themselves.

NODE_ENV

This is another enticing but terrible idea carried over from Rails. There should be exactly 1 place in your app, app/config.js that looks at the NODE_ENV environment variable. Everything else should take an explicit option as a class constructor argument or module configuration parameter.

If the email module has an option as to how to deliver emails (SMTP, log to stdout, put in queue etc), it should take an option like {deliver: 'stdout'} but it should absolutely not check NODE_ENV.

Tests

I now keep my test files in the same directory as their corresponding code and use filename extension naming conventions to distinguish tests from production code.

  • foo.js has the module "foo"'s code
  • foo.tape.js has the node-based tests for foo and lives in the same dir
  • foo.btape.js can be used for tests that need to execute in a browser environment

I use filesystem globs and the find . -name '*.tape.js' command to get access to all my tests as necessary.

How to organize code within each .js module file

This project's scope is mostly about where files and directories go, and I don't want to add much other scope, but I'll just mention that I organize my code into 3 distinct sections.

  1. Opening block of CommonJS require calls to state dependencies
  2. Main code block of pure-JavaScript. No CommonJS pollution in here. Don't reference exports, module, or require.
  3. Closing block of CommonJS to set up exports

这篇关于ExpressJS如何构建应用程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆