使用 node-json-db 为 node 项目添加一个简易的本地数据库
数据库大家都不陌生,对于前端来说也不算太熟悉。 很多时候可能会和我一样为了信息抓取会用 node 做一些小爬虫(当然别乱用:爬虫写得好,牢饭吃到饱)
可是 node 的容错性属实有点低,比如
- 一共抓取 20 页的数据,抓到第 5 页,发现 dom 结构不同了,JS 报错了,进程结束
- 抓取第六页的时候,被发现了,爬虫返回了 500, ajax 报错 进程又结束了
- 抓取 20 页数据,抓到一半断网了。这时候数据没保存,又得从头抓起
针对以上的问题,其实无非就是因为我们抓取的每一页数据并没有实时的存储下来,导致遇到错误都得从头开始。那么 node-json-db 就极大方便了我们对数据的操作
搭建 node-json-db 的测试环境
终端执行
npm init -y
npm i koa koa-router nodemon node-json-db
新建几个目录:/src/index
,/db
添加一个启动命令 package.json
"dev": "nodemon src/index.js",
- /src/index.js
const { JsonDB } = require('node-json-db')
const { Config } = require('node-json-db/dist/lib/JsonDBConfig')
const Koa = require('Koa')
const Router = require('koa-router')
const path = require('path')
const fs = require('fs')
const app = new Koa()
var db = new JsonDB(new Config(path.join(__dirname, '../db/myDataBase.json'), true, true, '/'))
const router = new Router()
router.get('/add', function(ctx) {
// ... 预留位置写db操作语句
ctx.body = {
code: 0,
data: db.getData('/'), // 每次访问接口直接打印db所有的数据
msg: ''
}
})
app.use(router.routes())
app.listen(8000, function() {
console.log('app run at http://localhost:8000/')
})
运行后,访问 localhost:8000/ 看到如下
基础 API
看文档也有具体的说明,我只是把几个常用的 API 列举一下
引入 node-json-db
const { JsonDB } = require('node-json-db')
const { Config } = require('node-json-db/dist/lib/JsonDBConfig')
初始化
const db = new JsonDB(new Config(path.join(__dirname, '../db/myDataBase.json'), true, false, '/'))
几个参数的含义:
export declare class Config implements JsonDBConfig {
filename: string;
humanReadable: boolean;
saveOnPush: boolean;
separator: string;
syncOnSave: boolean;
constructor(filename: string, saveOnPush?: boolean, humanReadable?: boolean, separator?: string, syncOnSave?: boolean);
}
filename
: 就是数据库保存的位置和名称saveOnPush
: 是否每当调用push
方法的时候都存到本地 json 中,如果使用false
则需要自己调用save
方法进行保存humanReadable
: 可读性。存入本地 JSON 的时候,默认会进行数据压缩,如果该配置为 true ,则会相当于编辑器格式化 JSON 一样有缩进有换行separator
: 分隔符 默认是/
syncOnSave
: 使用同步写入(毕竟 nodejs 写入文件是支持异步写入的)
上面说到的 separator 分隔符,可以这么理解
const user = { name: 'Jioho', age: 18 }
// 正常我们获取 user 下的 name 属性写法如下
console.log(user.name)
// 修改:
user.name = 'jio'
分隔符就好像上面代码中的 .
,所以在 node-json-db 中,假如你想修改 user.name
,那么就调用相应的 api,其中键值为 user/name
展示不能理解后面有完整的示例
增删改查
增 db.push()
调用 db.push
方法进行增
操作(如果saveOnPush
为 false,则还需要调用 db.save()
才能保存数据到本地)
但是和我们认识的增有一定的区别
举个例子:
// ... 省略引入和初始化db步骤
// 该部分代码写入刚才预留db操作的位置
// 连续调用 3 次 push
db.push('/user', { name: 'Jioho', age: 18 })
db.push('/user', { name: 'Jioho2', age: 19 })
db.push('/user', { name: 'Jioho3', age: 20 })
调用 /add 接口,最后的返回值只有一条数据:
{
"code": 0,
"data": {
"user": {
"name": "Jioho3",
"age": 20
}
},
"msg": ""
}
因为 /user
是一个对象,并不是一个数组,如果想要递增的效果,改为如下代码,明确 user 对象是一个数组类型
PS: 直接修改代码后运行会报错,因为在本地的数据中 user 已经存储了一个对象类型,所以需要手动把本地的 JSON 文件清空掉在运行代码
所以定义数据结构必须一开始想好,否则后面想改就比较麻烦了
db.push('/user[]', { name: 'Jioho', age: 18 })
db.push('/user[]', { name: 'Jioho2', age: 19 })
db.push('/user[]', { name: 'Jioho3', age: 20 })
查
查询的 API 目前有如下几个方法:
db.getData(dataPath)
传入需要查询的数据路径,返回值为 any
毕竟这只是 JSON 操作,所以并不能像 SQL 一样比如指定查询出 user 数组下的所有 name,需要自行写逻辑(查询出所有的 user 数据,然后筛选 name 属性出来)
// 当前JSON 中所有的数据
db.getData('/')
// 查询所有的 user 数据
db.getData('/user')
// 查询第一条
db.getData('/user[0]')
db.getObject(dataPath)
该方法和 db.getData()
一样,只是可以指定返回值类型,如果是在 TS 环境中
interface User {
name: String
age: Number
}
const userList: User[] = db.getObject<User[]>('/user[0]')
const user: User = db.getObject<User>('/user[0]')
db.exists
判断路径下的数据是否存在,这个还是比较实用的,防止数据未定义情况下就去调用
if (db.exists('/user')) {
return db.getData('/user')
} else {
return []
}
db.count
返回当前数据类型的条数
db.count('/user')
db.getIndex(dataPath: string, searchValue: (string | number), propertyName?: string): number
找到所匹配的的一条查询的值需要是字符串或者数字类型,不能为数组类型
返回值为查询到的第一个索引,即便有多条匹配的数据,返回值也是第一个匹配的索引,如果没找到则返回 1
// 查询所有等于19岁的用户
db.getIndex('/user', 19, 'age')
db.getIndexValue
同 getIndex 一样,getIndex 返回的是索引,getValue 返回的则是对应的数据
db.filter(rootPath: string, callback: FindCallback): T[] | undefined;
和 JS 的 filter 一样,传入需要查询的对象的路径,然后传入筛选的方法,返回值为一个数组(支持泛型操作)
// 查询所有等于19岁的用户
db.filter('/user', function(value, index) {
if (value.age >= 19) {
return true
}
return false
})
db.find
用法和 filter 一样,返回值为第一条匹配的数据(也是支持泛型)
改
修改数据其实还是用到 db.push
方法
还记得一开始说的 增
方法中如果 push 的对象不是数组则是替换原理吗?所以改也是用到 push,其实 push 还有第三个参数就是 是否覆盖源对象
默认是 true
db.push('/vipuser', { name: 'Jio', age: 18 })
// 这时候的对象是 user: { name: 'Jio', age: 18 }
// 修改年龄为19
db.push('/vipuser', { name: 'Jio', age: 19 })
// user: { name: 'Jio', age: 20 }
// 修改年龄为20
db.push('/vipuser', { age: 20 })
// ! 注意这里的 name 属性被覆盖了
// user: { age: 20 }
// 这里用上了第三个参数,标记为false,而且没有写入 age 属性
db.push('/vipuser', { name: 'Jioho', job: '前端' }, false)
// user: { name:'Jioho' age: 20, job: '前端' }
以上就是覆盖和不覆盖的区别
假设需要修改数组中指定某一项的值,只需要指定好路径,进行修改即可
db.push('/user[0]', { age: 20 }, false)
// 等价于
db.push('/user[0]/age', 20)
// 如果不指定索引,这时候修改的的则是 user 数组中的最后一项的 age 属性
db.push('/user[]/age', 22)
删
db.delete(path)
,传入要删除的路径即可
更新
db.reload()
因为还有 db.save 方法的存在,个人猜测所有的的数据在初始化后都读到了内存中,这样可以减少每次查数据的时候还要读一次文件的操作。
然而既然是文件,可能不止 node-json-db 可以操作这份 JSON 文件,node 自身也可以写入文件,所以当自己操作 node api 写入文件后,可以通过 db.reload() 进行把最新的数据重新读到内存中
db.resetData(data)
重置当前的数据为data
,这将会重置所有的数据
最后
node-json-db
的出现让一些 node 小工具有更好的拓展性,称之为“数据库”可能会有点夸张,毕竟数据库最基础的 where 方法之类的并没有实现,只有简单的 JS 的 filter
,find
作为支撑
不过对于一些小应用,用这套 JSON 操作的方法来存储一些配置(比如很多脚手架新建项目后都会询问是否保存配置下次使用)。这个库就能很好的胜任了,包括各种查询的方便的操作
最后有一个小提示,就是指定索引 push 或者删除等操作之前,记得先判断 exists~