一、Koa简介
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
https://koa.bootcss.com/#
二、环境搭建
先安装koa框架
npm i koa -S
创建app.js文件,必须的hello,world
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
await next()
ctx.response.type = "text/html"
ctx.response.body = "<h1>Hello World</h1>"
})
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
三、中间件
Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。
app.use(async (ctx, next) => {
await next()
ctx.response.type = "text/html"
ctx.response.body = "<h1>Hello World</h1>"
})
ctx
ctx就是上下文(Context),Context 将 node 的 request 和 response 对象封装到单个对象中,还对 Koa 内部对一些常用的属性或者方法做了代理操作,使得我们可以直接通过 ctx 获取。比如,ctx.request.url 可以写成 ctx.url。除此之外,Koa 还约定了一个中间件的存储空间 ctx.state。通过 state 可以存储一些数据,比如用户数据,版本信息等。
next
这是一种级联的方式,当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。
// 按照官方示例
const Koa = require('koa')
const app = new Koa()
// 记录执行的时间
app.use(async (ctx, next) => {
let stime = new Date().getTime()
console.log('开始时间'+stime);
await next()
let etime = new Date().getTime()
ctx.response.type = 'text/html'
ctx.response.body = '<h1>Hello World</h1>'
console.log('结束时间'+etime);
console.log(`请求地址: ${ctx.path},响应时间:${etime - stime}ms`)
});
app.use(async (ctx, next) => {
console.log('中间件1 doSoming');
await next();
console.log('中间件1 end');
})
app.use(async (ctx, next) => {
console.log('中间件2 doSoming');
await next();
console.log('中间件2 end');
})
app.use(async (ctx, next) => {
console.log('中间件3 doSoming');
await next();
console.log('中间件3 end');
})
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
四、路由koa-router
如果我们不引入koa-router的话,我们只能通过中间件进行配置
app.use(async (ctx, next) => {
if (ctx.request.path === '/') {
ctx.response.body = '<h1>index page</h1>';
} else {
await next();
}
});
app.use(async (ctx, next) => {
if (ctx.request.path === '/home') {
ctx.response.body = '<h1>home page</h1>';
} else {
await next();
}
});
app.use(async (ctx, next) => {
if (ctx.request.path === '/404') {
ctx.response.body = '<h1>404 Not Found</h1>';
} else {
await next();
}
});
引入koa-router
npm i koa-router -S
安装完成后执行
const Koa = require('koa')
// 注意 require('koa-router') 返回的是函数:
const router = require('koa-router')()
const app = new Koa()
// 添加路由
router.get('/', async (ctx, next) => {
ctx.response.body = `<h1>index page</h1>`
})
router.get('/home', async (ctx, next) => {
ctx.response.body = '<h1>HOME page</h1>'
})
router.get('/404', async (ctx, next) => {
ctx.response.body = '<h1>404 Not Found</h1>'
})
// 调用路由中间件
app.use(router.routes())
app.listen(3000, ()=>{
console.log('server is running at http://localhost:3000')
})
koa-router支持get、post、put、delete等用法
此外还支持url命名、单路由多层嵌套中间件、路由嵌套、路由前缀、url参数等等
var router = new Router({
prefix: '/users'
});
router.get('/:category/:title', function (ctx, next) {
console.log(ctx.params);
// => { category: 'programming', title: 'how-to-node' }
});
五、视图Nunjucks
前期准备工作做完,我们下面要做的就是页面展示,视图渲染
我简单介绍一下koa-nunjucks-2这个模板引擎
Nunjucks语法简单介绍
{{ username }} // 变量
{{ foo | title }} // 过滤器
// if语句
{% if hungry %}
I am hungry
{% elif tired %}
I am tired
{% else %}
I am good!
{% endif %}
// for循环
<ul>
{% for item in items %}
<li>{{ item.title }}</li>
{% else %}
<li>This would display if the 'item' collection were empty</li>
{% endfor %}
</ul>
// 宏
{% macro field(name, value='', type='text') %}
<div class="field">
<input type="{{ type }}" name="{{ name }}"
value="{{ value | escape }}" />
</div>
{% endmacro %}
// 把 field 当作函数一样使用:
{{ field('user') }}
{{ field('pass', type='password') }}
更多语法内容请查阅官方文档
安装
npm i koa-nunjucks-2 -S
代码
const Koa = require('koa')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
const app = new Koa()
const router = require('./router')
app.use(nunjucks({
ext: 'html',
path: path.join(__dirname, 'views'),// 指定视图目录
nunjucksConfig: {
trimBlocks: true // 开启转义 防Xss
}
}));
app.use(bodyParser())
router(app)
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
六、静态资源
视图层展示之后我们需要处理静态资源
npm i koa-static -S
增加并指定 /public 目录为静态资源目录
const Koa = require('koa')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
// 引入 koa-static
const staticFiles = require('koa-static')
const app = new Koa()
const router = require('./router')
// 指定 public目录为静态资源目录,用来存放 js css images 等
app.use(staticFiles(path.resolve(__dirname, "./public")))
app.use(nunjucks({
ext: 'html',
path: path.join(__dirname, 'views'),
nunjucksConfig: {
trimBlocks: true
}
}));
app.use(bodyParser())
router(app)
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
七、解析JSON
我们数据一般都是JSON格式的,我们只需要设置把数据挂载在响应体 body 上,同时告诉客户端『返回的是 JSON 数据』,客户端就会按照 JSON 来解析了
ctx.set("Content-Type", "application/json")
ctx.body = JSON.stringify(json)
我们将这段代码提取为中间件,这样更方便代码的维护性和扩展性
创建js
module.exports = () => {
function render(json) {
this.set("Content-Type", "application/json")
this.body = JSON.stringify(json)
}
return async (ctx, next) => {
ctx.send = render.bind(ctx)
await next()
}
}
我们把 JSON 数据的处理方法挂载在 ctx 对象中,并起名为 send。当我们需要返回 JSON 数据给客户端时候,只需要调用此方法,并把 JSON 对象作为参数传入到方法中就行了,用法如下:
ctx.send({
status: 'success',
data: 'hello ikcmap'
})
引入中间件,修改app.js
const Koa = require('koa')
const app = new Koa()
const router = require('./router')
const middleware = require('./middleware')
middleware(app)
router(app)
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
八、记录日志
安装 log4js 模块
npm i log4js -S
日志配置
const log4js = require('log4js');
// 引入日志输出信息的封装文件
const access = require("./access.js");
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]
const baseInfo = {
appLogLevel: 'debug',
dir: 'logs',
env: 'dev',
projectName: 'koa2-tutorial',
serverIp: '0.0.0.0'
}
const { env, appLogLevel, dir, serverIp, projectName } = baseInfo
// 增加常量,用来存储公用的日志信息
const commonInfo = { projectName, serverIp }
module.exports = () => {
const contextLogger = {}
const appenders = {}
appenders.cheese = {
type: 'dateFile',
filename: `${dir}/task`,
pattern: '-yyyy-MM-dd.log',
alwaysIncludePattern: true
}
if (env === "dev" || env === "local" || env === "development") {
appenders.out = {
type: "console"
}
}
let config = {
appenders,
categories: {
default: {
appenders: Object.keys(appenders),
level: appLogLevel
}
}
}
const logger = log4js.getLogger('cheese');
return async (ctx, next) => {
const start = Date.now()
log4js.configure(config)
methods.forEach((method, i) => {
contextLogger[method] = (message) => {
// 将入参换为函数返回的字符串
logger[method](access(ctx, message, commonInfo))
}
})
ctx.log = contextLogger;
await next()
const responseTime = Date.now() - start;
logger.info(access(ctx, {
responseTime: `响应时间为${responseTime/1000}s`
}, commonInfo))
}
}
九、错误处理
当页面访问异常的时候进行错误页面展示
const Path = require('path')
const nunjucks = require('nunjucks')
module.exports = (opts = {}) => {
// 增加环境变量,用来传入到视图中,方便调试
const env = opts.env || process.env.NODE_ENV || 'development'
const folder = opts.errorPageFolder
const templatePath = Path.resolve(__dirname, './error.html')
let fileName = 'other'
return async (ctx, next) => {
try {
await next()
if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404);
} catch (e) {
let status = parseInt(e.status)
const message = e.message
if(status >= 400){
switch(status){
case 400:
case 404:
case 500:
fileName = status;
break;
default:
fileName = 'other'
}
}else{
status = 500
fileName = status
}
const filePath = folder ? Path.join(folder, `${fileName}.html`) : templatePath
// 渲染对应错误类型的视图,并传入参数对象
try{
nunjucks.configure( folder ? folder : __dirname )
const data = await nunjucks.render(filePath, {
env: env, // 指定当前环境参数
status: e.status || e.message, // 如果错误信息中没有 status,就显示为 message
error: e.message, // 错误信息
stack: e.stack // 错误的堆栈信息
})
// 赋值给响应体
ctx.status = status
ctx.body = data
}catch(e){
// 如果中间件存在错误异常,直接抛出信息,由其他中间件处理
ctx.throw(500, `错误页渲染失败:${e.message}`)
}
}
}
}
十、规范与部署
全局安装 nodemon:
npm i nodemon -g
本地项目中也需要安装:
npm i nodemon -S
更多细节用法,请查阅官方文档
部署运行
线上部署运行的话,方法也有很多,我们推荐使用 pm2。
pm2 是一个带有负载均衡功能的Node应用的进程管理器。
安装方法与 nodemon 相似,需要全局安装:
npm i pm2 -g
运行方法:
pm2 start app.js
更多细节用法,请查阅官方文档