从原生Node.js到Koa:Web服务的逆袭
2025-01-13 08:26 阅读(105)

今天让我们来认识一个由Express背后的团队开发且非常轻量级的node.js框架 koa,再聊它之前我们先来看看原生的node开发。


原生node开发

我们先用原生node启动一个web服务;

const http = require('http')

http.createServer((req, res) => {  //req(请求体)和res(响应体)
  console.log(req.headers)
}).listen(3000, () => {
  console.log('server is running');
})

其中req是请求体,res响应体:


请求体(Request Body) :客户端发送给服务器的数据。在HTTP请求中,请求体可以包含客户端想要提交给服务器的信息,例如表单数据、JSON数据等。请求体通常用于GET请求或POST请求。

响应体(Response Body) :服务器发送给客户端的数据。在HTTP响应中,响应体包含了服务器的实际响应内容,例如HTML页面、JSON数据、图片等。


然后使用HTTP模块创建的基本服务器,且在接收到请求后打印出请求头的信息,并在本地主机的3000端口上监听连接。然后这里我们用postman当作前端朝这个端口发送一个GET请求。

然后看到控制台打印请求体中的请求头的信息。

然后我们可以使用res.end(xxx)将数据发送给前端。

const http = require('http')

http.createServer((req, res) => {  
  res.end('后端返回数据 hello world')
}).listen(3000, () => {
  console.log('server is running');
})

这时再用postman朝地址发get请求,就可以拿到后端传过来的数据了,可以看到响应体中的数据如下。

浏览器是先拿到响应头,响应头中记录了响应时间,数据长度等信息。Content-Length指示响应体的长度,客户端可以根据该值确定数据传输是否已经完成,这也是响应头存在的意义,响应头还包含了其他重要信息,如Content-Type(指示数据的MIME类型)、Cache-Control(控制缓存行为)、Set-Cookie(设置cookies)等。

但如果想要请求'/home'路径下的数据呢?这就需要来个if判断请求体中的路径了。

const http = require('http')

http.createServer((req, res) => { 
  if (req.url === '/home') {
    res.end('首页的数据')
  }
}).listen(3000, () => {
  console.log('server is running');
})

这样只有通过/home路径才可以拿到相应的数据,后端就是这样来制作不同的接口地址。

如果一个项目有很多个接口的话,就需要一直if else,这会导致代码难以维护和理解,就可以使用Koa这样的现代Node.js框架可以更好地组织和维护具有大量接口的项目。Koa提供了中间件机制,可以以更优雅的方式处理请求,避免了大量的if else语句。

koa

基本语法

koa并不是node自带的模块,所以需要npm来安装这个第三方库。

初始化项目,安装koa

npm init -y
npm install koa

就可以看到koa源码:

然后将其引入到项目中使用

const Koa = require('koa')
const app = new Koa()   // 创建 koa 实例
const Koa = require('koa')
const app = new Koa()

const main = (ctx) => {   // ctx 包含了 request 和 response 两个对象
  console.log(ctx.url);  // 打印请求的 url
  ctx.body = 'hello world'  // 向前端返回的数据
}

app.use(main)

app.listen(3000, () => {
  console.log('服务器启动成功');
})

main 是一个中间件函数。在 Koa 中,中间件是一个函数,它接收一个 ctx(上下文)对象作为参数。ctx 对象包含了请求(request)和响应(response)的信息。想要向前端返回的数据就可以写在ctx.body里面。前端成功接收到数据。

当想传送的数据为<h2>Hello world</h2>,浏览器会自动将其当成html标签使用,如果就想将它当成字符串输出,就需要在响应头里面写上type属性里的内容,ctx.response.type 设置响应类型,让浏览器知道该以什么方式解析响应体,如下。

const main = (ctx) => {
  ctx.response.type = 'text'
  ctx.body = '<h2>Hello world</h2>'
}

如果想要访问/home路径下时将一个html页面输出;

const Koa = require('koa')
const fs = require('fs')  // 引入 fs

const app = new Koa()

const main = (ctx) => {
  if (ctx.url === '/home') {
    ctx.response.type = 'html'
    ctx.body = fs.readFileSync('./assets/template.html')    // 读取html文件地址
    // ctx.body = fs.createReadStream('./assets/template.html')    // 十六进制,浏览器可直接将其解析成html
  }
}

app.use(main)

app.listen(3000, () => {
  console.log('服务器启动成功');
})

