PostgREST高级技巧:专家级使用经验分享

PostgREST高级技巧:专家级使用经验分享

【免费下载链接】postgrest PostgREST是一个开源的RESTful API服务器,用于将PostgreSQL数据库暴露为RESTful API。 - 功能:RESTful API服务器;PostgreSQL数据库;RESTful API。 - 特点:易于使用;轻量级;支持多种编程语言;高性能。 【免费下载链接】postgrest 项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

引言:超越基础的PostgREST之旅

你是否已经掌握了PostgREST的基本用法,却在面对复杂业务场景时感到力不从心?是否想要充分发挥PostgreSQL与PostgREST组合的强大潜力,构建高性能、高安全性的API服务?本文将带你深入PostgREST的高级特性,分享专家级使用经验,助你突破瓶颈,实现API开发的质的飞跃。

读完本文,你将能够:

  • 深入理解PostgREST的内部架构与工作原理
  • 掌握高级查询技巧,优化API性能
  • 实现复杂的权限控制与数据安全策略
  • 利用PostgREST与前端框架构建动态Web应用
  • 解决实际开发中遇到的常见难题与性能瓶颈

PostgREST架构深度剖析

整体架构概览

PostgREST的架构设计体现了其"以数据库为中心"的核心理念。下图展示了PostgREST的主要组件及其交互关系:

mermaid

核心组件详解

  1. API请求处理流程

PostgREST处理一个API请求的完整生命周期如下:

mermaid

  1. Schema Cache(模式缓存)

Schema Cache是PostgREST性能优化的关键组件。它在启动时从数据库中加载模式信息,并在运行时保持更新。这一机制显著减少了数据库查询次数,提高了API响应速度。

mermaid

  1. 查询计划生成

Plan模块负责将API请求转换为数据库执行计划,这是PostgREST最复杂的组件之一。它处理以下关键任务:

  • 资源嵌入(resource embedding)的JOIN操作生成
  • 过滤条件转换为SQL WHERE子句
  • 排序、分页、聚合等操作的SQL转换
  • 权限检查与访问控制

高级查询技巧与性能优化

复杂查询构建

  1. 高级过滤与条件查询

PostgREST支持丰富的查询操作符,可实现复杂的过滤逻辑:

# 基本比较操作
GET /todos?task=eq.Todo1

# 模糊匹配
GET /todos?task=like.*important*

# 范围查询
GET /todos?priority=gte.3&due_date=lt.2023-12-31

# 数组包含
GET /users?tags=cs.{work,urgent}

# JSON字段查询
GET /products?metadata->rating=gt.4.5

# 复杂逻辑组合
GET /todos?or=(task=like.*report*,and=(priority=gte.3,due_date=lt.2023-12-31))
  1. 资源嵌入与关联查询优化

资源嵌入是PostgREST的强大特性,但不当使用会导致性能问题。以下是一些优化技巧:

# 基本嵌入
GET /authors?select=name,books(title,published_date)

# 深层嵌入
GET /authors?select=name,books(title,chapters(number,title))

# 带过滤条件的嵌入
GET /authors?select=name,books(title,published_date)&books.published_date=gt.2020-01-01

# 使用外连接避免数据丢失
GET /authors?select=name,books(title).outer

# 嵌入排序与限制
GET /authors?select=name,books(title,published_date)&order=name.asc&books.order=published_date.desc&books.limit=5

为提高嵌入查询性能,建议:

  • 为外键关系创建适当的索引
  • 限制返回字段数量,只获取必要数据
  • 对大型数据集使用分页
  • 考虑使用计算字段替代深层嵌入
  1. 聚合查询与统计分析

PostgREST提供了丰富的聚合函数支持,可直接通过API进行数据分析:

# 基本聚合
GET /todos?select=count(*),avg(priority),max(due_date)

# 分组聚合
GET /todos?select=status,count(*)&group=status

# 带过滤条件的聚合
GET /todos?select=status,count(*)&group=status&due_date=lt.2023-12-31

# 多字段分组
GET /todos?select=status,category,count(*),sum(estimated_hours)&group=status,category

# 聚合与嵌入结合
GET /authors?select=name,books!books_author_id_fkey(count(*),avg(rating))&group=name

性能优化策略

  1. 连接池配置优化

