精读express系列——实战篇(1)

foreword(前言)

上一篇主要简单介绍了express的一些高阶玩法,本篇文章将主要围绕基于express的项目结构这个主题:

  • 梳理一个简单清晰的项目结构;
  • express generator 以及它所构建的项目结构;
  • express generator 构造器简单原理分析;

手写一个简单的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),而在具体编码之前,我将进行项目预演:

  • 梳理应用所需的技术难点并找到合适的解决方案;
  • 梳理项目业务模块;
  • 梳理项目业务流程;
  • 基本的UI设计;

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]

个人目前能力有限,并没有自主构建一个社区的能力,如有任何问题或想法与我沟通,请通过上述某个平台联系我,谢谢!!!

你可能感兴趣的:(前端系列周刊,node.js)