上一篇对项目的目录结构和 app.js 等一些文件做了一些改造,然而那只是开始。

接下来将做进一步的改造和完善。

我们先看看几个主要的脚本文件,下面的代码是我稍微修改过并添加注释的,方便理解每句代码的意思。

app.js:

var express = require('express'),
path = require('path'),
favicon = require('serve-favicon'),
logger = require('morgan'),
cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'),
routes = require('./routes/index'),
users = require('./routes/users'); //生成一个 express 实例
var app = express(); //指定 web 应用的标题栏小图标的路径为:/static/favicon.ico
app.use(favicon(path.join(__dirname, 'static', 'favicon.ico')));
//加载日志中间件
app.use(logger('dev'));
//加载解析 json 的中间件
app.use(bodyParser.json());
//加载解析 urlencoded 请求体的中间件
app.use(bodyParser.urlencoded({ extended: false }));
//加载解析 cookie 的中间件
app.use(cookieParser());
//设置 static 文件夹为存放静态文件的目录
app.use(express.static(path.join(__dirname, 'static'))); //路由控制器
app.use('/', routes);
app.use('/users', users); //捕获404错误,并转发到错误处理器
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
}); //错误处理器
if (app.get('env') === 'development') {
//开发环境下的错误处理器,返回完整的错误对象
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.send({
code: 0,
message: err.message,
error: err
});
});
} app.use(function(err, req, res, next) {
//生产环境下的错误处理器,只返回错误提示消息
res.status(err.status || 500);
res.send({
code: 0,
message: err.message
});
}); //导出app实例供其他模块调用
module.exports = app;

原本错误处理器里面调用了 res.render 方法,但它依赖于模版引擎。而前面我已经把模版引擎配置相关的代码移除掉了,所以这里改用 res.send 方法实现。

代码里指定了网站小图标,记得将小图标文件放到对应位置,否则会报错,实在没有小图片就把那行代码注释掉吧。

start.js(PS:也就是之前的 bin/www)

#!/usr/bin/env node
//上面一行表明此文件是 node 可执行文件 var app = require('./app'),//引入 app.js 导出的 app 实例
debug = require('debug')('test:server'),//引入 debug 模块,打印调试日志
http = require('http'),//引入 http 模块,用以创建 http 服务
port = process.env.PORT || '3000',//环境变量如果设置了端口号,就用环境变量设置的,否则使用默认值3000
server = null; //设置端口号
app.set('port', port); //创建 http 服务
server = http.createServer(app); //监听端上面设置的口号
server.listen(port);
//绑定错误事件处理函数
server.on('error', onError);
//绑定监听事件处理函数
server.on('listening', onListening); //错误事件处理函数
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
} var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; //对特定的错误类型做友好的处理
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
} //监听事件处理函数
function onListening() {
var addr = server.address(),
bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind);
}

本来在 start.js 里有一个 normalizePort 函数,用来做端口号转换的。但我觉得没有什么必要,只要在配置端口号的时候注意即可,没必要做这样的适配,于是将它移除,简化代码。

routes/index.js 和 routes/users.js:

//routes/index.js
var express = require('express');
var router = express.Router(); router.get('/', function(req, res, next) {
res.send('hello world');
}); module.exports = router;
//routes/users.js
var express = require('express');
var router = express.Router(); router.get('/', function(req, res, next) {
res.send('respond with a resource');
}); module.exports = router;

上面两段脚本,生成一个路由实例用来捕获访问主页的 GET 请求,导出这个路由并在 app.js 中通过 app.use 方法被加载。当访问对应的 url 时,就会执行相应的处理回调。

index.js 本来也是使用了 res.render 方法,也被改成使用 res.send 方法了。原因和上面提到过的同样修改点相同,这里就不再重复。

然而,目前的这种路由配置形式我觉得不怎么美丽,因为我们可以试想一下,假如应用比较复杂,路由配置也相应的更复杂,那么在 app.js 就会出现大量的路由配置相关代码...

为了避免这种情况,导致 app.js 过于臃肿,于是我将路由配置相关的代码重新规划了一下。

先看看改造后的目录结构(主要变动在 routes):

本来 routes 目录下有多个 js 文件,现在变成只有一个 main.js 了,然后里面又多了一个 modules 目录,里面会有多个 js 文件,它们都是 main.js 的子模块。

比如图中看到的两个 menuMod.js 和 userMod.js,分别是菜单以及用户相关的业务逻辑,以此规则划分模块。