定义接口

定义接口需要用到路由,然后在里面定义不同的路由来处理不同的接口,每个路由都关联一个HTTP方法和一个路径。


安装路由


npm i @koa-router

引入使用

const Router = require('@koa/router')
const router = new Router()
app.use(router.routes())  // 让路由生效

然后写一个首页的接口

const Koa = require('koa')
const app = new Koa()
const Router = require('@koa/router')
const router = new Router()

// 首页的页面
router.get('/home', (ctx, next) => {
  ctx.body = {
    code: 200,
    msg: 'success',
    data: {
      name: '张三',
      age: 20,
    }
  }
})

// 生效路由
app.use(router.routes())

app.listen(3000, () => {
  console.log('服务启动成功')
})

回调函数里面的(ctx, next)参数是router.routes()里面的。


这时候前端访问/home接口。

当要请求需要传参数时,可以在 url 后面拼接?id=123,然后响应体通过ctx.query拿到传过来的参数。传多个值时可以使用&拼接。


const Koa = require('koa')
const app = new Koa()
const Router = require('@koa/router')
const router = new Router()
// const { bodyParser } = require('@koa/bodyparser')

// app.use(bodyParser())  // 解析请求体

// 首页的接口
router.get('/home', (ctx, next) => {
  console.log(ctx.query);      // { id: '123' }
  const { id } = ctx.query      // 将传过来的对象解构出来

  ctx.body = {
    code: 200,
    msg: 'success',
    data: {
      name: '张三',
      age: 20,
      id     // 显示
    }
  }
})

// 生效路由
app.use(router.routes())

app.listen(3000, () => {
  console.log('服务启动成功')
})

通过postman传值,如下;

再写一个登录接口,登录接口就要用到post了,但是 koa 的路由无法直接解析 post 传递的参数,需要使用 @koa/bodeparser 中间件来解析 post 请求的参数

使用npm i @koa/bodyparser安装。作用就是辅助koa解析post请求。


const Koa = require('koa')
const app = new Koa()
const Router = require('@koa/router')
const router = new Router()

const { bodyParser } = require('@koa/bodyparser')
app.use(bodyParser())  // 解析请求体

// 登录页面的接口
router.post('/login', (ctx) => {
  console.log(ctx.request.body);   // { username: 'admin', password: 123 }

  // 获取请求体中的用户名和密码
  const { username, password } = ctx.request.body;

  if (username === 'admin' && password === '123') {
    ctx.body = {
      code: 200,
      msg: '登录成功',
      user: ctx.request.body.username
    }
  } else {
    ctx.body = {
      code: 401,
      msg: '账号或密码错误'
    }
  }
})

// 生效路由
app.use(router.routes())

app.listen(3000, () => {
  console.log('服务启动成功')
})

post请求用ctx.request.body传入,而前面的get请求是通过?xxx传入。然后判断账号密码返回响应体里面的信息。然后我们通过postman传入账号密码比较。


后端拿到了前端postman传过来的参数。

洋葱模型

聊洋葱模型就是中间件的执行顺序的比喻。


koa里面的中间件就是一个函数,就是koa在执行过程中执行的函数,它接收两个参数:ctx(上下文对象)和 next(一个函数,用于调用下一个中间件)。中间件用于处理 HTTP 请求和响应。


看如下代码:


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

const one = (ctx, next) => {
  console.log(1);
  next() // 直接调用下一个中间件
  console.log(2);
}
const two = (ctx, next) => {
  console.log(3);
  next()
  console.log(4);
}
const three = (ctx, next) => {
  console.log(5);
  next()
  console.log(6);
}

app.use(one)
app.use(two)
app.use(three)

app.listen(3000)

第一个中间件one执行到next()的时候就会直接调用下一个中间件two,当执行到最后一个中间件时,next()发现没有中间件就直接执行下一行,执行完后再往上一个中间件执行,就相当于一个递归,输出结果如下。

如果没有next()就只会执行第一个中间件,next()的存在就是为了可以人为地控制下一个中间件的执行,从而允许请求继续在中间件链中流动。

好了,关于koa就介绍到这里了,如果你觉得Koa这个小巧玲珑的框架让你心动不已,那就给它点个赞吧!毕竟,它可是个不折不扣的Web开发小能手,帮你轻松应对各种请求,让你的服务器跑得又快又稳。


https://juejin.cn/post/7458630988761284617