大佬教程收集整理的这篇文章主要介绍了node.js – 使用Node / Express构建企业应用程序,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
阅读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
谢谢.
┬──ノ(° – °ノ)
在开始回答之前,请让我道歉,让这种回应方式比通常的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部分:
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}
我不能称之为解决方案,因为在那里有无数的解决方案.这取决于你的创造力和你的需要.以下是一个体面的解决方案;我在一个相对较大的项目中使用它,它似乎运行良好,它修复了我之前指出的一切.
我会去模型 – >服务 – >控制器 – >路由器,保持有趣,直到结束.
模型
我不会详细介绍模型,因为这不是问题的主题.
车型/用户/ 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,否则您将始终放心,每个人都会经历您的模型验证,这样您就可以将它们放置在控制器上.不要说单位测试验证器在前面的例子中是微不足道的.服务
该服务将作为处理器.给定可接受的参数,它将处理它们并返回一个值.
大多数时候(包括这一个),它会使用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: '⁽ƈ ͡ (ुŏ̥̥̥̥םŏ̥̥̥̥) ु' }); });
此外,您应该使用像debug或winston这样的东西,而不是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功能和ES2017和Babel透明.
结果
这只是完整的代码(以前已经显示),没有注释或注释.您可以通过滚动到相应的部分来查看有关此代码的所有内容.
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,请注明来意。