使用Koa2+mongoDB起一个服务

166 阅读3分钟

mongoDB配置: juejin.cn/post/720881…

技术栈:mongodBD mongoose Koa2 axios

init

npm init

package.json

{

    "name": "xxx",

    "version": "1.0.0",

    "description": "",

    "main": "index.js",

    "scripts": {

        "start": "node bin/www",

        "dev": "./node_modules/.bin/nodemon bin/www",

        "prod": "pm2 start bin/www",

        "test": "echo \"Error: no test specified\" && exit 1"

    },

    "author": "",

    "license": "ISC",

    "dependencies": {

        "axios": "^0.21.0",

        "debug": "^4.1.1",

        "koa": "^2.7.0",

        "koa-body": "^4.2.0",

        "koa-bodyparser": "^4.2.1",

        "koa-convert": "^1.2.0",

        "koa-json": "^2.0.2",

        "koa-logger": "^3.2.0",

        "koa-multer": "^1.0.2",

        "koa-onerror": "^4.1.0",

        "koa-router": "^7.4.0",

        "koa-static": "^5.0.0",

        "koa-views": "^6.2.0",

        "koa2-cors": "^2.0.6",

        "koa2-request": "^1.0.4",

        "mongodb": "^3.6.3",

        "mongoose": "^5.5.11",

        "pug": "^2.0.3",

        "puppeteer": "^5.5.0"

    },

    "devDependencies": {

        "nodemon": "^1.19.1"

    }

}

通过npm i下载依赖

入口文件

在我们package.json中,启动服务需要运行bin/www文件。

     在这个文件中,需要做的事有:
     - 定义port端口号
     - 启一个http服务
     - 监听服务异常(error)和变化

具体代码如下:

  • 定义port端口号
function normalizePort(val) {

    var port = parseInt(val, 10);

    if (isNaN(port)) {

        // named pipe

        return val;

    }

if (port >= 0) {

    // port number

    return port;

}

    return false;

}

var port = normalizePort(process.env.PORT || '3000');
  • 启一个http服务,并监听

方式一,通过http.createServer起一个简单的服务。

var app = require('../app');
var http = require('http');
var server = http.createServer(app.callback());
server.listen(port);

server.on('error', onError);

server.on('listening', onListening);

方式二,通过koa的app.listen起一个服务。

var opn = require('opn');
var Koa = require('koa');
var app = new Koa();
// 端口占用重启 开发环境

let startServer = function (port) {

app.listen(port, () => {

    if (env === 'development') {

        opn(`http://localhost:${port}`, {app: ['google chrome']});

    }

}).on('error', (err) => {

    console.log('端口错误+' + port);

    if (env === 'development') {

        startServer(PORT + 1);

    }

});

}

startServer(3000)

上述两种方式底层都是通过createServer实现的,本质没有区别。

http.createServer做了什么? 待补充

中间件

启动服务后,需要用到koa对接口进行配置封装, 也就是上一步中,createServer的入参=>app.js文件。

     在这个文件中,需要做的事有:
     - 连接数据库
     - 处理错误
     - 解决跨域问题 / 配置响应头
     - 格式化response
     - 配置日志
     - 限制请求体大小
     - 注册路由

这里简单讲一下koa的洋葱模型: image.png

koa中自定义方法都是通过app.use(async (ctx, next)=>{})实现的,use中的方法是异步执行的,比如下面的代码:

为什么是这样? 待补充

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

app.use(async (ctx, next) => {
  console.log(1)
  await next;
  console.log(1)
})

app.use(async (ctx, next) => {
  console.log(2)
  await next;
  console.log(2)
})

app.use(async (ctx, next) => {
  console.log(3)
})

// 最后输出
1 2 3 2 1

  • 连接数据库 这里我们以mongodb为例,可以使用mongoose作为链接处理mongodb的工具。
//引入模块

const mongoose = require('mongoose')

const config = {

    mongodbUrl: 'mongodb://localhost:27017/didaima',
    
}

  


//连接数据库

mongoose.connect(`${config.mongodbUrl}`, {

    useNewUrlParser: true,

    useUnifiedTopology: true

})

//得到数据库连接句柄

const db = mongoose.connection

//通过数据库连接句柄,监听mongoose数据库成功的事件

db.on('open', function (err) {

    if (err) {

        console.log('数据库连接失败')

        throw err
    }

    console.log('数据库连接成功')

})
  • 处理错误 koa-onerror可以帮助我们于格式化异常情况的页面输出。
const onerror = require('koa-onerror')
onerror(app)
  • 解决跨域问题 / 配置响应头
const cors = require('koa2-cors')
app.use(
cors(
// {

// origin: "*",

// credentials: true,

// exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],

// allowMethods: ['GET', 'POST', 'DELETE'],

// allowHeaders: ['Content-Type', 'Authorization', 'Accept']

// }
)
)
  • 格式化response koa-json可以帮我们将请求response格式化成json
var json = require("koa-json")
app.use(json())
  • 配置日志 koa-logger可以在服务端输出请求日志
var logger = require("koa-logger")
app.use(logger())
  • 限制请求体大小 koa-body可以配置对请求体的限制
var koaBody = require("koa-body")
app.use(

    koaBody({

        formLimit: '15mb',

        jsonLimit: '15mb',

        textLimit: '15mb'

    })

)
  • 注册路由

这一步比较重要,我们的接口都将在这边实现。

const Router = require('koa-router')

const router = new Router()

const registerRouter = require('./routes')

app.use(registerRouter())

app.use(router.routes()) // 中间件中使用router

app.use(router.allowedMethods());

由于我们可能不止一个router文件,所以在这里构造一个统一的注册器,通过node读取router文件夹下的所有router文件,并依次注册。

routes.js

const compose = require('koa-compose')

const glob = require('glob')

const { resolve } = require('path')

  


registerRouter = () => {

    let routers = [];

    glob.sync(resolve(__dirname, './', '**/*.js'))

    .filter(value => (value.indexOf('index.js') === -1))

    .map(router => {

        routers.push(require(router).routes())

        routers.push(require(router).allowedMethods())

    })

    return compose(routers)

}

  
module.exports = registerRouter

koa-compose是将 koa/koa-router 各个中间件合并执行,也就是依次注册路由。

至此,一个可以链接mongodb数据库的koa中间件服务就起好了,试试npm run node 入口文件

mongoose

这里我们细说一下mongoose

mongoose 是 MongoDB 的一个对象模型工具,它对 MongoDB 的常用方法进行了封装,让 node.js 操作 MongoDB 更加优雅简洁。

数据库操作中无非就是CRUD(增删改查),mongoose通过connect方法连接数据库后,同样可以调用方法来控制CRUD。

增 - insert

虽然mongoDB不需要在创建文档的时候指定文档的数据结构,但是mongoose中推荐使用model去定义一下schema。

比如,我们要创建一个描述图书馆中的图书的文档:

/* 定义 user Schema */

const mongoose = require("mongoose");

const bookSchema = new mongoose.Schema({

    bookName: { type: String },
    currentCount: { type: Number }

});

  


// 创建Model

const PageMedel = mongoose.model("book", bookSchema, 'book');

module.exports = PageMedel

通过注册路由去操作增加(insert)数据:

model可以通过insertMany()去新增数据。

const router = new Router()
const Router = require('koa-router')
const BookModel = require('../models/book')

router.post('/addBook', async (ctx, next) => {

    let data = ctx.request.body

    let book = BookModel;

try {

    page = await page.insertMany([data])

    ctx.body = {

        message: '保存成功',

        data: page[0]._id,

        code: 0

    }

} catch (e) {

    ctx.body = { message: `insert失败, 失败原因:${e}`, code: 1 }

}

    await next()

})

完成上述工作,就可以调用/addBook接口去新增图书了~

删除可以使用model.remove

model.remove({ _id: data.id }, (err, d) => {

    if (err) {

        reject({ message: '删除失败', status: 10001 })

    } else {

        resolve({ message: '删除成功', status: 10000, id: data.id })

    }

})

moongoose中的修改和mongoDB中原生的修改方法类似,这里列举一个updateOne

{ _id: data.id }是过滤器的过滤条件。

$set是指部分属性更新,而不是覆盖。

model.updateOne({ _id: data.id }, {$set: data}, (err, d) => {

    if (err) {

        reject({ message: '编辑失败', status: 10001 })

    } else {

        resolve({ message: '编辑成功', status: 10000, id: data.id })

    }

})

可以使用以下方法做查询

  • model.find({})
  • model.findOne({ bookName: "js从入门到放弃" })
  • model.findById(id)
  • ...