而 routes 目录下的 main.js 则是一个路由配置的主文件,通过引入 routes/modules 目录中的各个子模块,指定不同请求方式,不同 url 所对应的不同回调函数。

改造过程中,根据目录结构的改变,还要对 app.js 作对应修改,下面看看修改后的代码...

app.js

var express = require('express'),
path = require('path'),
favicon = require('serve-favicon'),
logger = require('morgan'),
cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'),
routes = require('./routes/main'); //生成一个 express 实例
var app = express(); //指定 web 应用的标题栏小图标的路径为:/static/favicon.ico
app.use(favicon(path.join(__dirname, 'static', 'favicon.ico')));
//加载日志中间件
app.use(logger('dev'));
//加载解析 json 的中间件
app.use(bodyParser.json());
//加载解析 urlencoded 请求体的中间件
app.use(bodyParser.urlencoded({ extended: false }));
//加载解析 cookie 的中间件
app.use(cookieParser());
//设置 static 文件夹为存放静态文件的目录
app.use(express.static(path.join(__dirname, 'static'))); //配置路由
routes(app); //捕获404错误,并转发到错误处理器
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
}); //错误处理器
if (app.get('env') === 'development') {
//开发环境下的错误处理器,将错误信息渲染 error 模版并显示到浏览器中
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
} app.use(function(err, req, res, next) {
//生产环境下的错误处理器,不会将错误信息泄露给用户
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
}); //导出 app 实例供其他模块调用
module.exports = app;

routes/main.js

var _ = require('underscore'),//引入 underscore 模块
userMod = require('./modules/userMod'),//引入 user 模块
menuMod = require('./modules/menuMod'),//引入 menu 模块
config = {
get: {
'/user/list': userMod.getUserList,
'/user/status': userMod.checkStatus,
'/menu/list': menuMod.getMenuList
},
post: {
'/user/save': userMod.save,
'/menu/save': menuMod.save
}
};//路由配置 module.exports = function (app) { //分析路由配置对象,逐一处理
_.each(config, function (subConfig, method) { _.each(subConfig, function (func, url) { app[method](url, func); }); }); };

routes/modules/userMod.js

var userMod = {
getUserList: function (req, res) {
res.send('userList');
},
checkStatus: function (req, res) {
res.send('userStatus');
},
save: function (req, res) {
res.send('userSave');
}
}; module.exports = userMod;

routes/modules/menuMod.js

var menuMod = {
getMenuList: function (req, res) {
res.send('menuList');
},
save: function (req, res) {
res.send('menuSave');
}
}; module.exports = menuMod;

上面的改造,还添加了一个项目依赖库,它就是知名的 underscore。

打开命令行工具,进入项目根目录,执行下面指令,安装 underscore,并将它加入到 package.json 的 dependencies 之中:

npm install underscore --save

经过这样的改造后,以后修改路由规则,只需要在 routes/main.js 里修改 config 配置对象,然后根据需要添加新的子模块,或者在原有的子模块里加入新的方法即可。

需要注意的是,每个子模块里的每一个方法,都有两个一样的参数 req 和 res,也就是 request 对象 和 response 对象。

至于这两个对象都提供了什么方法,什么属性,可以去查 API,传送门:http://expressjs.jser.us/api

OK,到目前为止,整个项目从创建到现在,已经面目全非了。

但我居然跑都没跑过一次...是不是感觉很不靠谱?

其实这个项目是根据之前的经验重新整理的,所以在弄的时候,基本就没测试是否能运行了。

但为防百密一疏,或者说是谨慎起见,这里我们运行一下试试看。

打开命令行工具,进入到项目根目录,然后运行下面指令:

node start

如果没看到报错信息,说明服务成功运行了。

接下来,我们用浏览器访问 http://localhost:3000/user/list

如果能看到浏览区里出现 userList,说明成功返回了

再试试用浏览器访问 http://localhost:3000/user/status

浏览区里出现 userStatus,OK,没问题了

至此,我们的应用已经成功实现了根据不同的请求,做出相应相应的基本功能。后面可以根据需要慢慢完善细节了~

