Node.js中使用Koa创建Web服务器、编写接口

Koa(koa.js)中文网 -- 基于 Node.js 平台的下一代 web 开发框架koa (koajs)是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。https://www.koajs.com.cn/

Koa 是一种可以替代 Express 的Web框架。开发思路和 Express 差不多,最大的特点是可以避免异步嵌套,免除重复繁琐的回调函数嵌套。

1. 创建基本的Web服务器

# 在项目中下载koa包
npm i koa
//1、引入koa
const Koa=require('koa')
//2、实例化koa,创建Web服务器
const app=new Koa()

//第一个中间件
app.use(async(ctx,next)=>{
    await next()
    console.log('再打印第一个中间件')
    ctx.body='Hello world'
})
//第二个中间件
app.use(async(ctx)=>{
    console.log('先打印第二个中间件')
})

//3、启动Web服务器,调用app.listen(端口号,启动成功后的回调函数)
app.listen(3000)

ctx 上下文,包含了 request 和 response 等信息。

Koa与Express的对比:ctx.body用于返回数据相当于res.writeHead()   res.send() 

Koa用到ES6的语法:箭头函数 ()=>{} 中的this指向上下文。


2. 路由

路由是客户端的请求与服务器处理函数之间的映射关系。

(1)使用原生方法实现路由:不够简洁,优雅!

//1、导入koa
const Koa = require('koa')
//2、创建Web服务器
const app = new Koa()

//路由中间件
app.use(async (ctx) => {
    if (ctx.url === '/') {
        ctx.body = 'Home Page'
    } else if (ctx.url === '/users') {
        if (ctx.method === 'GET') {
            ctx.body = 'Users Page'
        } else if (ctx.method === 'POST') {
            ctx.body = 'Create New User'
        } else {
            ctx.status = 405 //Not allowed
        }
    } else if (ctx.url.match(/\/users\/\w+/)) { //用户id由字母数字组成
        if (ctx.method === 'GET') {
            // macth()返回的数组第一项为整个url,在正则表达式中给我们需要的信息加上小括号,数组第二项就为我们需要的信息
            const userId = ctx.url.match(/\/users\/(\w+)/)[1] //获取数组第二项的userId
            ctx.body = `用户id为${userId}`
        } else {
            ctx.status = 405 //Not allowed
        }
    } else {
        ctx.status = 404 //Not Found
    }
})

//3、启动Web服务器,调用app.listen(端口号,启动成功后的回调函数)
app.listen(3000)

(2)使用koa-router实现路由:

# 在项目中下载koa-router包
npm i koa-router
# 解析请求体的包
npm i koa-bodyparser
const Koa = require('koa')
const app = new Koa()
// 1、引入koa-router模块
const Router = require('koa-router')
// 2、实例化,创建路由
const router = new Router() //配置无路由前缀的路由
const usersRouter = new Router({ //配置路由前缀为users的路由
    prefix: '/users'
})

// 获取post提交的数据,解析请求体
const bodyparser = require('koa-bodyparser') // 引入koa-bodyparser
app.use(bodyparser())

// 安全中间件
const auth = async (ctx, next) => {
    if (ctx.url !== '/users') {
        ctx.throw(401) //Unauthorized
    }
    await next()
}

// 虚拟数据
const db = [{
    name: '小明',
    age: '13',
    sex: '男'
}, {
    name: '小红',
    age: '15',
    sex: '女'
}]

// 3.挂载路由
router.get('/', (ctx) => { // 首页
    ctx.body = '<h1>Home Page</h1>'
})
usersRouter.get('/', auth, (ctx) => { // 查列表
    ctx.set('Allow', 'GET,POST') // 设置响应头Headers
    ctx.body = db
})
usersRouter.get('/:id', (ctx) => { // 查一个
    if (ctx.params.id >= db.length) {
        ctx.throw(412, '先决条件错误,id大于数组长度')
    }
    ctx.body = db[ctx.params.id] // 假设id为数据索引
})
usersRouter.post('/', (ctx) => { // 增
    // 要新增的数据通过请求体传递过来
    db.push(ctx.request.body)
    ctx.body = ctx.request.body
})
usersRouter.put('/:id', (ctx) => { // 改
    // 要修改为的数据通过请求体传递过来
    db[ctx.params.id] = ctx.request.body
    ctx.body = ctx.request.body
})
usersRouter.delete('/:id', (ctx) => { // 删
    db.splice(ctx.params.id, 1) // 假设id为数据索引
    ctx.status = 204 // No content
})