合理配置连接池对PostgREST性能至关重要。以下是一些关键配置参数:

# postgrest.conf
db-pool = 20                  # 连接池大小
db-pool-timeout = 300         # 连接超时时间(秒)
db-channel = "postgrest_changes"  # 监听通道

连接池大小应根据服务器资源和预期并发量进行调整,一般建议设置为CPU核心数的2-4倍。

  1. 缓存策略

PostgREST的Schema Cache是提高性能的关键。合理配置缓存刷新策略:

# postgrest.conf
db-schema = "api"             # API模式
schema-cache-max-lifetime = 600  # 缓存最大生命周期(秒)

对于频繁变化的数据库模式,可以使用LISTEN/NOTIFY机制实现实时刷新:

-- 在数据库中执行
NOTIFY pgrst, 'reload schema';
  1. 查询优化

利用PostgreSQL的性能优化特性,结合PostgREST的查询能力:

-- 创建优化的索引
CREATE INDEX idx_todos_due_date_status ON api.todos(due_date, status);

-- 使用物化视图加速复杂查询
CREATE MATERIALIZED VIEW api.todo_stats AS
  SELECT status, category, count(*), avg(priority)
  FROM api.todos
  GROUP BY status, category;

-- 为物化视图创建索引
CREATE UNIQUE INDEX idx_todo_stats_status_category ON api.todo_stats(status, category);

-- 定期刷新物化视图
REFRESH MATERIALIZED VIEW api.todo_stats;

通过API查询物化视图:

GET /todo_stats?select=status,category,count,avg

高级权限控制与数据安全

行级安全策略(RLS)深度应用

PostgreSQL的行级安全策略与PostgREST结合,可实现细粒度的数据访问控制:

-- 启用RLS
ALTER TABLE api.todos ENABLE ROW LEVEL SECURITY;

-- 创建策略:用户只能查看自己的任务
CREATE POLICY todos_select_own ON api.todos
  FOR SELECT USING (created_by = current_setting('jwt.claims.user_id')::integer);

-- 创建策略:用户只能更新自己的任务
CREATE POLICY todos_update_own ON api.todos
  FOR UPDATE USING (created_by = current_setting('jwt.claims.user_id')::integer);

-- 创建策略:管理员可以查看所有任务
CREATE POLICY todos_select_admin ON api.todos
  FOR SELECT USING (
    EXISTS (
      SELECT 1 FROM api.users 
      WHERE id = current_setting('jwt.claims.user_id')::integer 
      AND role = 'admin'
    )
  );

JWT认证高级配置

高级JWT配置示例:

# postgrest.conf
jwt-secret = "your-very-secure-secret-key"  # JWT密钥
jwt-aud = "postgrest"                      # 受众声明
jwt-role-claim = "role"                    # 角色声明字段
jwt-claim-key = "https://your-api.com/claims"  # 自定义声明前缀
jwt-exp = 3600                             # 过期时间(秒)

使用RSA非对称加密算法增强安全性:

# postgrest.conf
jwt-secret = "@/path/to/public.key"  # 使用公钥验证JWT

多租户数据隔离

利用PostgreSQL的模式(schema)功能实现多租户隔离:

-- 创建租户模式
CREATE SCHEMA tenant_a;
CREATE SCHEMA tenant_b;

-- 创建相同结构的表
CREATE TABLE tenant_a.todos (LIKE api.todos INCLUDING ALL);
CREATE TABLE tenant_b.todos (LIKE api.todos INCLUDING ALL);

-- 创建租户切换函数
CREATE OR REPLACE FUNCTION api.switch_tenant(tenant_id text) 
RETURNS void AS $$
BEGIN
  -- 验证租户权限
  IF NOT EXISTS (
    SELECT 1 FROM api.tenant_users 
    WHERE tenant_id = $1 
    AND user_id = current_setting('jwt.claims.user_id')::integer
  ) THEN
    RAISE EXCEPTION 'Permission denied for tenant %', $1;
  END IF;
  
  -- 设置当前租户
  PERFORM set_config('app.tenant', $1, true);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 创建视图动态访问当前租户数据
