Node.js   发布时间:2022-04-24  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了node.js – 使用Node / Express构建企业应用程序大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
我想了解如何使用Node / Express / Mongo(实际使用MEAN堆栈)来构建企业应用程序.

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

>路线
>控制器
>型号

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

另一方面,路由似乎像过度工程,因为他们所做的就是将端点(路径)映射到控制器方法.

我有Scala / Java背景,所以我有习惯把3层的所有逻辑 – 控制器/服务/道.

对我来说以下说法是理想的:

>控制器仅负责与WEB部分进行交互,即编组/解组合,一些简单的验证(必需,最小,最大,电子邮件正则表达式等);
>服务层(实际上我在NodeJS / Express应用程序中错过)仅负责业务逻辑,部分业务验证.服务层不知道有关WEB部分的信息(即它们可以从其他应用程序地址调用,不仅可以从Web上下文);
关于DAO层对我来说很清楚. Mongoose模型实际上是DAO,所以我最清楚的是在这里.

我认为我看到的例子非常简单,它们只显示了Node / Express的概念,但是我想看看一些真实世界的例子,涉及到很多业务逻辑/验证.

编辑:

另外还不清楚的是我没有DTO对象.虑这个例子:

const mongoose = @R_616_10613@ire('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
}

来自req.body的JSON对象作为创建Mongo文档的参数传递.它对我来说气味不好我想使用具体的类,而不是原始的JSON

谢谢.

解决方法

控制器是神的对象,直到你不希望他们是这样的
– 你不说zurfyx(╯°□°)╯(┻━

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

┬──ノ(° – °ノ)

在开始回答之前,请让我道歉,让这种回应方式比通常的SO长度更长.控制器一个人无所作为,这一切都是关于整个MVC模式.所以,我觉得有必要了解关于路由器的所有重要细节.控制器 – 服务< - >模型,以显示如何以最小的责任实现适当的隔离控制器.

假设情况

我们从一个小的假设开始:

>我想要一个API,用于通过AJAX进行用户搜索.
>我想要一个API,也可以通过Socket.io进行相同的用户搜索.

我们开始快速.这很容易,不是吗?

routes.js

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

控制器/ user.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);
  }
}

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

既然这不是一个socket.io的问题,我会跳过样板.

import User from '../models/User';
socket.on('@R_616_10613@estuser',(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);
  }
});

欧姆,这里闻到一些东西

> if(username ===”).我们必须两次写控制器验证器.如果有n个控制器验证器怎么办?我们是否必须保留两个(或更多)每个最新的副本?
> User.find({usernamE})重复两次.这可能是一个服务.

我们刚刚写了两个控制器,分别附加到Express和Socket.io的确切定义.他们很有可能在他们的一生中永远不会破裂,因为Express和Socket.io往往具有向后兼容性.但是,它们不可重用. Hapi快递快递你必须重做所有的控制器.

另一种可能不那么明显的不好的气味

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

RL中的API正在不断变化.在将来你可能希望你的回应是{err:whatever}或者可能更复杂(有用的),如:{error:whatever,status:500}

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

我不能称之为解决方案,因为在那里有无数的解决方案.这取决于你的创造力和你的需要.以下是一个体面的解决方案;我在一个相对较大的项目中使用它,它似乎运行良好,它修复了我之前指出的一切.

我会去模型 – >服务 – >控制器 – >路由器,保持有趣,直到结束.

模型

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

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

车型/用户/ validate.js

export function validateUsername(userName) {
  return true;
}

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

车型/用户/ index.js

import { validateUsername } from './validate';

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

const User = mongoose.model('User',userscheR_451_11845@a);

export default User;

只是一个基本的用户模式与用户名字段,并创建更新的mongoose控制字段.

在这里列出验证字段的原因是让您注意到,您应该在这里做大多数模型验证,而不是在控制器中.

@H_680_5@mongoose scheR_451_11845@a是达到数据库之前的最后一步,除非有人直接@L_557_47@mongoDB,否则您将始终放心,每个人都会经历您的模型验证,这样您就可以将它们放置在控制器上.不要说单位测试验证器在前面的例子中是微不足道的.

阅读更多关于这herehere.

服务

该服务将作为处理器.给定可接受的参数,它将处理它们并返一个值.

大多数时候(包括一个),它会使用Mongoose Models并返一个Promise(或回调;但如果你还没有这样做,那么I would definitely使用ES6与PromisE).

服务/ 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.
}

在这一点上你可能会想知道,没有catch块?不,因为我们以后会做一个很棒的戏法,我们不需要这个例子.

其他时候,一个琐碎的同步服务就足够了.确保您的同步服务从不包含I / O,否则您将阻止the whole Node.js thread.

服务/ user.js的

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

调节器

我们希望避免重复的控制器,所以我们只有一个控制器为每个动作.

控制器/ user.js的

export function getUser(userName) {
}

这个签名现在如何?漂亮,对吧?因为我们只对username参数感兴趣,所以我们不需要使用无用的东西,如req,next.

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

控制器/ user.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);
}