// 4.注册路由中间件
// 启动路由
app.use(router.routes())
app.use(usersRouter.routes())
// 可设置可不设置,但是推荐设置。allowedMethods作用:
//(1)响应options请求,返回支持的请求方法
//(2)返回405表示不允许访问,返回501表示方法还没实现
app.use(usersRouter.allowedMethods())

app.listen(3000)

3. 传值方式

(1)GET 传值

koa中GET传值通过 request 接收,接收的方法有两种:query 和 querystring。

  • query:返回的是格式化好的参数对象。
  • querystring:返回的是请求字符串。

http://localhost:3000/newscontent?aid=12345&name=xiaoming

const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router(); 

router.get('/', function (ctx, next) {
    ctx.body="Hello koa";
})

router.get('/newscontent,(ctx,next)=>{
    let url =ctx.url; // 获取url地址

    //从 request 中获取 GET 请求
    let request =ctx.request;
    let req_query = request.query; // 获取一个对象
    let req_querystring = request.querystring;  // 获取一个字符串

    //从上下文中直接获取
    let ctx_query = ctx.query; // 【最常用!!!!】获取一个对象
    let ctx_querystring = ctx.querystring;  // 获取的是一个字符串
    ctx.body={
       url,
       req_query,
       req_querystring,
       ctx_query,
       ctx_querystring
    }
});

//作用:启动路由 
app.use(router.routes());
//作用:当请求出错时的处理逻辑  
app.use(router.allowedMethods()); 

app.listen(3000,()=>{
    console.log('starting at port 3000'); 
});

(2)动态路由

动态路由里面可以传入多个值。

http://localhost:3000/product/12345/678

//请求方式 
router.get('/product/:aid/:cid',async (ctx)=>{
    //获取动态路由的数据
    console.log(ctx.params); //{ aid: '12345' , cid:'678' } 
    ctx.body='这是商品页面'; 
});

(3)获取POST提交的数据

使用 koa-bodyparser 中间件获取表单 POST 提交的数据,并将数据转化为对象。 

ctx.request.body 即为 POST 提交过来的数据。

var Koa = require('koa');
var app = new Koa(); 

var bodyParser = require('koa-bodyparser'); 
app.use(bodyParser());

app.use(async(ctx) => { 
    ctx.body = ctx.request.body;
});

app.listen(3000,()=>{
    console.log('starting at port 3000'); 
});

4. 中间件

中间件就是匹配路由之前或者匹配路由完成做的一系列的操作。

Koa中间件执行流程和Express不同,采用了洋葱模型:初识洋葱模型,分析中间件执行过程,浅析koa中间件源码_Jioho_的博客-CSDN博客_中间件洋葱模型了解洋葱模型执行顺序分析部分 koa 中间件的源码来加深对中间件的认识https://blog.csdn.net/Jioho_chen/article/details/123167603

const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router(); 

const bodyparser = require('koa-bodyparser') // 解析请求体
// 4. 第三方中间件
app.use(bodyparser())

// 1. 应用级中间件,可以匹配任何路由
app.use(async(ctx,next)=>{
    console.log(new Date()); // 匹配路由之前打印日期
    await next(); // 当前路由匹配完成以后继续向下匹配
    // 如果不写next这个路由匹配完成后就不会继续向下匹配
})

// 2. 路由级中间件,匹配到news路由后,继续向下匹配路由
router.get('/news', async(ctx, next)=>{ 
    console.log(1);
    await next();
})

// 3. 错误处理中间件,利用了洋葱模型的特点
app.use(async (ctx,next)=> { 
    next(); //先继续执行下面的路由匹配

    if(ctx.status==404){ // 执行完下面的路由匹配之后回过头来又判断是否出现了错误
        ctx.status = 404; 
        ctx.body="这是一个 404 页面"
    } 
});

