Nhost 后端示例深度解析:基于 tasks / attachments 数据模型的权限与函数设计
2026/9/16 16:20:42 网站建设 项目流程

Nhost 后端示例深度解析:基于 tasks / attachments 数据模型的权限与函数设计

【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost

本文以仓库中的 examples/guides/backend/README.md 为骨架,逐层拆解一个"小而完整"的 Nhost 后端示例:它用tasksattachments两张表演示了典型的按所有者(ownership)隔离的权限模型,并附带一个echo函数演示 Serverless Functions 的基本形态。读完本文,你将掌握 Nhost 中表结构迁移(Migrations)、Hasura 元数据权限(Permissions)、存储(Storage)文件归属,以及函数(Functions)从定义到运行验证的完整实战链路,并能直接对照仓库源码验证每个环节的实现细节。

示例后端的设计定位

该示例位于 examples/guides/backend 目录,其 README 明确说明它是一个"非常简单"的 Nhost 后端,用途是演示如何配合各种正在试验中的 SDK 使用 Nhost。也就是说,它是整个examples/guides系列(如 react-query 指南、react-apollo、react-urql、codegen-nhost 等)共用的后端底座,前端指南中的查询、变更、订阅全部围绕它展开。

整个示例后端的组成非常清晰,只有三大部分:

  1. 数据库 Schematasks表 +attachments表;
  2. 权限(Permissions):面向user角色的按归属隔离读写规则;
  3. 函数(Functions):一个返回请求信息的echo函数。

目录结构在仓库中完整可查:

examples/guides/backend/ ├── README.md # 本文档(示例说明) ├── Makefile # dev-env-up / dev-env-down 快捷命令 ├── env-up.sh # 启动脚本(nhost up) ├── functions/ # 函数目录(package.json / tsconfig.json) └── nhost/ ├── config.yaml # Nhost 配置(version: 3) ├── nhost.toml # 完整配置:hasura / auth / storage / postgres / functions ├── metadata/ # Hasura 元数据(权限、关系、自定义字段) └── migrations/ # 数据库迁移(SQL)

数据库 Schema:两张表构建"任务 + 附件"模型

原文档用两个清单定义了数据模型,以下逐一对照仓库中的迁移 SQL 进行还原。

tasks 表

