node.js 使用 Node/Express 构建企业应用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41875617/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 17:57:41  来源:igfitidea点击:

Building enterprise app with Node/Express

node.jsexpressarchitecture

提问by MyTitle

I'm trying to understand how to structure enterprise applciation with Node/Express/Mongo (actually using MEAN stack).

我试图了解如何使用 Node/Express/Mongo(实际上使用 MEAN 堆栈)来构建企业应用程序。

After reading 2 books and some googling (including similar StackOverflow questions), I couldn't find any good example of structuring large applications using Express. All sources I've read suggest to split application by following entities:

在阅读了 2 本书和一些谷歌搜索(包括类似的 StackOverflow 问题)后,我找不到任何使用 Express 构建大型应用程序的好例子。我读过的所有来源都建议按以下实体拆分应用程序:

  • routes
  • controllers
  • models
  • 路线
  • 控制器
  • 楷模

But the main problem I see with this structure is that controllers are like god objects, they knows about req, resobjects, responsible for validation and have business logicincluded in.

但是我看到这个结构的主要问题是控制器就像上帝对象,他们知道req,res对象,负责验证并包含业务逻辑

At other side, routes seems to me like over-engineering because all they doing is mapping endpoints(paths) to controller methods.

另一方面,路由在我看来像是过度设计,因为它们所做的只是将端点(路径)映射到控制器方法。

I have Scala/Java background, so I have habit to separate all logic in 3 tiers - controller/service/dao.

我有 Scala/Java 背景,所以我习惯将所有逻辑分成 3 层 - 控制器/服务/道。

For me following statements are ideal:

对我来说,以下陈述是理想的:

  • Controllers are responsible only for interacting with WEB part, i.e. marshalling/unmarshalling, some simple validation (required, min, max, email regex etc);

  • Service layer (which actually I missed in NodeJS/Express apps) is responsible only for business logic, some business validation. Service layer doesn't know anything about WEB part (i.e. they can be called from other place of application, not only from web context);

  • Regarding to DAO layer is all clear for me. Mongoose models are actually DAO, so it most clear thing to me here.

  • 控制器只负责与 WEB 部分的交互,即编组/解组,一些简单的验证(必需、最小值、最大值、电子邮件正则表达式等);

  • 服务层(实际上我在 NodeJS/Express 应用程序中错过了它)只负责业务逻辑,一些业务验证。服务层对 WEB 部分一无所知(即它们可以从应用程序的其他地方调用,而不仅仅是从 web 上下文中调用);

  • 关于 DAO 层对我来说很清楚。Mongoose 模型实际上是 DAO,所以这里对我来说最清楚。

I think examples I've seen are very simple, and they shows only concepts of Node/Express, but I want to look at some real world example, with much of the business logic/validation involved in.

我认为我看到的例子非常简单,它们只展示了 Node/Express 的概念,但我想看看一些真实世界的例子,其中涉及大部分业务逻辑/验证。

EDIT:

编辑:

Another thing isn't clear to me is absent of DTO objects. Consider this example:

我不清楚的另一件事是缺少 DTO 对象。考虑这个例子:

const mongoose = require('mongoose');
const Article = mongoose.model('Article');
exports.create = function(req, res) {
    // Create a new article object
    const article = new Article(req.body);
    // saving article and other code
}

There JSON object from req.bodyis passed as parameter for creating Mongo document. It smells bad for me. I would like to work with concrete classes, not with raw JSON

来自 JSON 对象req.body作为参数传递以创建 Mongo 文档。对我来说闻起来很糟糕。我想使用具体类,而不是原始 JSON

Thanks.

谢谢。

回答by zurfyx

Controllers are God objects until you don't want them to be so...
   – you don't say zurfyx (╯°□°)╯︵ ┻━┻

控制器是上帝的对象,直到你不希望它们变成这样......
   – 你不要说 zurfyx (╯°□°)╯︵ ┻━┻

Just interested in the solution?Jump ontothe latest section "Result".

只是对解决方案感兴趣?跳到最新的“结果”部分。

┬──┬??(° -°?)

┬──┬??(° -°?)