router.get('/', function (ctx, next) { ctx.body="Hello koa";}) 
router.get('/news',(ctx,next)=>{ctx.body="新闻页面" });

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log('starting at port 3000'); 
});

5. 路由模块化

路由模块化:将路由抽离为单独的模块,与控制器分离【推荐使用!】

入口文件index.js:

// index.js

const Koa = require('koa')
const app = new Koa()

const bodyparser = require('koa-bodyparser')
app.use(bodyparser()) 

const router=require('./routes/home.js')
const usersRouter=require('./routes/users.js')

app.use(router.routes())
app.use(usersRouter.routes())
app.use(usersRouter.allowedMethods())

app.listen(3000,()=>{
    console.log('koa server running at http://127.0.0.1:3000')
})

 路由放在routes文件夹中:

// /routes/home.js

const Router = require('koa-router')
const router = new Router()
const { index } = require('../controllers/home.js')

router.get('/', index)

module.exports = router
// /routes/users.js

const Router = require('koa-router')
const usersRouter = new Router({ //配置路由前缀为users的路由
    prefix: '/users'
})
const {getUserList,findUserById,createUser,updateUserById,deleteUserById}=require('../controllers/users.js')

usersRouter.get('/', getUserList)
usersRouter.get('/:id', findUserById)
usersRouter.post('/', createUser)
usersRouter.put('/:id', updateUserById)
usersRouter.delete('/:id', deleteUserById)

module.exports=usersRouter

控制器放在controllers文件夹中:

// /controllers/home.js

class HomeCtrl{
    index(ctx){
        ctx.body = '<h1>Home Page</h1>'
    }
}

module.exports=new HomeCtrl()
// /controllers/users.js

// 虚拟数据
const db = [{
    name: '小明',
    age: '13',
    sex: '男'
}, {
    name: '小红',
    age: '15',
    sex: '女'
}]

class UsersCtrl{
    getUserList(ctx){
        ctx.set('Allow', 'GET,POST') // 设置响应头Headers
        ctx.body = db
    }
    findUserById(ctx){
        if (ctx.params.id >= db.length) {
            ctx.throw(412, '先决条件错误,id大于数组长度')
        }
        ctx.body = db[ctx.params.id] // 假设id为数据索引
    }
    createUser(ctx){
        db.push(ctx.request.body)
        ctx.body = ctx.request.body
    }
    updateUserById(ctx){
        db[ctx.params.id] = ctx.request.body
        ctx.body = ctx.request.body
    }
    deleteUserById(ctx){
        db.splice(ctx.params.id, 1) // 假设id为数据索引
        ctx.status = 204 // No content
    }
}

module.exports=new UsersCtrl()

6. 异常处理

Koa自带异常处理,返回值为文本(text)。

1. HTTP状态码Status:

(1)请求成功:返回200。

(2)运行时错误:返回500。

(3)逻辑错误:无法找到,返回404。先决条件错误,返回412。参数格式错误,返回422。

2. 使用 koa-json-error 进行异常处理:

使用 koa-json-error 进行异常处理,返回值为json格式。

# 在项目中下载koa-json-error包
npm i koa-json-error

3. 使用 koa-parameter 校验参数是否正确:

使用 koa-parameter 校验参数是否正确,返回值为json格式。

# 在项目中下载koa-parameter包
npm i koa-parameter
// 入口文件 index.js

const Koa = require('koa')
const app = new Koa()
const bodyparser = require('koa-bodyparser')
app.use(bodyparser())

// 1.自己编写的异常处理中间件
// app.use(async (ctx, next) => {
//     try {
//         await next()
//     } catch (err) {
//         ctx.status = err.status || err.statusCode || 500
//         // 返回的错误信息最好是json格式
//         ctx.body = {
//             message: err.message
//         }
//     }
// })

// 2.使用koa-json-error中间件进行异常处理
const error = require('koa-json-error')
app.use(error({
    // development环境下返回错误栈stack,production环境下不返回错误栈stack
    postFormat: (e, {stack,...rest}) => process.env.NODE_ENV === 'production' ? rest : {stack,...rest}
}))

// 3.使用koa-parameter中间件校验参数格式是否正确
const parameter = require('koa-parameter')
app.use(parameter(app)) // app参数使koa-parameter可以全局使用

