模型驱动的 REST 或 GraphQL API,用于 CRUD 及更多





5.00/5 (23投票s)
通用的 REST 或 GraphQL 端点,用于 CRUD 和更多功能,使用 JavaScript 编写,采用 Node.js、Express 和 PostgreSQL。
引言
本文介绍了一个模型驱动的 REST 或 GraphQL API,用于 CRUD(创建、读取、更新、删除)。通过它,您可以编写简单的模型(指定数据库表和要公开的列集),REST 端点就会自动可用。无需手动编写任何 SQL。
这个概念可以在不同的技术栈和语言中实现。这里我使用了 JavaScript(生成 SQL)以及 Node.js、Express 和 PostgreSQL。
背景
大多数项目都需要 创建
、读取
、更新
和 删除
对象。当这些对象足够简单时(一个驱动表和数据库中的几列),代码在不同对象之间非常相似。事实上,模式是相同的,唯一的区别是表名以及列的名称和类型。
当然,总会有需要手动编写的复杂端点,但通过自动化简单的端点,我们可以节省大量时间。
Using the Code
示例数据库提供了三个示例:待办事项列表、地址簿和图形小说库存。这些示例使用了对象 ID“todo
”、“contact
”和“comics
”,如其模型中所指定。
在试用它们之后,更改数据库(在 config.js 中),更改模型(在 models 目录中),RESTful API 将根据您的数据结构进行更改。
按照以下步骤使用示例数据库安装和设置项目。
安装
下载 或从 GitHub 克隆。
# To get the latest stable version, use git from the command line.
git clone https://github.com/evoluteur/evolutility-server-node
或使用 npm 包
# To get the latest stable version, use npm from the command line.
npm install evolutility-server-node
安装
安装 Evolutility-Server-Node 后,请按照以下步骤操作
- 创建一个 PostgreSQL 数据库。
- 在 config.js 文件中,设置 PostgreSQL 连接字符串和要访问新数据库的模式名称。
- 在命令行中,键入以下内容
# Install dependencies npm install # Create sample database w/ demo tables node js/setup/database.js # Run the node.js server npm start
在浏览器中,访问 URL https://:3000/api/v1/evolutility/todo。
配置
在 root 目录中,编辑 "config.js" 文件以设置数据库连接和其他选项,如分页和上传目录。
模型
为了被 REST API 访问,每个数据库表都必须在模型中进行描述。模型包含驱动表的名称以及 API 中存在的字段/列列表。
实体
属性 | 含义 |
id | 标识实体的唯一键(用作 API 参数) |
table | 数据库表名 |
字段 | 字段数组 |
titleField | 记录标题的字段 ID |
searchFields | 用于执行搜索的字段 ID 数组 |
字段
属性 | 含义 |
id | 字段的唯一键(可以与列相同,但不必如此) |
列 | 字段的数据库列名 |
lovtable | 用于字段值进行联接的表(仅用于“lov ”类型的字段) |
lovcolumn | 字段值(在 lovtable 中)的列名(仅用于“lov ”类型的字段) |
type | 字段类型不是数据库列类型,而是更偏向 UI 字段类型。可能的字段类型
|
readonly | 阻止字段修改 |
inMany | 确定字段(默认情况下)是否出现在记录列表中 |
注意:更多字段属性(unique
、min
、max
、minLength
、maxLength
...)将在稍后添加。
示例模型
这是待办事项应用的模型。
module.exports = {
id: "todo",
table: "task",
titleField: "title",
searchFields: ["title", "duedate", "description"],
fields: [
{
id: "title",
column: "title",
type: "text",
inMany: true
},
{
id: "duedate",
column: "duedate",
type: "date",
inMany: true
},
{
id: "category",
column: "category_id",
type: "lov",
lovtable: "task_category",
inMany: true
},
{
id: "priority",
column: "priority_id",
type: "lov",
lovtable: "task_priority",
required: true,
inMany: true
{
id: "complete",
column: "complete",
type: "boolean",
inMany: true
},
{
id: "description",
column: "description",
type: "textmultiline"
}
]
};
此模型仅涵盖后端。也可以对前端进行建模(只要 UX 模式足够简单,或者变得过于复杂而不值得)。您可以在我之前的文章《CRUD 应用程序的极简元模型》中了解更多关于此内容。
REST API
Evolutility-Server-Node 的 API 受到 PostgREST 的 API 的强烈启发(甚至部分复制),PostgREST 也提供通用的 CRUD,但它直接检查数据库架构而不是使用模型。
在本地运行项目时,“todo
”应用的 URL 是 https://:3000/api/v1/evolutility/todo。
请求信息
获取单个
要通过 ID
获取特定记录,请使用“< ObjectName >/ID
”。
GET /<object>/<id>
GET /todo/12
获取多个
所有对象都已公开。您可以通过使用模型 ID 查询项目列表。
GET /<object>
GET /todo
过滤
您可以通过添加字段条件来过滤结果行,每个条件都是一个查询字符串参数。
GET /<object>/<field.id>=<operator>.<value>
GET /todo?title=sw.a
GET /todo?priority=in.1,2,3
添加多个参数会合并条件
todo?complete=0&duedate=lt.2017-01-01
这些运算符可用
运算符 | 含义 | 示例 |
eq | equals | /todo?category=eq.1 |
gt | greater than | /todo?duedate=gt.2017-01-15 |
lt | less than | /todo?duedate=lt.2017-01-15 |
gte | 小于或等于 | /todo?duedate=gte.2017-01-15 |
lte | 小于或等于 | /todo?duedate=lte.2017-01-15 |
ct | contains | /todo?title=ct.e |
sw | 开始于 | /todo?title=sw.a |
fw | 结束于 | /todo?title=fw.z |
in | 值列表中的一个 | /todo?priority=in.1,2,3 |
0 | 为 false | /todo?complete=0 |
1 | 为 true | /todo?complete=1 |
null | 为 null | /todo?category=null |
nn | 不为 null | /todo?category==nn |
排序
保留字“order
”用于重新排序响应行。它使用逗号分隔的字段和方向列表
GET /<object>?order=<field.id>.<asc/desc>
GET /todo?order=priority.desc,title.asc
如果未指定方向,则默认为升序
GET /todo?order=duedate
限制和分页
保留字“page
”和“pageSize
”用于限制响应行。
GET /<object>?page=<pageindex>&pageSize=<pagesize>
GET /todo?page=0&pageSize=50
格式化
默认情况下,所有 API 都以 JSON 格式返回数据。此 API 调用允许以 CSV 格式请求数据(导出到 Excel)。此功能使用了 express-csv。
GET /<object>?format=csv
GET /todo?format=csv
注意:在返回的数据中,每个对象都有一个额外的属性“_full_count
”,该属性指示查询中的记录总数(在限制之前)。
更新数据
记录创建
要创建数据库表中的一行,请 POST 一个 JSON 对象,其键是您要创建的列的名称。缺少键将在适用时设置为默认值。
POST /todo
{ title: 'Finish testing', priority: 2}
更新
PATCH /todo
{ title: 'Finish testing', priority: 2}
删除
只需使用 DELETE
动词和要删除的记录的 ID 即可。
DELETE /<object>/<id>
DELETE /todo/5
额外端点
除了 CRUD,Evolutility-Server-Node 还为图表和值列表等常见 UI 需求提供了端点。
发现
返回对象列表及其 API(仅包括标记为激活的对象)。
GET /
注意:此端点必须在配置中启用 {apiInfo: true}。
Charts
对于图表数据,可以获取聚合数据。
GET /<object>/chart/<field id>
GET /todo/chart/category
值列表
UI 中的下拉字段(模型中为 field.type="lov"
)有一个 REST 端点,用于获取下拉列表的值。
GET /<object>/lov/<field id>
GET /todo/lov/category
统计
返回数字字段的总计数以及最小值、最大值、平均值和总值。
GET /<object>/stats
GET /todo/stats
文件上传
此端点允许您上传文件。当前(简陋的)实现仅将文件保存在文件服务器上,一个名为对象 ID 的文件夹中。
POST /<object>/upload/<id>
POST /comics/upload/5
使用查询参数:file
和“fieldid
”。
嵌套集合
如果模型定义了集合,则可以使用此端点查询它们。
GET /<model.id>/collec/<collection.id>?id=<id>
GET /winecellar/collec/wine_tasting?id=1</code>
API 版本
此端点获取 API 版本(如项目 package.json 文件中所指定)。
GET /version
GraphQL
Evolutility-Server-Node 提供基于与 REST API 相同模型的 GraphQL 接口。
在本地运行项目时,garphiQL 的 URL 是 https://:2000/graphql。
请求信息
按 ID 获取单个记录
用于按 ID 获取单个记录。
{
contact (id: 1 ){
firstname
lastname
category_txt
email
}
}
获取多个记录
所有对象都公开用于查询,并带有搜索和过滤器。过滤器使用与 REST API 相同的条件语法(例如:{ firstname: "sw.A"
} 表示“名字以 'A
' 开头”)。
类型为“lov”(值列表)的字段表示为 2 个字段,用于 ID 和值。
{
urgent_tasks: todos ( complete: "false", priority: "lt.3" ){
title
description
priority
priority_txt
category
category_txt
complete
}
ab_a_contacts: contacts (search: "ab", firstname: "sw.A") {
id
firstname
lastname
category_txt
email
}
}
图表数据
对于所有对象记录,都可以按字段进行聚合和计数(针对数字或“lov”类型的字段)。
{
contacts_by_category: contact_charts(fieldId:"category"){
label
value
}
task_by_priority: todo_charts(fieldId:"priority") {
label
value
}
restaurants_by_cuisine: restaurant_charts(fieldId:"cuisine") {
label
value
}
}
关注点
模型驱动架构(MDA)将使您更快地启动和运行,同时也能节省您项目演进维护中的时间。
例如,当项目完成后,我们通常需要添加字段。这通常意味着向数据库添加列,并在使用该数据库表的每个 REST 端点中手动添加字段。通过模型驱动的方法,一旦将列添加到数据库,您只需要在一个地方(模型)添加字段,并且使用该模型的每个端点都将公开它。
模型驱动的方法也可以应用于 UI。如果您想尝试一下,我为匹配的模型驱动 UI 制作了两个不同的实现:Evolutility-UI-React(用于 React)和 Evolutility-UI-jQuery(用于 jQuery 和 BackboneJS)。
历史
- 2017 年 2 月 15 日:初始版本