65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (23投票s)

2017年2月15日

MIT

7分钟阅读

viewsIcon

53147

downloadIcon

532

通用的 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 后,请按照以下步骤操作

  1. 创建一个 PostgreSQL 数据库。
  2. config.js 文件中,设置 PostgreSQL 连接字符串和要访问新数据库的模式名称。
  3. 在命令行中,键入以下内容
    # 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 字段类型。可能的字段类型
  • boolean(是/否)
  • date
  • datetime
  • decimal
  • 文档
  • email
  • 图像
  • 整数
  • lov(值列表)
  • money
  • 文本
  • textmultiline
  • 时间
  • url
readonly 阻止字段修改
inMany 确定字段(默认情况下)是否出现在记录列表中

注意:更多字段属性(uniqueminmaxminLengthmaxLength...)将在稍后添加。

示例模型

这是待办事项应用的模型。

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 日:初始版本
© . All rights reserved.