Prior getting started with the answer, let me apologize for making this response way longer than the usual SO length. Controllers alone do nothing, it's all about the whole MVC pattern. So, I felt like it was relevant to go through all important details about Router <-> Controller <-> Service <-> Model, in order to show you how to achieve proper isolated controllers with minimum responsibilities.

在开始回答之前,让我为使这种响应方式比通常的 SO 长度更长而道歉。控制器本身什么都不做,这都是关于整个 MVC 模式的。因此,我觉得有必要了解有关路由器 <-> 控制器 <-> 服务 <-> 模型的所有重要细节,以便向您展示如何以最小的责任实现适当的隔离控制器。

Hypothetical case

假设案例

Let's start with a small hypothetical case:

让我们从一个小假设案例开始:

  • I want to have an API that serves an user search through AJAX.
  • I want to have an API that also serves the same user search through Socket.io.
  • 我想要一个通过 AJAX 为用户搜索提供服务的 API。
  • 我想要一个 API,它也可以通过 Socket.io 为相同的用户搜索提供服务。

Let's start with Express. That's easy peasy, isn't it?

让我们从 Express 开始。这很容易,不是吗?

routes.js

路由.js

import * as userControllers from 'controllers/users';
router.get('/users/:username', userControllers.getUser);

controllers/user.js

控制器/用户.js

import User from '../models/User';
function getUser(req, res, next) {
  const username = req.params.username;
  if (username === '') {
    return res.status(500).json({ error: 'Username can\'t be blank' });
  }
  try {
    const user = await User.find({ username }).exec();
    return res.status(200).json(user);
  } catch (error) {
    return res.status(500).json(error);
  }
}

Now let's do the Socket.io part:

现在让我们做 Socket.io 部分:

Since that's not a socket.ioquestion, I'll skip the boilerplate.

由于这不是socket.io问题,我将跳过样板。

import User from '../models/User';
socket.on('RequestUser', (data, ack) => {
  const username = data.username;
  if (username === '') {
    ack ({ error: 'Username can\'t be blank' });
  }
  try {
    const user = User.find({ username }).exec();
    return ack(user);
  } catch (error) {
    return ack(error);
  }
});

Uhm, something smells here...

嗯,这里有什么味道……

  • if (username === ''). We had to write the controller validator twice. What if there were ncontroller validators? Would we have to keep two (or more) copies of each up to date?
  • User.find({ username })is repeated twice. That could possibly be a service.
  • if (username === ''). 我们不得不两次编写控制器验证器。如果有n控制器验证器怎么办?我们是否必须保持每个副本的两个(或更多)最新版本?
  • User.find({ username })重复两次。那可能是一项服务。

We have just written two controllers that are attached to the exact definitions of Express and Socket.io respectively. They will most likely never break during their lifetime because both Express and Socket.io tend to have backwards compatibility. BUT, they are not reusable. Changing Express for Hapi? You will have to redo all your controllers.

我们刚刚编写了两个控制器,分别附加到 Express 和 Socket.io 的确切定义。它们在其生命周期中很可能永远不会中断,因为 Express 和 Socket.io 都倾向于向后兼容。但是,它们不可重复使用。换快递换哈皮?您将不得不重做所有控制器。

Another bad smell that might not be so obvious...

另一种可能不那么明显的难闻的气味......

The controller response is handcrafted. .json({ error: whatever })

控制器响应是手工制作的。 .json({ error: whatever })

APIs in RL are constantly changing. In the future you might want your response to be { err: whatever }or maybe something more complex (and useful) like: { error: whatever, status: 500 }

RL 中的 API 不断变化。将来,您可能希望您的回复{ err: whatever }更复杂(和有用),例如:{ error: whatever, status: 500 }

Let's get started (a possible solution)

让我们开始吧(一个可能的解决方案)

I can't call it thesolution because there is an endless amount of solutions out there. It is up to your creativity, and your needs. The following is a decent solution; I'm using it in a relatively large project and it seems to be working well, and it fixes everything I pointed out before.

我不能把它解决方案,因为有解决方案的无尽的金额在那里。这取决于您的创造力和需求。以下是一个不错的解决方案;我在一个相对较大的项目中使用它,它似乎运行良好,它修复了我之前指出的所有问题。