const router = require('./routes/home.js')
const usersRouter = require('./routes/users.js')

app.use(router.routes())
app.use(usersRouter.routes())
app.use(usersRouter.allowedMethods())

app.listen(3000, () => {
    console.log('koa server running at http://127.0.0.1:3000')
})

koa-parameter可以全局使用:

// /controllers/users.js 中部分关键代码
// koa-parameter的全局使用方法

    createUser(ctx) {
        // 参数校验
        ctx.verifyParams({
            name: {
                type: 'string',
                required: true
            },
            age: {
                type: 'number',
                required: false
            }
        })
        db.push(ctx.request.body)
        ctx.body = ctx.request.body
    }

注意:若要使用process.env.NODE_ENV判断当前程序运行环境是开发环境还是生产环境,可以在package.json文件中进行配置。

模拟node.js项目在生产环境中运行:

npm start

 模拟node.js项目在开发环境中运行:

npm run dev

7. 快速创建Koa2工程

(1)全局安装 koa-generator

sudo npm i -g koa-generator

(2)创建koa项目:ejs版本

koa2 -e koa-proj

(3)打开koa项目文件夹后,安装如下包

cd koa-proj
# 初始化项目
npm i
# 设置环境变量的包
npm i cross-env -D
# 使用mongodb数据库
npm i mongoose
# 使用redis
npm i redis
npm i koa-redis
# 使用session
npm i koa-generic-session
# 使用 koa-parameter 校验参数是否正确
npm i koa-parameter
# 测试
npm install --save-dev supertest
npm install --save-dev jest@24.9.0

jest参数:runInBand为顺序执行,forceExit执行完后退出,colors有颜色的输出 

(4)运行项目

npm run dev

(5)访问项目

localhost:3000

(6)git

# 还是在koa-proj文件夹中
git init
git remote add origin 远程git仓库地址
git status
git log
git pull origin master
git status
git add .
git commit -m "init project"
git push origin master

        之后每次提交代码:

git status
# git diff
git add .
git commit -m "备注信息"
git push origin master

(7)inspect测试

配置:在package.json文件的dev中加上--inspect=9229

在谷歌浏览器地址栏中输入chrome://inspect

加断点:在需要加断点的代码处写上debugger 

(8)jest单元测试

单元测试:单个功能或接口,给定输入,得到输出。看输出是否符合要求。需要手动编写用例代码,然后统一执行,验证所有功能是否正常。

注意:jest单元测试需要运行后缀为.test.js的文件。

编写测试用例demo.test.js:

/**
 * demo.test.js
 * @description test demo
 */
function sum(a, b) {
    return a + b
}

// 参数一:描述,参数二:测试用例
test('10+20应该等于30', () => {
    const res = sum(10, 20)
    expect(res).toBe(30)  //期望结果是30
    //expect(res).not.toBe(40)    //期望结果不等于40
})

jest连接服务器server.js:

/**
 * server.js
 * @description jest server
 */

 const request = require('supertest')
 const server = require('../app').callback()
 
 module.exports = request(server)

测试接口,编写测试用例json.test.js:

/**
 * json.test.js
 * @description json test
 */

const server=require('./server')

test('json接口返回数据格式正确',async()=>{
    const res=await server.get('/json')
    expect(res.body).toEqual({
        title:'koa2 json'
    })
})

运行单元测试:

npm run test

【koa2】ReferenceError: TextEncoder is not defined_顾鸟的博客-CSDN博客启动项目报错 https://blog.csdn.net/qq_40881695/article/details/124143927 

node—使用中间件jest和supertest进行对api接口的测试_酒未醒~的博客-CSDN博客_jest 接口测试使用jest和supertest进行对api接口的测试要使用koa脚手架创建项目,koa-generator中间件,我自己创建的项目程序入口是app.js,而脚手架创建的是在bin/www**安装:**npm install -g koa-generator命令:koa2+项目名生成项目自己创建项目时进行测试时出现了这两个报错,使用脚手架创建的项目就没有出错1、在package.json中添加test,需要使用到中间件cross-env,动态配置开发,测试,生产三种对应域名及其及打https://blog.csdn.net/qq_43778787/article/details/107100414

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值