原文档定义的tasks表包含 7 个字段:

  • id(UUID)
  • created_at(Timestamp)
  • updated_at(Timestamp)
  • user_id(外键指向auth.users.id
  • title(Text)
  • description(Text)
  • completed(Boolean)

对应的真实建表语句位于 migrations/default/1738758216166_create_table_public_tasks/up.sql:

CREATE TABLE "public"."tasks" ( "id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "title" text NOT NULL, "description" text NOT NULL, "completed" boolean NOT NULL DEFAULT false, "user_id" uuid NOT NULL, PRIMARY KEY ("id"), FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON UPDATE cascade ON DELETE cascade );

这段 SQL 有 4 个值得注意的实现细节:

  • id默认gen_random_uuid():主键由 PostgreSQL 自动生成 UUID,无需应用层干预;该函数依赖pgcrypto扩展,因此迁移文件末尾有CREATE EXTENSION IF NOT EXISTS pgcrypto;
  • 时间戳默认值created_atupdated_at都默认为now(),保证插入即有时间。
  • 外键指向 auth schemauser_id引用的是 Nhost Auth 服务管理的auth.users表,这是"登录用户"与"业务数据"之间建立归属关系的标准做法。
  • updated_at自动刷新:迁移中附带了一个触发器set_public_tasks_updated_at,配合函数set_current_timestamp_updated_at(),在每次UPDATE时自动把updated_at刷为当前时间——这也是文档中updated_at(Timestamp)字段"可写但通常不用手写"的原因。

attachments 表

原文档定义的attachments表是典型的多对多关联表

  • task_id(外键指向tasks.id
  • file_id(外键指向storage.files.id

对应的建表语句位于 migrations/default/1738758597255_create_table_public_attachments/up.sql:

CREATE TABLE "public"."attachments" ( "task_id" uuid NOT NULL, "file_id" uuid NOT NULL, PRIMARY KEY ("task_id","file_id"), FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("file_id") REFERENCES "storage"."files"("id") ON UPDATE restrict ON DELETE restrict );

从源码可以看到几点超出原文档的描述:

  • 联合主键(task_id, file_id)构成复合主键,天然防止同一文件被重复挂到同一任务上。
  • 关联的是 Nhost 的存储服务表storage.files由 Nhost Storage 服务管理(对应 nhost.toml 中的[storage] version = '0.7.2'),该表本身就带uploaded_by_user_id等归属字段,见 storage_files.yaml。
  • 删除策略有区分task_idON DELETE CASCADE(任务删了附件记录跟着删),file_idON DELETE RESTRICT(文件被引用时不允许删除),避免悬空引用。

元数据层的"自定义命名"

Hasura 元数据 public_tasks.yaml 与 public_attachments.yaml 中,对列做了 GraphQL 命名映射:

  • taskscreated_at → createdAtupdated_at → updatedAtuser_id → userID
  • attachmentstask_id → taskID,并为file_idtask_id声明了两个对象关系filetask

也就是说,数据库列名保持 snake_case,而 GraphQL API 对外暴露 camelCase 字段,前端开发者写查询时用createdAtuserIDtaskID,这是 Nhost(基于 Hasura)元数据配置的典型玩法。

权限模型:让用户只能操作自己的数据

原文档给出了三段权限描述,这是本示例的核心知识点,也是文档中最具实战价值的部分:

  • tasksuser角色可以 insert/select/update 自己拥有的任务,归属由user_id列追踪,且插入时从会话自动设置该列;
  • attachmentsuser角色可以 insert/select/delete 自己拥有的任务与文件的附件;
  • storage.filesuser角色可以 insert/select/delete 自己拥有的文件。

下面分别对照元数据文件还原其在 Hasura 中的真实写法。

tasks:插入时自动写入 user_id

public_tasks.yaml 中insert_permissions的定义如下:

insert_permissions: - role: user permission: check: user_id: _eq: X-Hasura-User-Id set: user_id: x-hasura-User-Id columns: - completed - description - title

关键机制是set段:用户插入时无需也不能自己指定user_id,Hasura 会从会话变量x-hasura-User-Id自动填充——这正是原文档所说"Ownership is tracked by theuser_idcolumn which is set automatically on insert from the session"。而check又确保即使有人绕过前端提交user_id,也会被校验拒绝(必须等于会话中的用户 ID)。columns限制了用户可写的列只有title/description/completed

其余三类权限同样围绕归属过滤:

select_permissions: - role: user permission: columns: [completed, description, title, created_at, updated_at, id, user_id] filter: user_id: { _eq: X-Hasura-User-Id } update_permissions: - role: user permission: columns: [completed, description, title] filter: {} # 先按此条件定位可更新的行 check: user_id: { _eq: X-Hasura-User-Id } # 更新后仍必须属于自己的行 delete_permissions: - role: user permission: filter: user_id: { _eq: X-Hasura-User-Id }

实现要点:select/delete通过filter把查询范围锁定在当前用户;update额外有check,保证更新后行依然归属当前用户(防止把任务"改给"别人)。

attachments:跨表归属校验

public_attachments.yaml 的权限比tasks复杂——它要同时校验"任务是我的"且"文件是我的"。实现方式是借助前面声明的对象关系filetask,做嵌套过滤:

insert_permissions: - role: user permission: check: _and: - file: { uploaded_by_user_id: { _eq: X-Hasura-User-Id } } - task: { user_id: { _eq: X-Hasura-User-Id } } columns: [file_id, task_id]

selectdelete使用同样的_and组合过滤:

select_permissions: - role: user permission: columns: [file_id, task_id] filter: _and: - file: { uploaded_by_user_id: { _eq: X-Hasura-User-Id } } - task: { user_id: { _eq: X-Hasura-User-Id } } delete_permissions: - role: user permission: filter: _and: - file: { uploaded_by_user_id: { _eq: X-Hasura-User-Id } } - task: { user_id: { _eq: X-Hasura-User-Id } }

这意味着:用户只能为"自己的任务"挂"自己上传的文件",以及只能读取/删除这样的关联记录——原文档中"insert/select/delete attachments for tasks and files that they own"的精确含义在此落地。

storage.files:文件归属的存储侧控制

storage_files.yaml 对 Nhost Storage 的files表做了同构控制:插入时通过set: { uploaded_by_user_id: x-hasura-User-Id }自动记录上传者,select/update/delete全部以uploaded_by_user_id: { _eq: X-Hasura-User-Id }过滤,从而形成"我的文件只能我读写删"的闭环。

函数:echo —— 最简单的 Serverless Function

原文档对函数的描述只有一句:"一个名为echosimple函数,会原样返回一些请求信息"。虽然examples/guides/backend/functions/目录下目前只有脚手架文件(package.json),但同仓库的 examples/demos/backend/functions/echo.ts 给出了该函数的标准实现,可以直接作为参考:

import process from "node:process"; import type { Request, Response } from "express"; import cors from "cors"; const corsMiddleware = cors(); export default (req: Request, res: Response) => { corsMiddleware(req, res, () => { res.status(200).json({ headers: req.headers, query: req.query, body: req.body, method: req.method, node: process.version, arch: process.arch, invocationId: req.invocationId, }); }); };

这个实现完整呼应了"return back some request information"的语义:它把headers、query、body、method以及 Node 运行时版本、架构、调用 ID(invocationId)打包成 JSON 返回。同时可以看到 Nhost Functions 的两个约定:

  1. 默认导出:每个函数文件通过export default导出一个 Express 风格的处理函数(req, res) => void
  2. CORS 中间件:示例统一包裹cors()中间件,便于浏览器端直接调用。

函数运行时由 nhost.toml 的[functions.node] version = 22指定(Node.js 22),这是本地开发环境实际使用的运行时版本。

从零跑起这个后端

仓库提供了两条启动路径,均在examples/guides/backend目录下:

方式一:Makefile 快捷命令(推荐)

make dev-env-up # 等价于执行 ./env-up.sh make dev-env-down # 等价于 nhost down --volumes

方式二:直接执行脚本

env-up.sh 的内容如下:

#!/bin/sh # if .secrets file doesn't exist, cp .secrets.example .secrets if [ ! -f .secrets ]; then cp .secrets.example .secrets fi nhost up

脚本做了两件事:若本地缺少.secrets文件则从.secrets.example复制(nhost up会读取其中的密钥占位符),然后执行nhost up拉起完整本地环境。停止时使用nhost down --volumes可连数据卷一并清理,保证可重复重建。

启动后,Nhost CLI 会根据 nhost.toml 编排全部服务,该文件中的关键配置包括:

  • Hasuraversion = 'v2.46.0-ce',开发模式开启(devMode = true)、Console 可用(enableConsole = true),JWT 密钥与 Admin Secret 通过{{ secrets.XXX }}占位符注入;
  • Authversion = '0.41.1',默认角色user[auth.user.roles] default = 'user'),允许userme两种角色;邮箱密码登录启用、emailVerificationRequired = false、密码最短 9 位,并有邮箱/短信/暴力破解/注册/全局五档限流配置;
  • Storageversion = '0.7.2'
  • Postgresversion = '14.20-20251217-1',存储容量 1GB;
  • 重定向白名单allowedUrlshttp://localhost:5173exp://192.168.1.103:8081,兼顾 Web 与 React Native 开发。

与其他 SDK 指南的衔接

这个后端示例的价值在于"被复用"。examples/guides系列的其他指南都直接基于它演示前端集成,最典型的是 react-query/README.md:

  • 它演示了用@nhost/nhost-js创建客户端(createClient({ region: "local", subdomain: "local" }))并注入会话;
  • 通过useAuthenticatedFetcher封装带鉴权的 GraphQL 请求;
  • 使用 GraphQL CodeGen 生成类型安全的 React Query hooks,在组件中调用useGetNinjaTurtlesWithCommentsQuery/useAddCommentMutation

也就是说,tasks/attachments是"后端能力底座",而 ninja_turtles / comments 等是演示用扩展表。需要说明的是,后端目录的迁移中还包含几张原文档未提及的扩展表,如 ninja_turtles、comments(其权限同样遵循"user_id自动填充、只能改删自己的评论"的模式,见 public_comments.yaml)以及带种子数据的 movies 表。它们不影响本文所述核心模型,仅作为不同指南演示时的附加素材。

小结

通过这个示例后端,可以完整看到 Nhost "开箱即用的后端" 的三层结构:

层次载体本示例中的体现
数据层migrations/*.sqltasksattachments及扩展表
权限层metadata/.../tables/*.yaml基于X-Hasura-User-Id的按归属过滤与自动填充
计算层functions/echo请求信息回显函数

原文档"非常简单的后端"背后,隐藏着 Nhost 生产级权限体系的标准范式:外键指向auth.users建立归属、Hasura 元数据用会话变量约束读写、Storage 文件同样纳入归属模型、函数以 Express 风格默认导出。对照 迁移 SQL、元数据 与 echo 函数实现,即可把这套模式完整迁移到自己的项目中。

【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询