手把手教你入门koa,让你成为crud工程师的第一步
前言:之前有捣鼓过nodejs的koa框架,学会怎么搭个服务然后写一些简单的接口,现在写下文章分享下。
开始
搭建最简单的服务
我们先创建一个文件夹然后cmd进去(npm init
)初始化,然后安装最基本的服务npm i koa
和npm i @koa/router
,并在当前文件夹创建一个utils文件夹和app.js文件,app.js是启动服务的主文件。在utils文件夹新建一个config.js文件,这里用来写以后用到的全局配置。
当前目录:
/*config.js*/
//全局配置文件
const config = {
baseUrl: '',//配置的域名
port: 3001,//api访问的端口
tokenSecret: "CXH",//token的加密
wsPort: 3100,//WebSocket的端口号
//数据库相关的配置
db: {
database: "test",//数据库名称
username: 'root',//mysql用户名
password: 'root',//mysql密码
port: '3306',//mysql端口号
host: 'localhost',//服务器ip
},
}
//导出一整个模块
module.exports = config
const Koa = require('koa')
const app = new Koa()
const Router = require("@koa/router")
const router = new Router()//引入路由,并实例化路由
const config =require('./utils/config')
//示例
router.get('/',ctx=>{
ctx.response.body='这是示例'
})
app.use(router.routes())//app.use([地址],中间件|路由|函数体),安装中间件、路由、接受一个函数
//监听端口号
app.listen(config.port,()=>{
console.log(`listening on ${config.port}`);
})
然后可以在控制台运行node app.js
或nodemon app.js
笔者建议用第二个,nodemon 是一种工具,可在检测到目录中的文件更改时通过自动重新启动节点应用程序来帮助开发基于 node.js 的应用程序。简单来说就是热更新,文件有变更就会重新启动。安装方法:npm i nodemon -g
。
到了这一步,我们成功搭建了一个最简单的koa服务,做人要贪心,尤其知识方面,我们不能满足于此,让我们继续往下走。
模型(Model)
接下来我们来按照MVC的设计模式,来设计Model层(比如数据库记录列表),我们要先安装这两个mysql2
和sequelize
,
npm i mysql2 sequelize
mysql2是专注于性能的 Node.js MySQL 客户端。支持准备好的语句、非 utf8 编码、二进制日志协议、压缩、ssl等等。
Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。
先在根目录创建一个server文件夹,然后创建database.js文件用来创建mysql连接
const {Sequelize} = require("sequelize")
const config = require("../utils/config")
module.exports = db = new Sequelize(
config.db.database,
config.db.username,
config.db.password,
{
host: config.db.host,
dialect: "mysql",
timezone: "+08:00",
dialectOptions: {
dateStrings: true,
typeCast: true,
},
define: {
freezeTableName: true, //Model名与表名相同
},
logging: false,
}
)
async function sync() {
console.log("正在同步模型")
const [err, res] = await to(db.sync({ alter: true }))
if (err) {
console.log(err)
}
const association = require("./association")
association()
console.log("同步模型完成")
}
sync()
这里需要再新建一个文件association.js
,用于读取后面model文件夹里面的文件,并整合起来暴露出去
const fs = require('fs');
const lib = fs.readdirSync(__dirname + '/model');
console.log(lib)
var models = {}
for (let item of lib) {
let model = item.replace('.js', '')
models[model] = require('./model/' + item)
}
const association = () => {}
module.exports = association
根据上面的association.js
的作用,我们可以在utils文件夹里面新建一个index.js
文件用来整合以后utils文件夹的文件,统一暴露。我们顺便新增一个插件npm i await-to-js
/* index.js */
//全局方法入口
const fs = require('fs');
const utils = fs.readdirSync(__dirname);
console.log(utils)
utils.splice(utils.indexOf('index.js'),1)
const importGlobal = ()=>{
for(let item of utils){
let libName = item.replace('.js','')
global[libName] = require('./'+item)
}
}
module.exports = importGlobal
然后修改app.js
里面的内容
//----const config =require('./utils/config') 删除这一行代码
//++++增加下面代码
//引入全局配置
const importGlobal = require("./utils")
importGlobal()
/*to.js*/
const {default:to} = require("await-to-js")
module.exports = to
/*database.js*/
//补充
const to = require('../utils/to')
在index.js
里面的语句打印出来可以看出已经统一整合。然后我们继续弄数据库。
这里我用Navicat工具创建一个数据库和一张表,这里的数据库账号密码,数据库名和表名都是用户自己的,不全是我代码配置的。
接下来我们在model文件夹创建一个文件,Show.js
,用来描述我当前所用的这张表的属性。
const {DataTypes} = require("sequelize")
const db = require("../database")
const Show = db.define(
"testdata",//数据库的表名
{
//设置表的属性
id:{
type:DataTypes.INTEGER,
primaryKey:true,
notNull:true,
autoIncrement:true
},
name:DataTypes.STRING,
phone:DataTypes.INTEGER
},
{
timestamps: true,
createdAt: "createTime",
updatedAt: "updateTime",
}
)
module.exports = Show
接下来我们就要测试数据库是不是连通了,运行下项目
可以看到模型同步成功,说明连接成功了。然后我们先尝试一个简单查询接口
我们在app.js
文件加上这一串代码,然后保存,热更新后我们在网页直接访问localhost:3001/list
//示例
router.get('/list',async ctx=>{
// 查询所有用户
const users = await showModel.findAll();
console.log(users)
ctx.body=users
})
控制器(Controller)
ok,我们又离成功进一步了,虽然我们成功写了接口,但是在正式项目中我们不能在app.js里面这样写接口,会十分臃肿,MVC模式,那么下面就是controller控制器了。我们把所有接口统一在一个地方。
我们先在server文件夹新建一个controller文件夹,用来存储之后的controller,我们在该文件夹创建一个show.js
文件
/*show.js*/
const Router = require("@koa/router")
const {
default: to
} = require("await-to-js")
const router = new Router()
const showModel = require("../model/Show")
//示例,有关这个表的操作或相关操作都写在这个文件下
router.get('/list',async ctx=>{
// 查询所有用户
const users = await to(showModel.findAll());
console.log(users)
ctx.body=users
})
module.exports = router.routes()
既然我们controller都模块化了,那么我们路由也要进行模块化,我们在server文件夹下创建一个router.js
const Router = require("@koa/router")//引入koa-router
const router = new Router()
//引入路由,以后每有一个controller就加多一个路由
router.use("/show",require("./controller/show"))
module.exports=router.routes()
接下来修改app.js
文件的配置
//删除下面
//const Router = require("@koa/router")
//const router = new Router()
//app.use(router.routes())
//新增为下面的
// koa路由
const router = require("./server/router")
app.use(router)
热更新后我们重新请求之前的接口会发现404
,是因为我们更改了路由,前面多了/show
,我们需要访问localhost:3001/show/list
才是正确的。
现在基本功能都没啥问题,那么下面想着就是优化的地方了
结尾:优化
配置响应中间件
我这里就简单配置一些响应方式,以及传参的非空判断,具体更详细的可以读者们可以自己去深入。我们在server文件夹创建middleware文件夹用于以后中间件文件的存放。创建response.js
//response.js
// 返回值中间件
const response = () => {
return async (ctx, next) => {
ctx.empty = (arr) => {
var isnull = []
/**
* POST请求就取body体,GET请求就取query,取出传参的数据并放进数组
* arr参数为设置哪些非空的字段,然后两个数组比较,缺少哪些必填参数
*/
const req =
ctx.request.method == "POST"
? ctx.request.body
: ctx.query
for (let item of arr) {
if (!req[item]) {
isnull.push(item)
}
}
if (isnull.length) {
ctx.body = {
code: -1,
msg: "缺少参数" + isnull.join("、"),
}
return true
}
return false
}
ctx.suc = (msg, data) => {
ctx.body = { code: 200, msg, data }
}
ctx.err = (msg, err) => {
ctx.body = {
code: -1,
msg,
err: err ? err.toString() : "",
}
}
await next()
}
}
module.exports = response
然后我们在app.js
这里挂载中间件,这里我们需要再新增个插件koa-bodyparser
,npm i koa-bodyparser
,然后在app.js里面配置。
koa-bodyparser
:这个中间件可以将post请求的参数转为json格式返回,koa接收到的post请求参数并不是json格式,我们需要将其转换为json。注意点:如果post传过来的数据是 form-data 类型的, 此时通过 ctx.request.body 获取不到 post 的参数。
//新增
const response = require("./server/middleware/response")
const bodyParser = require('koa-bodyparser')
//引入koa-bodyparser
app.use(bodyParser())
// 配置请求返回
app.use(response())
接着在前面的show.js
里面增加个新增接口
/**
* @api {post} /show/add 增加一条数据
* @apiGroup 添加
* @apiParam {String} username
* @apiParam {string} phone
*/
router.post('/add',async ctx=>{
let data = ctx.request.body
console.log(ctx.request.body)
//判断所传参数是否为空值
if (ctx.empty(["name", "phone"])) {
return
}
const [err, newUser] = await to(
showModel.create({
name: data.name,
phone: data.phone
})
)
if (err) {
ctx.err("添加失败", err)
console.log('err', err)
return
} else {
console.log('succ')
delete newUser.phone
ctx.suc("添加成功", newUser)
}
})
然后我们利用三方工具来进行请求
可以发现成功添加进去,如果去掉一个参数也能够校验到,说明都没问题了。
到了这一步,剩下的就是对Sequelize的操作是否熟练,能否应对各种业务逻辑,那么你就是一个入门的crud工程师了
配置token
除了基本的增删改查,一个项目中基本都用到token,笔者这里token是用JWT来实现的,也就是jsonwebtoken
插件。我们首先npm下
npm i jsonwebtoken
在middleware文件夹新建token.js
和auth.js
文件。
token.js
:用于对token的校验的以及各个状态返回的封装
auth.js
:用于生成token,配置生成token的参数
/*token.js*/
//用于接口进来检验token
const jwt = require('jsonwebtoken')
const verifyToken = async(ctx,next)=>{
let url = ctx.request.url.split('?')[0]
//以下接口不校验token
let url_config =[ '/user/getToken', ]
//检测是否在不校验接口列表中
let changer = url_config.some((item)=>{
return item == url
})
if(changer){
//不检验token
await next()
}else{
//检测token,当请求头携带token和userId时才通过
let token = ctx.request.headers["authorization"]
let userId = ctx.request.headers["userid"]
if(token && userId){
let payload = jwt.verify(token,'CHEN',async(err,decode)=>{
if(err){
if(err.name=='TokenExpiredError'){
ctx.body={
code:decode,
msg:'token已过期'
}
}else if(err.name == 'JsonWebTokenError'){
ctx.body={
code:decode,
msg:'无效的token'
}
}
}else{
if(decode.userId != userId){
ctx.body = {
code:decode,
msg:'用户ID不正确'
}
}else{
await next()
}
// await next()
}
})
}else{
ctx.body={
code:1000,
msg:'登录信息已过期'
}
}
}
}
module.exports = verifyToken
/*auth.js*/
const jwt = require("jsonwebtoken")//引入jwt
var auth = {
createToken(userId){
//根据userId生成token,设定规则
//时间戳的过期时间单位都为秒
const payload = {
userId,
//获取当前时间
time:Math.round(new Date() / 1000),
//过期时间
timeout:60*60
}
// expiresIn过期时间,单位为秒
//jwt.sign('规则','加密名字','过期时间','箭头函数')
const token = jwt.sign(payload,config.tokenSecret,{expiresIn:60*60})
return token
},
verifyToken(allowUrl) {
return async (ctx, next) => {
if (
allowUrl.indexOf(ctx.request.url) == -1 &&
ctx.request.url.split("/")[1] != "doc"
) {
if (!ctx.request.header.token) {
ctx.body = { code: 110, msg: "token无效" }
return
}
try {
const token = ctx.request.header.token
const payload = jwt.verify(
token,
config.tokenSecret
)
if (
payload.time + payload.timeout <
new Date().getTime()
) {
ctx.body = { code: 111, msg: "token过期" }
return
}
ctx.request.header.userId = payload.userId
await next()
} catch (err) {
ctx.body = {
code: 110,
msg: "token无效",
err: err.toString(),
}
return
}
} else {
await next()
}
}
},
}
module.exports = auth
接着依旧在app.js
配置关于token的中间件
//校验token
const verifyToken = require('./server/middleware/token')
app.use(verifyToken)
这时候我们再次在网页请求localhost:3001/show/list
会发现登录过期,说明token的验证生效了
然后我们在controller文件夹下新建user.js
用来存储用户信息和生成token的操作
const Router = require("@koa/router")
const auth =require("../middleware/auth")
const{
default:to
}=require("await-to-js")
const router = new Router
router.get("/getToken",async ctx=>{
let data = ctx.request.query
// console.log(data)
let token = auth.createToken(data.userId)
// console.log('token',token)
ctx.suc('成功',{token:`Bearer ${token}`})
})
module.exports = router.routes()
在router.js
新增新的路由
//用户获取token等路由
router.use("/user",require("./controller/user"))
我们将token加在请求头上然后再请求之前的list接口,
在控制台可以看到之前list接口的数据打印出来了,说明没有问题了。
最后:项目目录
总结:基本入门就到这了,只要会了上面那些,剩下的就是对于数据库操作的熟练以及其他业务逻辑的处理能力。身为crud工程师,我们可以说是入门了。后面的功能可以自行去探索,常见比如上传功能,导入导出等网上都有文章。感谢大家的阅读,如果这篇文章能帮助到你我将不胜荣幸,如果有不足之处希望大佬们不吝赐教。(* ̄︶ ̄)
转载自:https://juejin.cn/post/7175085003271831612