CREATE OR REPLACE VIEW api.tenant_todos AS
  SELECT * FROM (
    SELECT table_name::text 
    FROM information_schema.tables 
    WHERE table_schema = current_setting('app.tenant')
  ) AS t(tbl)
  CROSS JOIN LATERAL (
    EXECUTE format('SELECT * FROM %I.todos', t.tbl)
  ) AS data;

在API中使用:

# 设置租户
POST /rpc/switch_tenant
{"tenant_id": "tenant_a"}

# 访问租户数据
GET /tenant_todos

PostgREST与前端框架集成:HTMX实例

PostgREST不仅可以作为REST API后端,还可以直接与前端框架结合,提供完整的Web应用开发体验。下面以HTMX为例,展示如何构建一个动态的待办事项应用。

准备工作

首先,配置PostgREST支持HTML响应:

-- 创建HTML媒体类型
CREATE DOMAIN "text/html" AS text;

-- 授予权限
GRANT USAGE ON DOMAIN "text/html" TO web_anon, authenticator;

构建HTML响应函数

-- HTML转义函数
CREATE OR REPLACE FUNCTION api.sanitize_html(text) RETURNS text AS $$
  SELECT replace(replace(replace(replace(replace($1, '&', '&amp;'), '"', '&quot;'), '>', '&gt;'), '<', '&lt;'), '''', '&apos;')
$$ LANGUAGE sql;

-- 单个待办事项HTML生成函数
CREATE OR REPLACE FUNCTION api.html_todo(api.todos) RETURNS text AS $$
  SELECT format($html$
    <div class="todo-item grid" id="todo-%1$s">
      <div class="todo-task">
        <form hx-post="/rpc/toggle_todo" hx-target="#todo-%1$s" hx-swap="outerHTML">
          <input type="hidden" name="id" value="%1$s">
          <button type="submit" class="todo-toggle">
            %2$s
          </button>
          <span class="todo-text %3$s">%4$s</span>
        </form>
      </div>
      <div class="todo-actions">
        <button class="edit-btn" 
                hx-get="/rpc/edit_todo_form?id=%1$s" 
                hx-target="#todo-%1$s" 
                hx-swap="outerHTML">
          编辑
        </button>
        <button class="delete-btn" 
                hx-delete="/rpc/delete_todo" 
                hx-target="#todo-%1$s" 
                hx-swap="delete"
                hx-confirm="确定要删除吗?">
          删除
        </button>
      </div>
    </div>
  $html$,
  $1.id,
  CASE WHEN $1.done THEN '✓' ELSE '□' END,
  CASE WHEN $1.done THEN 'done' ELSE '' END,
  api.sanitize_html($1.task)
  );
$$ LANGUAGE sql STABLE;

-- 所有待办事项列表HTML生成函数
CREATE OR REPLACE FUNCTION api.html_all_todos() RETURNS "text/html" AS $$
  SELECT format($html$
    <div class="todo-list">
      %s
    </div>
    $html$,
    COALESCE(
      string_agg(api.html_todo(t), '<hr/>' ORDER BY t.due_date, t.priority DESC),
      '<p class="empty-message">暂无待办事项</p>'
    )
  )
  FROM api.todos t
  WHERE created_by = current_setting('jwt.claims.user_id')::integer;
$$ LANGUAGE sql;

-- 添加待办事项函数
CREATE OR REPLACE FUNCTION api.add_todo(_task text, _priority integer, _due_date date) 
RETURNS "text/html" AS $$
BEGIN
  INSERT INTO api.todos (task, priority, due_date, created_by)
  VALUES (_task, _priority, _due_date, current_setting('jwt.claims.user_id')::integer);
  
  RETURN api.html_all_todos();
END;
$$ LANGUAGE plpgsql;

-- 切换待办事项状态函数
CREATE OR REPLACE FUNCTION api.toggle_todo(id integer) 
RETURNS "text/html" AS $$
DECLARE
  todo api.todos;
BEGIN
  UPDATE api.todos
  SET done = NOT done
  WHERE id = $1
  AND created_by = current_setting('jwt.claims.user_id')::integer
  RETURNING * INTO todo;
  
  RETURN api.html_todo(todo);
END;
$$ LANGUAGE plpgsql;

-- 删除待办事项函数
CREATE OR REPLACE FUNCTION api.delete_todo(id integer) 
RETURNS void AS $$
BEGIN
  DELETE FROM api.todos
  WHERE id = $1
  AND created_by = current_setting('jwt.claims.user_id')::integer;
END;
$$ LANGUAGE plpgsql;

-- 编辑待办事项表单函数
CREATE OR REPLACE FUNCTION api.edit_todo_form(id integer) 
RETURNS "text/html" AS $$
  SELECT format($html$
    <div class="todo-edit-form" id="todo-%1$s">
      <form hx-put="/rpc/update_todo" hx-target="#todo-%1$s" hx-swap="outerHTML">
        <input type="hidden" name="id" value="%1$s">
        <div class="form-group">
          <label for="task">任务:</label>
          <input type="text" name="task" value="%2$s" required>
        </div>
        <div class="form-group">
          <label for="priority">优先级:</label>
          <select name="priority" required>
            <option value="1" %3$s>低</option>
            <option value="2" %4$s>中</option>
            <option value="3" %5$s>高</option>
          </select>
        </div>
        <div class="form-group">
          <label for="due_date">截止日期:</label>
          <input type="date" name="due_date" value="%6$s" required>
        </div>
        <div class="form-actions">
          <button type="submit">保存</button>
          <button type="button" hx-get="/rpc/get_todo?id=%1$s" hx-target="#todo-%1$s" hx-swap="outerHTML">取消</button>
        </div>
      </form>
    </div>
  $html$,
  t.id,
  api.sanitize_html(t.task),
  CASE WHEN t.priority = 1 THEN 'selected' ELSE '' END,
  CASE WHEN t.priority = 2 THEN 'selected' ELSE '' END,
  CASE WHEN t.priority = 3 THEN 'selected' ELSE '' END,
  t.due_date::text
  )
  FROM api.todos t
  WHERE t.id = $1
  AND t.created_by = current_setting('jwt.claims.user_id')::integer;
$$ LANGUAGE sql;

-- 更新待办事项函数
CREATE OR REPLACE FUNCTION api.update_todo(id integer, task text, priority integer, due_date date) 
RETURNS "text/html" AS $$
BEGIN
  UPDATE api.todos
  SET task = $2, priority = $3, due_date = $4
  WHERE id = $1
  AND created_by = current_setting('jwt.claims.user_id')::integer;
  
  RETURN api.html_todo((SELECT t FROM api.todos t WHERE id = $1));
END;
$$ LANGUAGE plpgsql;

-- 主页面HTML生成函数
CREATE OR REPLACE FUNCTION api.todo_app() RETURNS "text/html" AS $$
  SELECT $html$
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>PostgREST + HTMX 待办事项应用</title>
      <style>
        /* CSS样式省略 */
        .container { max-width: 800px; margin: 0 auto; padding: 20px; }
        .todo-list { margin-top: 20px; }
        .todo-item { padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px; }
        .done .todo-text { text-decoration: line-through; color: #666; }
        /* 更多样式... */
      </style>
      <script src="https://cdn.jsdelivr.net/npm/htmx.org@1.9.6"></script>
    </head>
    <body>
      <div class="container">
        <h1>我的待办事项</h1>
        
        <div class="add-todo-form">
          <h2>添加新任务</h2>
          <form hx-post="/rpc/add_todo" hx-target="#todos-container" hx-swap="innerHTML">
            <div class="form-group">
              <label for="task">任务描述:</label>
              <input type="text" name="task" required placeholder="输入任务描述...">
            </div>
            <div class="form-row">
              <div class="form-group">
                <label for="priority">优先级:</label>
                <select name="priority" required>
                  <option value="1">低</option>
                  <option value="2" selected>中</option>
                  <option value="3">高</option>
                </select>
              </div>
              <div class="form-group">
                <label for="due_date">截止日期:</label>
                <input type="date" name="due_date" required>
              </div>
            </div>
            <button type="submit">添加</button>
          </form>
        </div>
        
        <div class="todo-filters">
          <button hx-get="/rpc/filter_todos?filter=all" hx-target="#todos-container">全部</button>
          <button hx-get="/rpc/filter_todos?filter=active" hx-target="#todos-container">未完成</button>
          <button hx-get="/rpc/filter_todos?filter=done" hx-target="#todos-container">已完成</button>
        </div>
        
        <div id="todos-container">
          <!-- 待办事项列表将通过HTMX动态加载 -->
          <div hx-get="/rpc/html_all_todos" hx-trigger="load"></div>
        </div>
      </div>
    </body>
    </html>
  $html$;
$$ LANGUAGE sql;

前端访问

直接通过PostgREST访问生成的HTML页面:

GET /rpc/todo_app

这个应用实现了完整的CRUD功能,包括:

  • 查看所有待办事项
  • 添加新的待办事项
  • 标记待办事项为完成/未完成
  • 编辑待办事项
  • 删除待办事项
  • 过滤待办事项(全部/未完成/已完成)

所有交互都通过HTMX实现,无需编写额外的JavaScript代码,充分展示了PostgREST作为全栈Web开发平台的潜力。

常见问题与解决方案

N+1查询问题

问题描述:当使用资源嵌入时,PostgREST可能会生成多个数据库查询,导致性能问题。

解决方案:使用计算字段预加载关联数据

-- 创建预加载关联数据的计算字段
CREATE OR REPLACE FUNCTION api.author_with_books(author_id integer)
RETURNS jsonb AS $$
  SELECT jsonb_build_object(
    'id', a.id,
    'name', a.name,
    'email', a.email,
    'books', jsonb_agg(jsonb_build_object(
      'id', b.id,
      'title', b.title,
      'published_date', b.published_date
    ))
  )
  FROM api.authors a
  LEFT JOIN api.books b ON a.id = b.author_id
  WHERE a.id = author_id
  GROUP BY a.id;
$$ LANGUAGE sql STABLE;

-- 创建API端点
CREATE OR REPLACE FUNCTION api.authors_with_books()
RETURNS SETOF jsonb AS $$
  SELECT api.author_with_books(id) FROM api.authors;
$$ LANGUAGE sql;

通过API访问:

GET /rpc/authors_with_books

处理大型结果集

问题描述:返回大量数据时,API响应缓慢且消耗大量资源。

解决方案:实现高效分页和游标分页

# 标准分页
GET /todos?limit=20&offset=40

# 使用游标分页(需要有序字段)
GET /todos?order=id.desc&limit=20&id=lt.100

实现基于游标的分页函数:

CREATE OR REPLACE FUNCTION api.paginated_todos(
  cursor integer DEFAULT NULL,
  page_size integer DEFAULT 20,
  order_direction text DEFAULT 'desc'
) RETURNS TABLE(
  data jsonb[],
  next_cursor integer,
  prev_cursor integer
) AS $$
DECLARE
  order_col text := 'id';
BEGIN
  -- 获取数据
  IF order_direction = 'desc' THEN
    IF cursor IS NULL THEN
      -- 第一页
      RETURN QUERY
      SELECT 
        array_agg(jsonb_build_object(
          'id', t.id,
          'task', t.task,
          'done', t.done,
          'priority', t.priority,
          'due_date', t.due_date
        )) AS data,
        MIN(t.id) AS next_cursor,
        NULL AS prev_cursor
      FROM (
        SELECT * FROM api.todos 
        ORDER BY id DESC 
        LIMIT page_size
      ) t;
    ELSE
      -- 后续页
      RETURN QUERY
      SELECT 
        array_agg(jsonb_build_object(
          'id', t.id,
          'task', t.task,
          'done', t.done,
          'priority', t.priority,
          'due_date', t.due_date
        )) AS data,
        MIN(t.id) AS next_cursor,
        MAX(t.id) AS prev_cursor
      FROM (
        SELECT * FROM api.todos 
        WHERE id < cursor
        ORDER BY id DESC 
        LIMIT page_size
      ) t;
    END IF;
  ELSE
    -- 升序处理,省略...
  END IF;
END;
$$ LANGUAGE plpgsql;

通过API访问:

GET /rpc/paginated_todos?page_size=20
GET /rpc/paginated_todos?cursor=100&page_size=20

事务处理

问题描述:需要确保多个操作的原子性。

解决方案:使用数据库事务函数

CREATE OR REPLACE FUNCTION api.transfer_funds(
  from_account_id integer,
  to_account_id integer,
  amount numeric,
  description text
) RETURNS jsonb AS $$
DECLARE
  from_balance numeric;
  result jsonb;
BEGIN
  -- 开始事务
  -- 检查余额
  SELECT balance INTO from_balance 
  FROM api.accounts 
  WHERE id = from_account_id 
  FOR UPDATE;
  
  IF from_balance < amount THEN
    RAISE EXCEPTION '余额不足';
  END IF;
  
  -- 更新转出账户
  UPDATE api.accounts 
  SET balance = balance - amount 
  WHERE id = from_account_id;
  
  -- 更新转入账户
  UPDATE api.accounts 
  SET balance = balance + amount 
  WHERE id = to_account_id;
  
  -- 记录交易
  INSERT INTO api.transactions (from_account_id, to_account_id, amount, description)
  VALUES (from_account_id, to_account_id, amount, description)
  RETURNING jsonb_build_object(
    'id', id,
    'from_account_id', from_account_id,
    'to_account_id', to_account_id,
    'amount', amount,
    'description', description,
    'created_at', created_at
  ) INTO result;
  
  RETURN result;
END;
$$ LANGUAGE plpgsql;

通过API调用事务函数:

POST /rpc/transfer_funds
{
  "from_account_id": 1,
  "to_account_id": 2,
  "amount": 100.50,
  "description": "转账测试"
}

性能监控与调优

启用PostgREST metrics

# postgrest.conf
db-extra-search-path = "extensions,utils,api"  # 额外搜索路径
server-metrics = true                           # 启用metrics

访问metrics端点:

GET /metrics

关键性能指标

需要关注的关键性能指标:

  1. 请求吞吐量:单位时间内处理的请求数
  2. 响应延迟:平均响应时间、P95/P99响应时间
  3. 错误率:失败请求的百分比
  4. 数据库连接使用率:连接池的使用情况
  5. 查询执行时间:数据库查询的耗时分布

性能调优案例

案例:API响应缓慢,数据库负载高

诊断步骤:

  1. 查看PostgREST metrics,发现平均响应时间长
  2. 检查PostgreSQL慢查询日志,发现特定查询执行效率低
  3. 使用EXPLAIN分析查询计划,发现缺少适当的索引

解决方案:

-- 添加缺失的索引
CREATE INDEX idx_books_author_id_published_date ON api.books(author_id, published_date);

-- 优化查询字段
-- 将:
-- GET /books?select=id,title,author{id,name,email,biography}
-- 改为:
-- GET /books?select=id,title,author:author_id{id,name}

结论与展望

PostgREST作为一个革命性的API开发工具,彻底改变了传统API开发的模式。通过将数据库直接暴露为RESTful API,它消除了大量重复劳动,同时提供了卓越的性能和安全性。

本文深入探讨了PostgREST的高级特性,包括架构深度剖析、高级查询技巧、权限控制、前端集成、问题解决方案等方面。这些专家级经验将帮助你充分发挥PostgREST的潜力,构建高效、安全、易于维护的API服务。

未来,随着PostgreSQL的不断发展和PostgREST社区的持续活跃,我们可以期待更多令人兴奋的特性和改进。无论是作为独立的API服务器,还是与其他工具结合使用,PostgREST都将在现代Web开发中扮演越来越重要的角色。

现在,是时候将这些高级技巧应用到你的项目中,体验PostgREST带来的开发效率提升和性能优势了。祝你在API开发的道路上越走越远!

附录:有用的资源

  1. 官方文档:https://postgrest.org/
  2. GitHub仓库:https://gitcode.com/GitHub_Trending/po/postgrest
  3. 社区论坛:https://discourse.postgrest.org/
  4. 学习资源
    • PostgREST官方教程
    • "PostgREST in Action" (书籍)
    • 各种社区贡献的示例项目和教程

希望本文能帮助你掌握PostgREST的高级用法。如果你有任何问题或建议,请在评论区留言。别忘了点赞、收藏、关注,以便获取更多类似的技术分享!

下期预告:PostgREST与实时数据处理,探索如何利用PostgREST构建实时更新的Web应用。

【免费下载链接】postgrest PostgREST是一个开源的RESTful API服务器,用于将PostgreSQL数据库暴露为RESTful API。 - 功能:RESTful API服务器;PostgreSQL数据库;RESTful API。 - 特点:易于使用;轻量级;支持多种编程语言;高性能。 【免费下载链接】postgrest 项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值