I'll go Model -> Service -> Controller -> Router, to keep it interesting until the end.

我会去模型 -> 服务 -> 控制器 -> 路由器,让它有趣到最后。

Model

模型

I won't go into details about the Model, because that's not the subject of the question.

我不会详细介绍模型,因为这不是问题的主题。

You should be having a similar Mongoose Model structure as the following:

你应该有一个类似的猫鼬模型结构如下:

models/User/validate.js

模型/用户/validate.js

export function validateUsername(username) {
  return true;
}

You can read more about the appropriate structure for mongoose 4.x validators here.

您可以在此处阅读有关 mongoose 4.x 验证器的适当结构的更多信息。

models/User/index.js

模型/用户/index.js

import { validateUsername } from './validate';

const userSchema = new Schema({
  username: { 
    type: String, 
    unique: true,
    validate: [{ validator: validateUsername, msg: 'Invalid username' }],
  },
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

export default User;

Just a basic User Schema with an username field and createdupdatedmongoose-controlled fields.

只是一个带有用户createdupdated名字段和猫鼬控制字段的基本用户架构。

The reason why I included the validatefield here is for you to notice that you should be doing most model validation in here, not in the controller.

我在validate此处包含该字段的原因是让您注意到您应该在这里进行大多数模型验证,而不是在控制器中。

Mongoose Schema is the last step before reaching the database, unless someone queries MongoDB directly you will always rest assured that everyone goes through your model validations, which gives you more security than placing them on your controller. Not to say that unit testing validators as they are in the previous example is trivial.

Mongoose Schema 是到达数据库之前的最后一步,除非有人直接查询 MongoDB,否则您将始终放心,每个人都经过您的模型验证,这比将它们放在控制器上更安全。并不是说前面示例中的单元测试验证器是微不足道的。

Read more about this hereand here.

在此处此处阅读有关此内容的更多信息。

Service

服务

The service will act as the processor. Given acceptable parameters, it'll process them and return a value.

该服务将充当处理器。给定可接受的参数,它将处理它们并返回一个值。

Most of the times (including this one), it'll make use of Mongoose Modelsand return a Promise(or a callback; but I would definitelyuse ES6 with Promises if you are not doing so already).

大多数时候(包括这个),它会使用猫鼬模型并返回一个Promise(或一个回调;但如果你还没有这样做的话,我肯定会使用带有Promise 的ES6)。

services/user.js

服务/user.js

function getUser(username) {
  return User.find({ username}).exec(); // Just as a mongoose reminder, .exec() on find 
                               // returns a Promise instead of the standard callback.
}

At this point you might be wondering, no catchblock? Nope, because we're going to do a cool tricklater and we don't need a custom one for this case.

此时您可能会想,没有catch阻塞?不,因为我们稍后会做一个很酷的技巧,我们不需要为这种情况定制一个。

Other times, a trivial sync service will suffice. Make sure your sync service never includes I/O, otherwise you will be blocking the whole Node.js thread.

其他时候,一个简单的同步服务就足够了。确保您的同步服务从不包含 I/O,否则您将阻塞整个 Node.js 线程

services/user.js

服务/user.js

function isChucknorris(username) {
  return ['Chuck Norris', 'Jon Skeet'].indexOf(username) !== -1;
}

Controller

控制器

We want to avoid duplicated controllers, so we'll only have acontroller for each action.

我们希望避免重复的控制器,因此我们将只为每个操作设置一个控制器。

controllers/user.js

控制器/用户.js

export function getUser(username) {
}

How does this signature look like now? Pretty, right? Because we're only interested in the username parameter, we don't need to take useless stuff such as req, res, next.

这个签名现在怎么样了?漂亮吧?因为我们只对用户名参数感兴趣,所以我们不需要采取无用的东西,比如req, res, next.

Let's add in the missing validators and service:

让我们添加缺少的验证器和服务:

controllers/user.js

控制器/用户.js

import { getUser as getUserService } from '../services/user.js'

function getUser(username) {
  if (username === '') {
    throw new Error('Username can\'t be blank');
  }
  return getUserService(username);
}

Still looks neat, but... what about the throw new Error, won't that make my application crash? - Shh, wait. We're not done yet.

看起来仍然很整洁,但是...怎么样throw new Error,这不会使我的应用程序崩溃吗?- 嘘,等等。我们还没有完成。

So at this point, our controller documentation would look sort of:

所以在这一点上,我们的控制器文档看起来有点像:

/**
 * Get a user by username.
 * @param username a string value that represents user's username.
 * @returns A Promise, an exception or a value.
 */

What's the "value" stated in the @returns? Remember that earlier we said that our services can be both sync or async (using Promise)? getUserServiceis async in this case, but isChucknorrisservice wouldn't, so it would simply return a value instead of a Promise.

什么是“价值” @returns?还记得之前我们说过我们的服务可以是同步的也可以是异步的(使用Promise)?getUserService在这种情况下是异步的,但isChucknorris服务不会,所以它只会返回一个值而不是一个 Promise。

Hopefully everyone will read the docs. Because they will need to treat some controllers different than others, and some of them will require a try-catchblock.

希望每个人都能阅读文档。因为他们需要处理一些与其他控制器不同的控制器,其中一些需要一个try-catch块。

Since we can't trust developers (this includes me) reading the docs before trying first, at this point we have to make a decision:

由于我们不能相信开发人员(包括我在内)在尝试之前阅读文档,此时我们必须做出决定:

  • Controllers to force a Promisereturn
  • Service to always return a Promise
  • 控制器强制Promise返回
  • 始终返回 Promise 的服务

? This will solve the inconsistent controller return (not the fact that we can omit our try-catch block).

? 这将解决不一致的控制器返回(而不是我们可以省略我们的 try-catch 块的事实)。

IMO, I prefer the first option. Because controllers are the ones which will chain the most Promises most of the times.

IMO,我更喜欢第一个选项。因为在大多数情况下,控制器会链接最多的 Promise。

return findUserByUsername
         .then((user) => getChat(user))
         .then((chat) => doSomethingElse(chat))

If we are using ES6 Promise we can alternatively make use of a nice property of Promiseto do so: Promisecan handle non-promises during their lifespan and still keep returning a Promise:

如果我们使用 ES6 Promise,我们也可以使用一个很好的属性Promise来做到这一点:Promise可以在它们的生命周期内处理非Promise并且仍然保持返回Promise

return promise
         .then(() => nonPromise)
         .then(() => // I can keep on with a Promise.

If the only service we call doesn't use Promise, we can make one ourselves.

如果我们调用的唯一服务不使用Promise,我们可以自己制作一个。

return Promise.resolve() // Initialize Promise for the first time.
  .then(() => isChucknorris('someone'));

Going back to our example it would result in:

回到我们的例子,它会导致:

...
return Promise.resolve()
  .then(() => getUserService(username));

We don't actually need Promise.resolve()in this case as getUserServicealready returns a Promise, but we want to be consistent.

Promise.resolve()在这种情况下,我们实际上并不需要,因为getUserService已经返回了一个 Promise,但我们希望保持一致。

If you are wondering about the catchblock: we don't want to use it in our controller unless we want to do it a custom treatment. This way we can make use of the two already built-in communication channels (the exception for errors and return for success messages) to deliver our messages through individual channels.

如果您对catch块感到疑惑:我们不想在控制器中使用它,除非我们想对其进行自定义处理。通过这种方式,我们可以利用两个已经内置的通信通道(错误消息的例外和成功消息的返回)通过单独的通道传递我们的消息。

Instead of ES6 Promise .then, we can make use of the newer ES2017 async / await(now official) in our controllers:

.then我们可以在控制器中使用较新的 ES2017 async / await现在官方)代替 ES6 Promise :

async function myController() {
    const user = await findUserByUsername();
    const chat = await getChat(user);
    const somethingElse = doSomethingElse(chat);
    return somethingElse;
}

Notice asyncin front of the function.

async前面的注意事项function

Router

路由器

Finally the router, yay!

最后是路由器,耶!

So we haven't responded anything to the user yet, all we have is a controller that we know that it ALWAYS returns a Promise(hopefully with data). Oh!, and that can possibly throw an exception if throw new Error is calledor some service Promisebreaks.

所以我们还没有对用户做出任何回应,我们只有一个控制器,我们知道它总是返回一个Promise(希望有数据)。哦!,如果throw new Error is called或某些服务Promise中断,这可能会引发异常。

The router will be the one that will, in an uniform way, control petitions and return data to clients, be it some existing data, nullor undefineddataor an error.

路由器将是一个将,在一个统一的方式,控制上访和返回数据到客户端,无论是现有的一些数据,null或者undefineddata或错误。

Router will be the ONLY one that will have multiple definitions. The number of which will depend on our interceptors. In the hypothetical case these were API (with Express) and Socket (with Socket.io).

路由器将是唯一具有多个定义的路由器。其中的数量将取决于我们的拦截器。在假设的情况下,这些是 API(使用 Express)和 Socket(使用 Socket.io)。

Let's review what we have to do:

让我们回顾一下我们必须做的事情:

We want our router to convert (req, res, next)into (username). A naive version would be something like this:

我们希望我们的路由器转换(req, res, next)(username). 一个天真的版本将是这样的:

router.get('users/:username', (req, res, next) => {
  try {
    const result = await getUser(req.params.username); // Remember: getUser is the controller.
    return res.status(200).json(result);
  } catch (error) {
    return res.status(500).json(error);
  }
});

Although it would work well, that would result in a huge amount of code duplication if we copy-pasted this snippet in all our routes. So we have to make a better abstraction.

尽管它会运行良好,但如果我们在所有路由中复制粘贴此代码段,则会导致大量代码重复。所以我们必须做一个更好的抽象。

In this case, we can create a sort of fake router client that takes a promise and nparameters and does its routing and returntasks, just like it would do in each of the routes.

在这种情况下,我们可以创建一种伪路由器客户端,它接受一个承诺和n参数并执行它的路由和return任务,就像它在每个路由中所做的那样。

/**
 * Handles controller execution and responds to user (API Express version).
 * Web socket has a similar handler implementation.
 * @param promise Controller Promise. I.e. getUser.
 * @param params A function (req, res, next), all of which are optional
 * that maps our desired controller parameters. I.e. (req) => [req.params.username, ...].
 */
const controllerHandler = (promise, params) => async (req, res, next) => {
  const boundParams = params ? params(req, res, next) : [];
  try {
    const result = await promise(...boundParams);
    return res.json(result || { message: 'OK' });
  } catch (error) {
    return res.status(500).json(error);
  }
};
const c = controllerHandler; // Just a name shortener.

If you are interested in knowing more about this trick, you can read about the full version of this in my other reply in React-Redux and Websockets with socket.io("SocketClient.js" section).

如果您有兴趣了解有关此技巧的更多信息,可以在我在React-Redux 和 Websockets with socket.io(“SocketClient.js”部分)中的其他回复中阅读有关此技巧的完整版本。

How would your route look like with the controllerHandler?

您的路线与controllerHandler?

router.get('users/:username', c(getUser, (req, res, next) => [req.params.username]));

A clean one line, just like in the beginning.

一条干净的线,就像一开始一样。

Further optional steps

进一步的可选步骤

Controller Promises

控制器承诺

It only applies to those who use ES6 Promises. ES2017 async / awaitversion already looks good to me.

它只适用于那些使用 ES6 Promises 的人。ES2017async / await版本对我来说已经很好了。

For some reason, I dislike having to use Promise.resolve()name to build the initialize Promise. It's just not a clear what's going on there.

出于某种原因,我不喜欢必须使用Promise.resolve()name 来构建初始化 Promise。目前还不清楚那里发生了什么。

I'd rather replace them for something more understandable:

我宁愿用更容易理解的东西替换它们:

const chain = Promise.resolve(); // Write this as an external imported variable or a global.

chain
  .then(() => ...)
  .then(() => ...)

Now you know that chainmarks the start of a chain of Promises. So does everyone who reads your code, or if not, they at least assume it's a chain a service functions.

现在您知道这chain标志着 Promise 链的开始。阅读您的代码的每个人也是如此,如果没有,他们至少会假设它是一个服务功能链。

Express error handler

快速错误处理程序

Express does have a default error handler which you should be using to capture at least the most unexpected errors.

Express 确实有一个默认的错误处理程序,您应该使用它来捕获至少最意外的错误。

router.use((err, req, res, next) => {
  // Expected errors always throw Error.
  // Unexpected errors will either throw unexpected stuff or crash the application.
  if (Object.prototype.isPrototypeOf.call(Error.prototype, err)) {
    return res.status(err.status || 500).json({ error: err.message });
  }

  console.error('~~~ Unexpected error exception start ~~~');
  console.error(req);
  console.error(err);
  console.error('~~~ Unexpected error exception end ~~~');


  return res.status(500).json({ error: '?? ? (????????????) ?' });
});

What's more, you should probably be using something like debugor winstoninstead of console.error, which are more professional ways to handle logs.

更重要的是,您可能应该使用debugwinston 之类的东西而不是console.error,它们是处理日志的更专业的方法。

And that's is how we plug this into the controllerHandler:

这就是我们如何将其插入到controllerHandler

  ...
  } catch (error) {
    return res.status(500) && next(error);
  }

We are simply redirecting any captured error to Express' error handler.

我们只是将任何捕获的错误重定向到 Express 的错误处理程序。

Error as ApiError

错误为 ApiError

Erroris considered the default class to encapsulate errors in when throwing an exception in Javascript. If you really only want to track your own controlled errors, I'd probably change the throw Errorand the Express error handler from Errorto ApiError, and you can even make it fit your needs better by adding it the status field.

Error被认为是在 Javascript 中抛出异常时封装错误的默认类。如果您真的只想跟踪您自己的受控错误,我可能会将throw Error和 Express 错误处理程序从Error更改为ApiError,您甚至可以通过将其添加到状态字段来使其更适合您的需求。

export class ApiError {
  constructor(message, status = 500) {
    this.message = message;
    this.status = status;
  }
}

Additional information

附加信息

Custom exceptions

自定义异常

You can throw any custom exception at any point by throw new Error('whatever')or by using new Promise((resolve, reject) => reject('whatever')). You just have to play with Promise.

您可以在任何时候通过throw new Error('whatever')或使用抛出任何自定义异常new Promise((resolve, reject) => reject('whatever'))。你只需要玩Promise

ES6 ES2017

ES6 ES2017

That's very opinionated point. IMO ES6(or even ES2017, now having an official set of features) is the appropriate way to work on big projects based on Node.

这是非常有见地的观点。IMO ES6(或什至ES2017,现在拥有一组官方功能)是处理基于Node.js 的大型项目的合适方式。

If you aren't using it already, try looking at ES6features and ES2017and Babeltranspiler.

如果您还没有使用它,请尝试查看ES6特性以及ES2017Babel转译器。

Result

结果

That's just the complete code (already shown before), with no comments or annotations. You can check everything regarding this code by scrolling up to the appropriate section.

这只是完整的代码(之前已经展示过),没有注释或注释。您可以通过向上滚动到相应部分来检查有关此代码的所有内容。

router.js

路由器.js

const controllerHandler = (promise, params) => async (req, res, next) => {
  const boundParams = params ? params(req, res, next) : [];
  try {
    const result = await promise(...boundParams);
    return res.json(result || { message: 'OK' });
  } catch (error) {
    return res.status(500) && next(error);
  }
};
const c = controllerHandler;

router.get('/users/:username', c(getUser, (req, res, next) => [req.params.username]));

controllers/user.js

控制器/用户.js

import { serviceFunction } from service/user.js
export async function getUser(username) {
  const user = await findUserByUsername();
  const chat = await getChat(user);
  const somethingElse = doSomethingElse(chat);
  return somethingElse;
}

services/user.js

服务/user.js

import User from '../models/User';
export function getUser(username) {
  return User.find({}).exec();
}

models/User/index.js

模型/用户/index.js

import { validateUsername } from './validate';

const userSchema = new Schema({
  username: { 
    type: String, 
    unique: true,
    validate: [{ validator: validateUsername, msg: 'Invalid username' }],
  },
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

export default User;

models/User/validate.js

模型/用户/validate.js

export function validateUsername(username) {
  return true;
}

回答by rohit salaria

Everyone has its own way of dividing the project into certain folders. the structure which I use is

每个人都有自己的方式将项目划分为特定的文件夹。我使用的结构是

  • config
  • logs
  • routes
  • controllers
  • models
  • services
  • utils
  • app.js/server.js/index.js (any name u prefer)
  • 配置
  • 日志
  • 路线
  • 控制器
  • 楷模
  • 服务
  • 实用程序
  • app.js/server.js/index.js(任何你喜欢的名字)

config folder contain configuration files like database connection settings for all phase of development like "production","development","testing"

config 文件夹包含配置文件,如所有开发阶段的数据库连接设置,如“生产”、“开发”、“测试”

example

例子

'use strict'
var dbsettings = {
    "production": {
//your test settings
    },
    "test": {

    },
    "development": {
        "database": "be",
        "username": "yourname",
        "password": "yourpassword",
        "host": "localhost",
        "connectionLimit": 100
    }
}
module.exports = dbsettings

log folder contain your connection logs error logs for debugging

日志文件夹包含用于调试的连接日志错误日志

controller is for validating your req data and business logic

控制器用于验证您的请求数据和业务逻辑

example

例子

const service = require("../../service")
const async = require("async")
exports.techverify = (data, callback) => {

    async.series([
        (cb) => {
            let searchObject = { accessToken: data.accessToken }
            service.admin.get(searchObject, (err, result) => {
                if (err || result.length == 0) {
                    callback(err, { message: "accessToken is invalid" })
                } else {
                    delete data.accessToken
                    service.tech.update(data, { verified: true }, (err, affe, res) => {
                        if (!err)
                            callback(err, { message: "verification done" })
                        else
                            callback(err, { message: "error occured" })
                    })
                }
            })
        }
    ])
}

models is for defining your db schema

模型用于定义您的数据库架构

example mongoDb schema

示例 mongoDb 架构

'use strict'
let mongoose = require('mongoose');
let schema = mongoose.Schema;
let user = new schema({
    accesstoken: { type: String },
    firstname: { type: String },
    lastname: { type: String },
    email: { type: String, unique: true },
    image: { type: String },
    phoneNo: { type: String },
    gender: { type: String },
    deviceType: { type: String },
    password: { type: String },
    regAddress: { type: String },
    pincode: { type: String },
    fbId: { type: String, default: 0 },
    created_at: { type: Date, default: Date.now },
    updated_at: { type: Date, default: Date.now },
    one_time_password: { type: String },
    forgot_password_token: { type: String },
    is_block: { type: Boolean, default: 0 },
    skin_type: { type: String },
    hair_length: { type: String },
    hair_type: { type: String },
    credits: { type: Number, default: 0 },
    invite_code: { type: String },
    refered_by: { type: String },
    card_details: [{
        card_type: { type: String },
        card_no: { type: String },
        card_cv_no: { type: String },
        created_at: { type: Date }
    }]
});
module.exports = mongoose.model('user', user);

services is for writing your data base query avoid writing queries in controller try to write query in this folder and call it in the controller

服务用于编写您的数据库查询避免在控制器中编写查询尝试在此文件夹中编写查询并在控制器中调用它

queries using mongoose

使用猫鼬查询

'use strict'
const modelUser = require('../../models/user');
exports.insert = (data, callback) => {
    console.log('mongo log for insert function', data)
    new modelUser(data).save(callback)
}
exports.get = (data, callback) => {
    console.log('mongo log for get function', data)
    modelUser.find(data, callback)
}
exports.update = (data, updateData, callback) => {
    console.log('mongo log for update function', data)
    modelUser.update(data, updateData, callback);
}
exports.getWithProjection = (data, projection, callback) => {
    console.log('mongo log for get function', data)
    modelUser.find(data, projection, callback)
}

utils is for common utility function which is commonly used in your project maybe like encrypt,decrypt password etc

utils 是你的项目中常用的常用工具函数,比如加密、解密密码等

example

例子

exports.checkPassword = (text, psypherText) => {
    console.log("checkPassword executed")
    console.log(text, psypherText)
    return bcrypt.compareSync(text, psypherText)
}
exports.generateToken = (userEmail) => {
    return jwt.sign({ unique: userEmail, timeStamp: Date.now }, config.keys.jsonwebtoken)
}

回答by Imperator

rohit salaria's answer basically explains the same app structure you are used to in java.

rohit salaria 的回答基本上解释了您在 java 中习惯的相同应用程序结构。

  • Controllers are the Controllers in Java
  • Models are the Data Access Layer
  • Services are the Service Layer
  • 控制器是 Java 中的控制器
  • 模型是数据访问层
  • 服务是服务层

I have a few remarks though. The first and most important one is that this is not Java. It may sound obvious but just look at your question and see that you are for looking the same development experience with the same concepts you got used in the Java world. My following remarks are just the explanation for this.

不过我有几点意见。第一个也是最重要的一点是这不是 Java。这听起来很明显,但只要看看您的问题,就会发现您正在寻找与您在 Java 世界中使用的相同概念相同的开发体验。我下面的评论只是对此的解释。

Missing DTOs. In Java they are just required, period. In a Java web application, where you store your data in a relational database and sending and receiving data to the front-end in JSON, it is natural that you convert the data to a Java object. However in a Node app everything is javascript and JSON. That is one of the strengths of the platform. With JSON being the common data format, it is not needed to write code or depend on libraries to translate between the data format of your layers.

缺少 DTO。在 Java 中,它们只是必需的,句号。在 Java Web 应用程序中,您将数据存储在关系数据库中并以 JSON 向前端发送和接收数据,很自然地将数据转换为 Java 对象。然而,在 Node 应用程序中,一切都是 javascript 和 JSON。这是该平台的优势之一。由于 JSON 是通用数据格式,因此不需要编写代码或依赖库来在层的数据格式之间进行转换。

Passing the data object directly from the request to the model. Why not? Having JSON as the common data format from the front-end to the database enables you to easily synchronize the data model of your app between all your layers. Of course you don't have to go this way, but it is sufficient most of the time, so why not use it? As for validation it is done in the model, where it belongs according to the MVC theory (, and not in the controller where laziness and pragmatism often puts it :)).

将数据对象直接从请求传递到模型。为什么不?将 JSON 作为从前端到数据库的通用数据格式,您可以轻松地在所有层之间同步应用程序的数据模型。当然你不必这样走,但大多数时候它已经足够了,为什么不使用它呢?至于验证,它是在模型中完成的,根据 MVC 理论,它属于它(而不是在控制器中,懒惰和实用主义经常把它放在那里:))。

For the final thought I want to add, that this is not the best platform when it comes to project size scaling. It is nod bat at all, but Java is better in that aspect.

对于我想补充的最后一个想法,就项目规模缩放而言,这不是最佳平台。它完全是点头蝙蝠,但 Java 在这方面更好。

回答by manish kumar

simple and basic rule

简单而基本的规则

  1. Keep components associated close to each other.

  2. Divide the page into components and work

  3. All the dependent components should be together

  4. shared things should be kept independent of all the other components .

  1. 保持关联的组件彼此靠近。

  2. 将页面分成组件并工作

  3. 所有的依赖组件应该在一起

  4. 共享的东西应该独立于所有其他组件。

Finally every language is sweet. Its just that how familiar you are with the language.You can only win the battle if you familiar with you sword.

最后,每一种语言都是甜蜜的。只是对语言的熟悉程度,只有熟悉了刀剑,才能打赢这场仗。

i am developing Angular2 application using NodeJS,Angular2 i'll help you with my directory structure.

我正在使用 NodeJS 开发 Angular2 应用程序,Angular2 我会帮助你解决我的目录结构。

main Module

主模块

`the main module` 

subModule

子模块

 `the sub module structure`

shared Module

共享模块

`keep the shared folder as a separate module`

Hope it helps :)

希望能帮助到你 :)