# 前置知识
koa 基于 express 制作,而 exporess 的基础是 connect
# connect
最简单的 Connect 程序应该是这样的:
const app = require("connect")();
app.use((req, res, next) => {
  3;
  res.end("Hello, world!");
});
app.listen(3000);
# 工作机制
connect 中间件就是 js 函数。这个函数一般会有 3 个参数:请求对象、响应对象、next 回调函数。一个中间件完成自己的工作,要执行后续的中间件时,可以调用这个回调函数。
# 组合中间件
connect 中的 use 方法就是用来组合中间件的,use()函数返回的是 connect 程序的实例,支持方法链。use 的调用顺序很重要。如果某个中间件不调用 next(),那链在它后面的中间件就不会被调用。
# 可配置中间件
为了做到可配置,中间件一般会遵循一个简单的惯例:用一个函数返回另一个函数(闭包):
function setup(options) {
  // 设置逻辑,这里做中间件的初始化
  return function (req, res, next) {
    // 中间件逻辑,即使被外部函数返回了,仍然可以访问options
  };
}
这种中间件的用法如下:
app.use(setup({ some: "options" }));
# 错误处理中间件
- connect 的默认错误处理器
 
因为函数 foo()没有定义,所以下面这个中间件会抛出错误 ReferenceError:
const connect = require("connect");
connect()
  .use((req, res) => {
    foo();
    res.setHeader("Content-Type", "text/plain");
    res.end("hello world");
  })
  .listen(3000);
Connect 默认的处理是返回响应状态码 500,响应主体是文本 Internal Server Error 和错误的详 细信息。
2. 自行处理程序错误
错误处理中间件函数必须有 4 个参数:err、req、res 和 next
const env = process.env.NODE_ENV || "development";
function errorHandler(err, req, res, next) {
  res.statusCode = 500;
  switch (env) {
    case "development":
      console.error("Error:");
      console.error(err);
      res.setHeader("Content-Type", "application/json");
      res.end(JSON.stringify(err));
      break;
    default:
      res.end("Server error");
  }
}
module.exports = errorHandler;
当 connect 遇到错误时,它会切换,只去调用错误处理中间件,connect 通过参数数量来区分是普通中间件还是错误处理中间件 
# Express
极简的 express 程序:
const express = require("exporess");
const app = exporess();
app.get("/", (req, res) => {
  res.send("hello");
});
app.listen(3000);
通过express-generator可以自动生成程序模版
# 变更 css 支持
npx express-generator -c css|sass|less
# 添加对 ejs 的支持
npx express-generator -e
# 配置环境变量
Express 有一个极简的环境驱动配置系统,这个系统由几个方法组成,全部由环境变量 NODE_ENV 驱动:
app.set();
app.get();
app.enable(); // 设置boolean值时,使用
app.disable();
app.enabled(); // 检测环境变量
app.disabled();
# 渲染视图
Express 中有两种渲染视图的办法:程序层面用 app.render(),在请求或响应层面用 res.render(),Express 内部用的是前一种。
# 传递参数
按照优先级排序:
- res.render()的参数
 - res.locals 传递请求层面的数据
 - app.locals 传递程序层面的数据
 
默认情况下 express 只会向视图中传递一个程序集变量settings,这个对象中包含所有用app.set()设定的值。实际上 express 是像下面这样输出这个对象的:app.locals.settings=app.settings
# 路由
可以使用中间件
# Koa2
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
http pro + 中间件 = koa // koa相当于http模块的增强版加上中间件功能
# 学习顺序
基础:
- 官网api (opens new window) 先看一遍:了解一下koa都有什么。
 - 阮一峰博客 (opens new window):了解一下开发流程和koa生态
 
进阶:
- Koa教程_Koa+Nodejs+MongoDb打造企业级CMS前后端全栈实战教程(大地-赞助)
 
# koa中间价
中间件的功能包括:
- 执行任何代码
 - 修改请求和响应对象
 - 终结请求-响应循环
 - 调用堆栈中的下一个中间件
 
中间件分为:应用级中间件和路由级中间件
# koa 中间件的执行顺序
koa 的中间件和 express 不同,koa 选择了洋葱圈模型。 
 中间件的顺序很重要,也就是调用app.use()的顺序决定了 middleware 的顺序。
# koa 实例
# app.listen
app.listen 方法只是以下方法的语法糖:
const http=require('http);
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
# app.use(function)
将给定的中间件方法添加到此应用程序。app.use()返回 this,因此可以链式表达
# app.context
app.context 是从其创建 ctx 的原型。您可以通过编辑app.context为 ctx 添加其他属性:
app.context.db = db();
app.use(async (ctx) => {
  console.log(ctx.db);
});
ctx 上的许多属性都是使用 getter,setter 和object.defineProperty()定义的。你只能通过在app.context上使用Object.defineProperty()来编辑这些属性(不推荐)
# 上下文(Context)
# ctx.req
Node 的 request 对象
# ctx.res
Node 的 response 对象
# ctx.request
koa 的 Request 对象
# ctx.response
koa 的 Response 对象
# ctx.state
推荐的命名空间,用于通过中间件船队信息和你的前端视图。
# ctx.app
应用程序实例引用
- ctx.app.emit: koa 应用扩展了内部的 Eventemitter。
 
# ctx.cookies
内部使用了cookies模块
# ctx.throw([status], [msg], [properties])
用来抛出一个包含 .status 属性错误的帮助方法,其默认值为 500。这样 Koa 就可以做出适当地响应。
koa 使用 http-errors 来创建错误。status 只应作为第一个参数传递。
# Request 别名
- ctx.header
 - ctx.headers
 - ctx.method
 - ctx.method=
 - ctx.url
 - ctx.url=
 - ctx.originalUrl
 - ctx.origin
 - ctx.href
 - ctx.path
 - ctx.path=
 - ctx.query
 - ctx.query=
 - ctx.querystring
 - ctx.querystring=
 - ctx.host
 - ctx.hostname
 - ctx.fresh
 - ctx.stale
 - ctx.socket
 - ctx.protocol
 - ctx.secure
 - ctx.ip
 - ctx.ips
 - ctx.subdomains
 - ctx.is()
 - ctx.accepts()
 - ctx.acceptsEncodings()
 - ctx.acceptsCharsets()
 - ctx.acceptsLanguages()
 - ctx.get()
 
# Response 别名
- ctx.body
 - ctx.body=
 - ctx.status
 - ctx.status=
 - ctx.message
 - ctx.message=
 - ctx.length=
 - ctx.length
 - ctx.type=
 - ctx.type
 - ctx.headerSent
 - ctx.redirect()
 - ctx.attachment()
 - ctx.set()
 - ctx.append()
 - ctx.remove()
 - ctx.lastModified=
 - ctx.etag=
 
# 响应(Response)
# response.status
获取响应状态,默认 404,而不是像 node 的 res.statusCode 那样默认 200
# 错误处理
默认情况下,所有错误输出到 stderr,除非 app.silent 为 true,当 err.status 是 404 或 err.expose 是 true 时,默认错误处理程序也不会输出错误。要执行自定义错误处理逻辑,可以添加一个 error 事件监听器:
app.on("error", (err) => {
  log.error("server error", err);
});
使用ctx.throw()外抛错误
# koa-router
koa本身是把router剥离出去了,所以需要引用koa-router包,使用方式:
const Koa=require('koa')
const app=new Koa()
const router=require('koa-router')();
router.get('/hello/:name',async (ctx,next)=>{
    console.log(ctx.params.name)
    var name=ctx.params.name;
    ctx.response.body=`<h1>Hello,${name}!</h1>`
})
router.get('/',async (ctx,next)=>{
    ctx.response.body='<h1>Index</h1>';
})
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);
console.log('app started at port 3000.....')
router.allowedMethods()的作用是什么呢?
allowedMethods处理的业务是当所有路由中间件执行完成之后,若ctx.status为空或者404的时候,丰富response对象的header头
参考:https://www.jianshu.com/p/fef91266a44c
注意:'/news'与'/news/'匹配的是不同的路由,需要单独匹配
# 处理post
无论是node还是koa都没有提供解析request的body的功能。所以,我们需要引入koa-body来解析原始request请求。
注意:koa-bodyparser应该在routes()之前被use
const Koa=require('koa');
const app=new Koa();
const router=require('koa-router')()
const bodyParser=require('koa-body');
router.get('/',async (ctx)=>{
  ctx.body=`
  <form action="/signin" method="post">
    <p><label>用户名:</label><input type="text" name="name" ></p>
    <p><label>密码:</label><input type="password" name="password"></p>
    <button type="submit">submit</button>
  </form>
  `  
})
router.post('/signin',async (ctx)=>{
    const {name,password}=ctx.request.body
    if(name==='admin'&&password==='123456'){
        ctx.body='Welcome Admin!'
    }else{
        ctx.body=`
        <h1>Login failed</h1>
        <p><a href="/">Try again</a></p>
        `
    }
})
app.use(bodyParser())
app.use(router.routes())
app.listen(3000)
console.log('app started at port http://localhost:3000')
# 重定向(redirect)
router.post("/doAdd", async (ctx) => {
  ctx.redirect('/')
});
# 嵌套路由
首先定义好子路由结构:
router.get('/test',(ctx)=>{
  ctx.body="hello,test"
})
module.exports=router.routers()
然后定义父节点路由:
const user=require('./routes/api/user')
router.use('/api/user',user)
# 简写
const router=require('koa-router')()  // 实例化简写(推荐)
# koa-static
对于静态资源如果要一个个去写路由就很麻烦,也没必要。直接使用koa-static
const path = require('path');
const serve = require('koa-static');
app.use(serve('/static')); 
app.use(serve('/public')); // 静态资源中间件可以设置多个,依次查找
首先去定义的文件夹查找,如果没有文件则在内部执行next()
# koa-session
session配置文件:
const session = require("koa-session");
const CONFIG={
  key:'koa.sess', // cookie key
  maxAge:86400000, // default is 1days
  overwrite:true,
  httpOnly:true,
  secure:true,
  sameSite:null, 
  autoCommit:true, // 自动提交headers
  signed:true, // 签名默认为true
  rolling:false, // 在每次请求时强行设置cookie,这将充值cookie过期时间(默认:false)
  renew:false, // 当session快过期时,重新设置session
}
app.use(session(CONFIG, app));
# koa-compose
合成中间件
const compose = require('koa-compose');
const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  next();
}
const main = ctx => {
  ctx.response.body = 'Hello World';
};
const middlewares = compose([logger, main]);
app.use(middlewares);
# koa-cors
跨域模块:
const cors = require('koa-cors')
// 处理跨域,放到中间件的最前面
app.use(cors());
# koa-views
koa页面模版
# 工程化
# 目录结构
server
  controllers // 操作层 执行服务端渲染,json接口返回数据,页面跳转
  models // 数据模型层 执行数据操作
  routers // 路由层 控制路由
  services // 业务层 实现数据层model到操作层controller的耦合封装
  views // 服务端模版代码
# 自动生成项目
使用koa-generator