Feathers 数据库适配器(Database Adapters)完全指南:统一 API、分页、查询限制与服务方法实战

发布时间:2026/9/21 15:21:04
Feathers 数据库适配器(Database Adapters)完全指南:统一 API、分页、查询限制与服务方法实战 Feathers 数据库适配器Database Adapters完全指南统一 API、分页、查询限制与服务方法实战【免费下载链接】feathersThe API and real-time application framework项目地址: https://gitcode.com/gh_mirrors/fe/feathersFeathers 的数据库适配器是一组将标准 CRUD 能力封装为 Feathers 服务Services 的模块让 Memory、MongoDB 与 SQLKnex等异构存储共享同一套初始化方式、同一套通用查询语法和一致的服务方法行为。读完本文你将掌握官方适配器的选型与安装、通用初始化选项id、paginate、multi与查询限制机制、分页对象的启用与按请求覆盖、通过params.adapter/params.paginate做动态调整以及find/get/create/update/patch/remove六类方法的完整语义与对应 REST 端点并能在源码层面理解其实现原理。什么是 Feathers 数据库适配器Feathers 数据库适配器是提供服务的模块这些服务针对特定数据库实现了标准的 CRUD 功能。它们遵循两条统一约定使用通用 API 进行初始化和设置提供通用查询语法 用于过滤、排序、分页与字段选择。需要注意的是服务Services 本身允许接入任何数据库或 API适配器只是带有通用 API 的便捷封装。如果找不到合适的适配器依然可以在自定义服务中直接使用数据库更多数据存储的支持可参见社区适配器部分。从源码上看所有适配器共同继承自 packages/adapter-commons/src/service.ts 中定义的AdapterBase抽象基类它要求子类实现_find、_get、_create、_update、_patch、_remove六个底层方法并在构造函数中为id、events、paginate、multi、filters、operators提供默认值——这正是通用 API的代码级来源。官方核心适配器一览Feathers 核心提供了三个官方数据存储适配器核心包Core Package支持的数据存储Memory内存MongoDBMongoDBSQL (Knex)MySQL、MariaDB、PostgreSQL、CockroachDB、SQLite、Amazon Redshift、OracleDB、MSSQLfeathersjs/memory面向全平台的内存存储适配器通常不用于生产服务器但非常适合非持久化数据或在浏览器、React Native 应用中缓存数据。feathersjs/knex基于 KnexJS 的查询构建器支持上述多种 SQL 数据库兼具直观语法与迁移等高级工具且没有完整 ORM 那样的开销。feathersjs/mongodb基于 MongoDB Node.js 驱动内部使用 MongoDB 聚合框架Aggregation Framework可在 Feathers 友好语法之上叠加$lookup、$unwind等聚合算子的全部能力。此外Ecosystem 页面 还维护着大量针对其他数据库和 ORM 的社区适配器。通用初始化new NameService(options)每个适配器都导出一个NameService类既可以导出也可以被继承扩展。初始化方式如下import { NameService } from feathers-name app.use(/messages, new NameService()) app.use(/messages, new NameService({ id, events, paginate }))通用选项Options所有数据库适配器都支持以下选项id {string}可选id 字段属性的名称默认通常为id或_id。例如 Knex 始终添加id而 MongoDB 始终添加_id。paginate {Object}可选一个分页对象包含default和max两个页大小值。multi {string[]|boolean}可选默认false允许create传入数组以及patch/remove以id为null的方式修改多个条目。可设为true放行所有方法或设为方法名数组如[ remove, create ]。以下旧版选项仍然可用但应避免使用events {string[]}可选已弃用该服务发送的自定义服务事件列表。应改用注册服务时传给app.use的events选项见 application.md#usepath-service--options。operators {string[]}可选已弃用额外允许的非标准查询参数列表如[ $regex ]。推荐改用查询 schema。filters {Object}可选已弃用额外顶层查询过滤器对象如{ $populate: true }也可以是转换函数如{ $ignoreCase: (value) value true ? true : false }。推荐改用查询 schema。源码佐证在 packages/adapter-commons/src/declarations.ts 的AdapterServiceOptions接口中operators、filters、events、whitelist均被标注为deprecated而multi、id、paginate仍是活跃选项与文档中的旧版选项应避免一致。各适配器的专属选项Memory见 docs/api/databases/memory.mdstartId默认0每次新建记录自增的起始 id、store用 id 到条目的映射预初始化数据存储、allow额外允许的查询参数。源码中 packages/memory/src/index.ts 的MemoryAdapter默认matcher为sift、sorter为内置排序函数并维护_uId自增计数器。Knex见 docs/api/databases/knex.mdModel {Knex}必填KnexJS 数据库实例、name {string}必填表名、schema {string}可选schema 表前缀如schema.table、tableOptions {only: boolean}仅 PostgreSQL控制是否使用ONLY关键字、extendedOperators {[string]: string}可选为查询构建器定义额外操作符的映射如{ $fulltext: }用于 PostgreSQL 全文搜索。MongoDB见 docs/api/databases/mongodb.mdModel {PromiseMongoDBCollection}必填解析为 MongoDB collection 实例的 Promise、disableObjectify {boolean}默认false禁用 id 字段到 MongoDB ObjectID 的转换、useEstimatedDocumentCount {boolean}默认false为true时文档计数改用estimatedDocumentCount而非countDocuments、disabledOperators {string[]}默认[$rename]屏蔽patch数据中特定的 MongoDB 更新操作符。查询是如何被限制的How queries are restricted适配器通过两种方式之一保护外部查询默认情况下二者是二选一的关系而非叠加的层级路径适用时机允许的查询由什么决定内置净化Built-in sanitization未配置validateQuery钩子或查询未被标记为已验证通用查询语法加上服务上的operators/filters查询 schemaQuery schemavalidateQuery以默认选项校验成功仅你的查询 schema当使用查询 schema 时适配器默认不会再次执行它的$操作符白名单——schema 本身就是完整的白名单。应配合querySyntax/ 查询辅助函数以及additionalProperties: false使未知操作符无法通过。手动编写的 TypeBoxType.Object({ ... })schema 若缺少该选项在 Ajv 下是宽松permissive的。若希望同时运行 schema 校验与内置白名单可使用validateQuery(schema, { skipSanitize: false })。源码层面这一机制体现在 packages/adapter-commons/src/service.ts 的sanitizeQuery方法中如果params.query已被 schema 标记为已验证通过Symbol.for(feathersjs/adapter/sanitized)则直接返回原查询不再做旧式净化否则调用 packages/adapter-commons/src/query.ts 的filterQuery用OPERATORS [$in, $nin, $lt, $lte, $gt, $gte, $ne, $or]加自定义operators校验每个属性遇到未允许的$前缀参数会抛出BadRequestInvalid query parameter/Invalid filter value。各数据库专属选项见对应适配器文档Memory、Knex、MongoDB。分页Pagination初始化适配器时可在paginate对象中设置以下选项default当未设置$limit时的默认条目数max每页允许的最大条目数即使查询中的$limit设置得更高也会被钳制。当设置了paginate.default时find将返回一个分页对象而非普通数组形式如下{ total: 记录总数, limit: 每页最大条目数, skip: 跳过的条目数偏移量, data: [/* 数据 */] }分页选项的设置方式如下const service require(feathers-db-name) // 初始化时设置 paginate 选项 app.use( /todos, service({ paginate: { default: 5, max: 25 } }) ) // 在本次调用的 params.paginate 中覆盖分页 app.service(todos).find({ paginate: { default: 100, max: 200 } }) // 禁用本次调用的分页 app.service(todos).find({ paginate: false })注意禁用或修改默认分页在客户端不可用。客户端只会把params.query传给服务器。$limit与分页配合的实用技巧启用分页后若只想获取记录总数可将$limit设为0此时仅执行一次快速的计数查询返回包含total与空data数组的分页对象。源码中getLimitpackages/adapter-commons/src/query.ts正是实现上限钳制的函数paginate.max存在时实际 limit 取Math.min(lower, upper)。params.adapter按请求动态修改适配器选项在服务方法params中设置adapter可以根据请求动态修改数据库适配器选项例如临时允许批量插入/修改或临时调整分页设置const messages [ { text: message 1 }, { text: message 2 } ] // 为本次请求启用批量插入 app.service(messages).create(messages, { adapter: { multi: true } })提示如果适配器有Model选项params.adapter.Model可用于按请求指向不同的数据库例如实现多租户系统。这通常在钩子hook中通过设置context.params.adapter完成。源码实现见AdapterBase.getOptionspackages/adapter-commons/src/service.ts它先以params.paginate覆盖默认分页再用...params.adapter浅合并覆盖其他选项allowsMulti方法也通过getOptions(params)读取multi后判断某方法是否允许批量操作Memory 适配器的_patch/_remove在id null且不允许multi时会抛出MethodNotAllowed。params.paginate按请求覆盖分页在服务方法params中设置paginate可以针对单次请求修改或禁用默认分页// 以数组形式获取全部消息 const allMessages await app.service(messages).find({ paginate: false })扩展适配器Extending Adapters扩展现有数据库适配器有两种方式继承基类或通过钩子添加功能。类继承Classes所有模块都以 ES6 类的形式导出NameService可直接继承class MyMessageService extends MemoryServiceMessage, MessageData {}以feathersjs/memory为例见 docs/api/databases/memory.md 与 packages/memory/src/index.ts泛型参数Result、Data、PatchData使继承后的服务拥有完整的类型安全CLI 生成的 SQL/MongoDB 服务同样通过extends KnexService.../extends MongoDBService...来定制。如何覆盖已有方法、实现新方法可参考 Service CLI 指南。无钩子方法Hook-less Methods数据库适配器支持在方法名前加_来调用不带任何钩子的服务方法_find、_get、_create、_patch、_update、_remove。当你需要服务的原始数据、又不想触发任何钩子时非常有用// 调用 get 而不运行任何钩子 const message await app.service(/messages)._get(message id)注意这些方法仅服务端内部可用客户端不可用且只存在于 Feathers 数据库适配器上。它们不会发送任何事件。这一约定在源码中对应InternalServiceMethods接口packages/adapter-commons/src/declarations.ts其注释明确标注不净化查询、不运行钩子、不应在客户端使用Memory 的公开find/get/patch/remove都会先调用sanitizeQuery再委托给_前缀的底层方法二者职责分离清晰可见。服务方法详解Service Methods本节说明所有适配器对服务方法的具体实现。以下以messages服务为例同时给出 REST 等价写法。constructor(options)初始化一个新的服务。重写时应当调用super(options)。adapter.find(params)adapter.find(params) - Promise使用通用查询机制返回params.query中匹配查询的所有记录。若启用了分页返回分页对象否则返回结果数组。// 查找用户 id 为 1 的所有消息 const messages await app.service(messages).find({ query: { userId: 1 } }) console.log(messages) // 查找属于 room 1 或 3 的所有消息 const roomMessages await app.service(messages).find({ query: { roomId: { $in: [1, 3] } } }) console.log(roomMessages)REST 等价写法GET /messages?userId1 GET /messages?roomId[$in]1roomId[$in]3adapter.get(id, params)adapter.get(id, params) - Promise通过唯一标识初始化时id选项指定的字段获取单条记录const message await app.service(messages).get(1) console.log(message)GET /messages/1adapter.create(data, params)adapter.create(data, params) - Promise用data创建新记录data也可以是数组以批量创建多条记录const message await app.service(messages).create({ text: A test message }) console.log(message) const messages await app.service(messages).create([ { text: Hi }, { text: How are you } ]) console.log(messages)POST /messages { text: A test message }adapter.update(id, data, params)adapter.update(id, data, params) - Promise用data整体替换id标识的单条记录。不允许替换多条id不能为nullid本身不可被修改const updatedMessage await app.service(messages).update(1, { text: Updates message }) console.log(updatedMessage)PUT /messages/1 { text: Updated message }adapter.patch(id, data, params)adapter.patch(id, data, params) - Promise将id标识的记录与data合并。id可以为null此时会修改所有匹配params.query的记录匹配规则与.find相同。id不可被修改const patchedMessage await app.service(messages).patch(1, { text: A patched message }) console.log(patchedMessage) const params { query: { read: false } } // 把所有未读消息标记为已读 const multiPatchedMessages await app.service(messages).patch( null, { read: true }, params )PATCH /messages/1 { text: A patched message }把所有未读消息标记为已读PATCH /messages?readfalse { read: true }adapter.remove(id, params)adapter.remove(id, params) - Promise删除id标识的记录。id可以为null以删除多条记录匹配params.query规则同.findconst removedMessage await app.service(messages).remove(1) console.log(removedMessage) const params { query: { read: true } } // 删除所有已读消息 const removedMessages await app.service(messages).remove(null, params)DELETE /messages/1删除所有已读消息DELETE /messages?readtrue方法语义速查方法行为id为null批量REST 端点find(params)按查询返回数组或分页对象—GET /messagesget(id, params)按唯一标识取单条不允许GET /messages/1create(data, params)新建data可为数组—POST /messagesupdate(id, data, params)整体替换不允许PUT /messages/1patch(id, data, params)合并式更新id可传null需multi允许PATCH /messages/1、PATCH /messages?readfalseremove(id, params)删除id可传null需multi允许DELETE /messages/1、DELETE /messages?readtrue从源码看alwaysMulti映射packages/adapter-commons/src/service.ts将find固定为允许批量、get与update固定为不允许批量而create/patch/remove的批量能力则完全由multi选项决定。MongoDB 的update还额外支持 MongoDB 的更新操作符但需注意disabledOperators默认阻止$rename与 schema 校验来防范权限提升等风险详见 MongoDB 适配器文档。实战延伸把通用 API 落到具体适配器内存场景npm install --save feathersjs/memory后app.use(messages, new MyMessageService({}))即可获得一个零配置的可查询内存服务见 docs/api/databases/memory.md。SQL 场景npm install --save feathersjs/knex将KnexService与来自应用配置的sqliteClient或对应数据库客户端结合Model与name为必填项还支持createQuery覆写实现联表查询、params.knex定制查询、以及transaction.start/end/rollback三个事务钩子见 docs/api/databases/knex.md。MongoDB 场景npm install --save feathersjs/mongodbModel传入解析为 collection 的 Promiseparams.pipeline可注入$lookup/$unwind等聚合阶段还可使用$feathers阶段指定 Feathers 查询注入位置params.mongodb可传{ upsert: true }或事务session见 docs/api/databases/mongodb.md。三个适配器都实现了通用查询语法——包括$limit、$skip、$sort、$select、$or、$and等过滤器以及$in、$nin、$lt、$lte、$gt、$gte、$ne等操作符还可在各自文档中查阅数据库特有的搜索能力SQL 的$like/$notlike/$ilikeMongoDB 的$regex/$search。这意味着你只需学会一套 API 和一套查询语法就能在 Feathers 中自由切换或混用多种数据存储。【免费下载链接】feathersThe API and real-time application framework项目地址: https://gitcode.com/gh_mirrors/fe/feathers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考