foreword(前言)
上一篇主要简单介绍了express的一些高阶玩法,本篇文章将主要围绕基于express的项目结构这个主题:
手写一个简单的express项目
1.首先,先把完整的目录结构构建好:
├── config (基础配置目录)
├── controllers (控制器,获取数据库数据或其他操作)
├── routes(路由)
├── views(视图)
├── static(静态资源目录)
├── app.js(入口)
├── package.json
└── package-lock.json
注:package.json直接通过npm init生成,因为我们的入口点是app.js,所以在package.json中将main.js改成app.js。
2.下载一些必要的npm依赖,目前我们先下载这几个:nodemon、express、body-parser、cookie-parser、pug
npm i --save express body-parser cookie-parser pug
npm i -D nodemon
(nodemon是一个解放手动重启的工具,我们只在开发依赖中安装它)
3.先在app.js中编写主要的代码:
const express = require('express')
const app = express()
let port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`)
})
4.config文件夹下分别编写我们的模板引擎配置文件、中间件配置文件、错误监听配置文件:
config/viewTemp.js
const path = require('path')
module.exports = app => {
app.set('views', path.join(__dirname, '../views'))
app.set('view engine', 'pug')
}
config/middleware.js
const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
module.exports = app => {
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())
app.use(cookieParser())
app.use('/static', express.static(path.join(__dirname, '../static')));
}
config/errorhandle.js
module.exports = app => {
app.use('*', (req, res) => {
res.status(404).send('the directory you request is not exist in the server')
})
app.use((err, req, res, next) => {
res.status(500).send('something went wrong')
})
}
5.编写controller脚本:
controller/home.js
module.exports = {
home: (req, res) => {
res.send('you are now in home page')
},
user: (req, res) => {
res.send('you are now in user page')
},
login: (req, res) => {
let query = req.query
if (query.username === 'yaodebian' && query.password === '123456') {
res.send('login successful')
} else {
res.send('wrong user name or password')
}
},
serverError: (req, res) => {
throw new Error('something went wrong')
}
}
manage.js
module.exports = {
home: (req, res) => {
res.send('you are now in manage page')
}
}
6.编写路由脚本:
routes/home.js
// controllers
const homeController = require('../controllers/home')
const express = require('express')
const router = express.Router()
router.get('/', homeController.home)
router.get('/home', homeController.home)
router.get('/user', homeController.user)
router.get('/login', homeController.login)
router.get('/500', homeController.serverError)
module.exports = router
routes/manage.js
// controllers
const manageController = require('../controllers/manage')
const express = require('express')
const router = express.Router()
router.get('/', manageController.home)
module.exports = router
routes/index.js
// routes
const homeRouter = require('./home')
const manageRouter = require('./manage')
module.exports = app => {
app.use('/', homeRouter) // 基于‘/’的请求会进入homeRouter中匹配并执行对应controller
app.use('/manage', manageRouter) // 基于‘/manage’的请求会进入manageRouter中匹配并执行对应controller
}
7.在app.js中引入各种配置和路由:
...
const app = express()
viewTemp(app) // 设置模板引擎
middleware(app) // 中间件初始化
route(app) // 路由配置
errorhandle(app) // 错误处理监听
let port = process.env.PORT || 3000
...
8.package.json中配置npm script:
...
"scripts": {
"start": "PORT=8080 nodemon app.js"
},
...
到此,我们已经编写了一个简单的express应用,通过访问不同的路由我们能看到不同的内容,也可以在static文件夹中放入一些静态资源文件,通过类似http://localhost:8080/static/imgs/pic.jpg
的方式访问。
express generator
express generator是express应用快速搭建脚手架,通过简单的命令输入即可在制定目录构建express应用。
首先全局下载express-generator: npm i -g express-generator
然后在制定目录下输入express
即可在该目录下快速构建一套express模板,具体目录结构如下:
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.jade
├── index.jade
└── layout.jade
基本结构其实和我们前面手写的差不多,但express generator生成的模板做了更多事。(有关express-generator的详细用法请点击这里:https://github.com/expressjs/generator)
1.在中间件上,通过morgan中间件监听http请求,并将相关信息在控制台中打印。(有关morgan中间件的详细用法,请点击这里:https://github.com/expressjs/morgan)
2.在请求路径出现404时,通过http-errors包装一个Error对象抛出以区分404错误和500错误。(有关http-errors的详细用法,请点击这里:https://github.com/jshttp/http-errors)
3.仔细看生成器生成模板中对端口的监听和我们手写express应用中对端口的监听,会有些不同,具体如下:
手写:
const app = express()
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`)
})
generator生成模板:
var server = http.createServer(app);
server.listen(port);
其实,这两种写法是一样的,app就是一个function (req, res) {}
结构的函数,它有一个这样的方法:
app.listen = function (port) {
http.createServer(this).listen(port);
}
所以app.listen和下面这两句是等价的:
var server = http.createServer(app);
server.listen(port);
4.generator设置了"error"、"listening"事件监听:
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;
// handle specific listen errors with friendly messages
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();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind); // 通过debug来打印调试日志
}
上面代码中,针对系统调用错误在端口监听和非端口监听上做了不同的处理,具体可以看上面的注释。
另外,onListening这个方法中用到了debug这个调试日志输入工具(https://www.npmjs.com/package/debug),从npm的周下载量可以看出,这个工具的使用是非常频繁的,要知道,大部分的npm包可能都在使用它。
debug的使用也比较简单,其核心是命名空间
,以下面代码简单介绍:
index.js:
var debug = require('debug')('http')
, http = require('http');
http.createServer(function(req, res){
debug(req.method + ' ' + req.url);
res.end('hello\n');
}).listen(3000, function(){
debug('listening');
});
上面的代码中,我们引入了debug(Function),并传入一个字符串http
,这个字符串就是命名空间,这是一个怎样的概念呢?
假如直接运行none index
或者nodemon index
,debug中的信息是不会以日志的形式打印出来,我们需要设置一个环境变量DEBUG
,即输入DEBUG=http node index
,这样日志才会被打印出来。或许到这里你可能能猜想到debug存在的意义了: 简单来讲,就是通过命名空间可以很好地控制不同模块调试日志的开启与关闭,让我们能够更好地管理与查看我们的日志信息。
如果你还看不懂,可以查阅这篇文章:https://www.coreycleary.me/using-the-debug-module-to-avoid-polluting-your-application-logs-with-logs-from-node-modules/ (国内文章通俗易懂的目前没找到,这篇虽然是英文,但讲的很清晰且易懂,英文看不太顺的同学可以借助翻译工具查阅,当然我也是一个英语渣。。。)
更多详细介绍还是移步前面贴的npm地址查阅文档。
express generator生成器是如何快速生成模板的?
express-generator就是一个nodejs脚手架,在github官方源码中,可以找到一个template
的文件夹,其中存放的就是所有可能被用到的模板,而通过不同的参数命令,它会将模板文件夹中对应的模板代码导出到制定的文件中,当所需的全部模板都生成后,整个应用就搭建好了。这就是它的工作原理,根据某些已存在的模板文件快速构建项目代码结构。
具体的脚本可以通过package.json的main属性查看,通过main属性,我们可以看到bin/express-cli.js
,这就是express-generator的脚本入口点,而整个项目的所有脚本都存放在这个入口点文件中。(有兴趣的同学可以通过这个地址:https://github.com/expressjs/generator/blob/master/bin/express-cli.js 查阅源码内容,核心代码为里面的createApplication函数)
本篇文章github讨论地址
https://github.com/yaodebian/blog/issues/22
下一期target
接下来的几期文章,我将通过express具体实现一个简单的 “即时通讯应用” (类似QQ),而在具体编码之前,我将进行项目预演:
last(最后)
非常感谢您能阅读完这篇文章,您的阅读是我不断前进的动力。对于上面所述,有什么新的观点或发现有什么错误,希望您能指出。
最后,附上个人常逛的社交平台:
知乎:https://www.zhihu.com/people/bi-an-yao-91/activities
csdn:https://blog.csdn.net/YaoDeBiAn
github: https://github.com/yaodebian我的邮箱:[email protected] or [email protected]
个人目前能力有限,并没有自主构建一个社区的能力,如有任何问题或想法与我沟通,请通过上述某个平台联系我,谢谢!!!