介绍
本文介绍了用于CRUD(创建、读取、更新、删除)的模型驱动REST或GraphQL API。有了它,您可以编写简单的模型(指定数据库表和要公开的列集),CRUD的REST端点将自动可用。不需要任何SQL的手动编码。
这个概念可以在不同的技术栈和语言上实现。在这里,我将JavaScript(生成SQL)与Node.js、Express和PostgreSQL结合使用。
背景
大多数项目需要Create、Read、Update和Delete对象。当这些对象足够简单时(一个驱动表和数据库中的几列),代码从一个对象到另一个对象非常相似。实际上,模式是相同的,唯一的区别是表的名称和列的名称和类型。
当然,总会有复杂的端点需要手工编写,但通过自动化简单的端点,我们可以节省大量时间。
使用代码
示例数据库为待办事项列表、地址簿和图画小说库存提供了三个示例。这些示例使用模型中指定的对象ID“todo”、“contact”和“comics”。
使用它们之后,更改数据库(在config.js中),更改模型(在模型目录中),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
在Web浏览器中,转到URL http://localhost:3000/api/v1/evolutility/todo。
配置
在根目录下,编辑文件“config.js”来设置数据库连接和其他选项,如分页和上传目录。
Models
要由REST API访问,必须在模型中描述每个数据库表。模型包含驱动表的名称和API中存在的字段/列的列表。
实体
财产 | 意义 |
id | 标识实体的唯一键(用作API参数) |
table | 数据库表名 |
fields | 字段数组 |
titleField | 记录标题的字段ID |
searchFields | 用于执行搜索的字段的字段ID数组 |
字段
财产 | 意义 |
id | 字段的唯一键(可以与列相同,但不一定) |
column | 字段的数据库列名称 |
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模式足够简单或者变得复杂到不值得)。
REST API
Evolutility-Server-Node的API受到PostgREST的极大启发(甚至部分复制),PostgREST也提供通用CRUD,但直接检查数据库模式而不是使用模型。
在本地运行项目时,“todo”应用程序的URL是http://localhost: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 | 等于 | /todo?category=eq.1 |
gt | 比...更大 | /todo?duedate=gt.2017-01-15 |
lt | 少于 | /todo?duedate=lt.2017-01-15 |
gte | 大于或等于 | /todo?duedate=gte.2017-01-15 |
lte | 小于或等于 | /todo?duedate=lte.2017-01-15 |
ct | 包含 | /todo?title=ct.e |
sw | 从...开始 | /todo?title=sw.a |
fw | 以…结束 | /todo?title=fw.z |
in | 值列表之一 | /todo?priority=in.1,2,3 |
0 | 是假的 | /todo?complete=0 |
1 | 是真的 | /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”,表示查询中的记录总数(限制之前)。
更新数据
记录创建
要在数据库表中创建一行,请发布一个JSON对象,其键是您要创建的列的名称。缺少的键将在适用时设置为默认值。
POST /todo
{ title: 'Finish testing', priority: 2}
更新
PATCH /todo
{ title: 'Finish testing', priority: 2}
删除
只需使用带有记录id的动词DELETE即可删除。
DELETE /<object>/<id>
DELETE /todo/5
附加端点
除了CRUD,Evolutility-Server-Node还为常见的UI需求提供端点,例如图表和值列表。
发现
返回对象及其API的列表(仅包括标记为活动的对象)。
GET /
注意:必须在配置中使用{apiInfo: true}启用此端点。
图表
对于图表数据,可以获得聚合数据。
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是http://localhost:2000/graphql。
请求的信息
按ID获取一条记录
通过Id获取单个记录。
{
contact (id: 1 ){
firstname
lastname
category_txt
email
}
}
获得许多记录
所有对象都暴露给带有搜索和过滤器的查询。过滤器使用与REST API相同 的条件语法 (例如:{ firstname: "sw.A"} 表示“名字以“A”开头)。
“lov”(值列表)类型的字段表示为Id和值的2个字段。
{
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)。
https://www.codeproject.com/Articles/1169060/Model-driven-REST-or-GraphQL-API-for-CRUD-and-More