仍然看起来整洁,但…扔新的错误,不会使我的应用程序崩溃吗?嗯,等等.我们还没有完成

所以在这一点上,我们的控制器文档将会是:

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

在@returns中所说的“价值”是什么?记住,早些时候我们表示我们的服务可以同步或异步(使用PromisE)在这种情况下,getUserservice是异步的,但isChucknorris服务不会,所以它只是返回一个值而不是一个Promise.

希望大家都会阅读文档.因为他们需要处理一些不同于其他控制器的控制器,其中一些将需要一个try-catch块.

由于我们不能信任开发人员(包括我在内),请先阅读文档,此时我们必须作出决定:

控制器强制承诺回报
>服务永远返回承诺

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

IMO,我更喜欢第一个选择.因为控制器是连接大多数时候最有希望的控制器.

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

如果我们使用ES6 Promise,我们可以使用Promise的一个不错的财产来做到这一点:Promise可以在他们的使用期限内处理非承诺,并且仍然继续返回承诺:

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

如果我们所说的唯一服务不是使用承诺,我们可以自己做一件事.

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

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

...
return Promise.resolve()
  .then(() => getUserservice(userName));

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

如果你想知道catch块:我们不想在我们的控制器中使用它,除非我们要做一个定制的处理.这样,我们可以利用两个已经内置的通信通道(错误的异常和成功消息的返回),通过各个通道发送消息.

而不是ES6 Promise.而且,我们可以利用我们的控制器中更新的ES2017 async / await(now official):

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

功能前注意异步.

路由器

最后路由器,呀!

所以我们还没有对用户作出任何回应,我们所有的都是一个控制器,我们知道它总是返回一个承诺(希望有数据).哦,这可能会抛出一个异常,如果抛出新的错误调用或一些服务Promise中断.

路由器将以统一的方式控制请求并将数据返回给客户端,无论是一些现有数据,零还是未定义的数据或错误.

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

我们来看看我们要做的事情:

我们希望路由器将(req,next)转换成(用户名).一个天真的版本会是这样的

router.get('users/:username',(req,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);
  }
});

尽管如此,如果我们在所有路线中复制 – 粘贴此代码段,那么这样做会导致巨大的代码重复.所以我们必须做一个更好的抽象.

在这种情况下,我们可以创建一种假路由器客户端,它承诺和n个参数,并且执行路由和返回任务,就像在每个路由中一样.

/**
 * 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,next),all of which are optional
 * that maps our desired controller parameters. I.e. (req) => [req.params.username,...].
 */
const controllerHandler = (promise,params) => async (req,next) => {
  const boundParams = params ? params(req,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.

如果您有兴趣了解这个技巧,可以在React-Redux and Websockets with socket.io的其他答复(“SocketClient.js”部分)中阅读有关完整版本的完整版本.

controllerHandler的路由怎么样?

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

一条干净的一条线,就像一开始.

进一步可选步骤

控制器承诺

它只适用于使用ES6 Promises的用户. ES2017异步/等待版本对我来说看起来不错.

由于某些原因,我不喜欢使用Promise.resolve()名称来构建初始化Promise.这不是很清楚.

我宁愿替他们更容易理解的东西:

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

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

现在你知道连锁标志着一系列承诺的开始.读取代码的每个人也是如此,至少假设它是一个链接服务功能.

Express错误处理程序

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

router.use((err,req,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: '⁽ƈ ͡ (ुŏ̥̥̥̥םŏ̥̥̥̥) ु' });
});

此外,您应该使用像debugwinston这样的东西,而不是console.error,这是更专业的处理日志的方法.

这就是我们如何将它插入到controllerHandler中:

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

我们只是将捕获的所有错误重定向到Express的错误处理程序.

作为ApiError出错

错误被认为是在JavaScript中抛出异常时封装错误认类.如果你真的只想跟踪自己的控制错误,我可能会将错误和Express错误处理程序从Error更改为ApiError,甚至可以通过添加状态字段来更好地满足您的需求.

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

附加信息

自定义异常

通过抛出新的错误(“任何”)或使用新的Promise((resolve,reject)=> reject(‘whatever’)),您可以随时抛出任何自定义异常.你只需要玩Promise.

ES6 ES2017

这是非常值得注意的. IMO ES6(甚至ES2017,现在具有官方功能)是基于Node的大型项目的适当方式.

如果您没有使用它,请尝试查看ES6功能ES2017Babel透明.

结果

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

router.js

const controllerHandler = (promise,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',next) => [req.params.username]));

控制器/ user.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;
}

服务/ user.js的

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

车型/用户/ index.js

import { validateUsername } from './validate';

const userscheR_451_11845@a = new scheR_451_11845@a({
  username: { 
    type: String,userscheR_451_11845@a);

export default User;

车型/用户/ validate.js

export function validateUsername(userName) {
  return true;
}

大佬总结

以上是大佬教程为你收集整理的node.js – 使用Node / Express构建企业应用程序全部内容,希望文章能够帮你解决node.js – 使用Node / Express构建企业应用程序所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。