【Express系列】第2篇——主程序的改造的更多相关文章

  1. Mysql高手系列 - 第21篇:什么是索引?

    Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 这是Mysql系列第21篇. 本文开始连续3篇详解mysql索引: 第1篇来说说什么是索引? 第2篇详解Mysql中 ...

  2. Mysql高手系列 - 第27篇:mysql如何确保数据不丢失的?我们借鉴这种设计思想实现热点账户高并发设计及跨库转账问题

    Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 欢迎大家加我微信itsoku一起交流java.算法.数据库相关技术. 这是Mysql系列第27篇. 本篇文章我们先来 ...

  3. 从0到1用react+antd+redux搭建一个开箱即用的企业级管理后台系列(基础篇)

    背景 ​ 最近因为要做一个新的管理后台项目,新公司大部分是用vue写的,技术栈这块也是想切到react上面来,所以,这次从0到1重新搭建一个react项目架子,需要考虑的东西的很多,包括目录结构.代码 ...

  4. javascript面向对象系列第三篇——实现继承的3种形式

    × 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...

  5. 【Windows编程】系列第五篇:GDI图形绘制

    上两篇我们学习了文本字符输出以及Unicode编写程序,知道如何用常见Win32输出文本字符串,这一篇我们来学习Windows编程中另一个非常重要的部分GDI图形绘图.Windows的GDI函数包含数 ...

  6. 【Windows编程】系列第三篇:文本字符输出

    上一篇我们展示了如何使用Windows SDK创建基本控件,本篇来讨论如何输出文本字符. 在使用Win32编程时,我们常常要输出文本到窗口上,Windows所有的文本字符或者图形输出都是通过图形设备接 ...

  7. 深入理解javascript函数系列第三篇——属性和方法

    × 目录 [1]属性 [2]方法 前面的话 函数是javascript中的特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样.甚至可以用Function()构造函数来创建新的函数对象.本 ...

  8. 深入理解javascript作用域系列第四篇——块作用域

    × 目录 [1]let [2]const [3]try 前面的话 尽管函数作用域是最常见的作用域单元,也是现行大多数javascript最普遍的设计方法,但其他类型的作用域单元也是存在的,并且通过使用 ...

  9. 深入理解javascript作用域系列第三篇——声明提升(hoisting)

    × 目录 [1]变量 [2]函数 [3]优先 前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javasc ...

随机推荐

  1. Freetype字体引擎分析与指南

    Freetype字体引擎分析与指南,很不错的一篇教程,推荐!!

  2. 《Python3网络爬虫开发实战》PDF+源代码+《精通Python爬虫框架Scrapy》中英文PDF源代码

    下载:https://pan.baidu.com/s/1oejHek3Vmu0ZYvp4w9ZLsw <Python 3网络爬虫开发实战>中文PDF+源代码 下载:https://pan. ...

  3. hdu 2189 悼念512汶川大地震遇难同胞——来生一起走

    题目 这道题用了,埃式筛选法和背包,我自己没有做出来,看了别人的代码,我也做不出来,特别是c[j]+=c[j-b[i]];弄了好久都没有弄懂. 这道题的解题思路:主要是先把150以内的所有素数找出来, ...

  4. iterm2 学习笔记

    itrem 笔记 选中即复制,有两种方式. 在新Tab中自动使用前一Tab路径,该怎么用? 系统热键:option+space 自动完成:输入打头几个字母,然后输入command+“;” iterm2 ...

  5. git提交提示workspace.xml出现conflicted

    问题:在github上管理项目,多次提交以后提交提示workspace.xml出现conflicted原因:Android项目在根目录的.gitignore文件中没有添加.idea文件夹忽略. 解决办 ...

  6. (原创)Hibernate persistentSet的remove()方法不起作用

    情景再现:hibernate 多对多  User对象(员工).Educate对象(培训课程),我想干的事情是想把第三方表格user_educate中的员工_培训课程中的一行信息删掉(删掉员工所选的一门 ...

  7. Windows下VM安装MacOS

    我们在使用Windows操作系统的情况下,不去买苹果电脑,想去玩玩Mac苹果操作系统,有两种选择,一种就是安装黑苹果(就是在非苹果电脑上安装MacOS)这种方式不推荐,因为你会遇到很多很多不兼容问题, ...

  8. leetcode 杨辉三角

    给定一个非负整数 numRows,生成杨辉三角的前 numRows 行. 在杨辉三角中,每个数是它左上方和右上方的数的和. 示例: 输入: 5 输出: [ [1], [1,1], [1,2,1], [ ...

  9. 安装 rabbitmq ,通过生成器获取redis列表数据 与 Celery 分布式异步队列

    一.安装rabbitmq  @全体成员 超简易安装rabbitmq文档 1.安装配置epel源rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/ ...

  10. Debug Dart at External Terminal

    launch.json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions ...