关于 hasura-graphql-engine
面向使用 Hasura GraphQL Engine 的开发者,介绍从数据库生成 GraphQL API,并配置权限、认证和事件驱动逻辑。内容还涵盖 Actions、远程 Schema 与实时订阅。
主要功能
- 01从 PostgreSQL 生成 GraphQL API
- 02按角色及行列配置权限
- 03集成 JWT 或 Webhook 认证
- 04数据库事件触发 Webhook
- 05通过 Actions 扩展业务逻辑
- 06合并远程 Schema 并支持订阅
适用场景
- 01为现有数据库构建 GraphQL API
- 02实现多租户数据访问控制
- 03在数据库变更时触发外部流程
- 04整合多个 GraphQL 服务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ manutej/ luxor-claude-marketplace --skill hasura-graphql-engine安装后在 Claude Code 中输入/hasura-graphql-engine确认已加载。
附带文件EXAMPLES.mdREADME.md
详细介绍
Hasura GraphQL Engine 精通指南
一份全面的技能指南,助你使用 Hasura 构建可用于生产环境的 GraphQL API。掌握即时 API 生成、细粒度权限、身份验证集成、事件驱动架构、自定义业务逻辑,以及面向现代应用的远程 Schema 拼接。
何时使用此技能
在以下场景中使用 Hasura GraphQL Engine:
- 无需编写后端代码即可快速构建 GraphQL API
- 需要根据现有 PostgreSQL 数据库即时生成 CRUD API
- 实现细粒度的行级和列级安全控制
- 使用 GraphQL 订阅构建实时应用
- 集成多个数据源(数据库、REST API、GraphQL 服务)
- 使用数据库触发器构建事件驱动架构
- 通过 Actions 使用自定义业务逻辑扩展 GraphQL
- 在 API 层实现身份验证和授权
- 快速构建管理面板、仪表盘或内部工具
- 从 REST 迁移到 GraphQL,而无需重写后端
- 需要可用于生产环境的功能(缓存、速率限制、监控)
- 构建支持基于角色访问控制的多租户 SaaS 应用
核心概念
即时生成 GraphQL API
Hasura 的核心价值在于根据数据库 Schema 自动生成 GraphQL API:
- 表跟踪:将 PostgreSQL 表接入 Hasura,即可即时获得查询、变更和订阅
- 关系检测:自动将外键关系推断为 GraphQL 连接
- 类型安全:数据库 Schema 会直接转换为 GraphQL 类型
- 零代码:无需编写解析器、配置 ORM 或处理样板代码
- 默认支持实时更新:每个查询都会自动配备对应的订阅
工作原理:
- 将 Hasura 连接到 PostgreSQL 数据库
- 在 Hasura Console 中跟踪表
- GraphQL API 会立即提供以下操作:
query- 获取数据,并支持筛选、排序和分页mutation- 插入、更新和删除操作subscription- 通过 WebSockets 实时更新数据
元数据驱动架构
Hasura 采用元数据驱动,而非代码驱动的架构:
- 元数据:定义 API 的 JSON/YAML 配置
- 声明式:定义所需内容,而非实现方式
- 版本控制:元数据文件可以提交到 Git
- CLI 迁移:Hasura CLI 管理元数据和迁移
- 程序化控制:通过 Metadata API 实现自动化
主要元数据组件:
- 表跟踪和关系
- 权限规则
- 远程 Schema
- Actions
- 事件触发器
- 自定义函数
权限系统
Hasura 的权限系统是其最强大的功能,可实现细粒度的访问控制:
- 基于角色:针对每种 GraphQL 操作和角色定义权限
- 行级安全:控制用户可以访问哪些行
- 列级安全:对特定角色隐藏敏感列
- 会话变量:根据 JWT 声明或 Webhook 数据动态设置权限
- 检查约束:用于确定访问权限的布尔表达式
权限类型:
select- 读取权限insert- 创建权限update- 修改权限delete- 删除权限
身份验证集成
Hasura 将身份验证委托给你的身份验证服务,但负责处理授权:
- JWT 模式:验证包含用户声明的 JWT 令牌
- Webhook 模式:调用 Webhook 获取会话变量
- 会话变量:
x-hasura-role、x-hasura-user-id、自定义声明 - 多提供商支持:支持 Auth0、Firebase、Cognito 和自定义身份验证
身份验证流程:
- 用户通过你的身份验证服务(Auth0、Firebase 或自定义服务)进行身份验证
- 身份验证服务签发包含 Hasura 声明的 JWT
- 客户端在 Authorization 标头中发送 JWT
- Hasura 验证 JWT 并提取会话变量
- 使用会话变量评估权限
- 在相应的访问控制下执行 GraphQL 查询
事件触发器
事件触发器通过在数据库发生更改时调用 Webhook,支持事件驱动架构:
- 数据库事件:INSERT、UPDATE、DELETE 触发器
- 可靠交付:至少一次交付,并支持重试
- 负载:JSON 格式的旧行和新行数据
- 异步处理:长时间运行的任务、外部集成
- 使用场景:发送邮件、同步到 Elasticsearch、更新缓存、触发工作流
操作
操作通过自定义业务逻辑扩展 Hasura:
- 自定义变更:定义由你的代码处理的 GraphQL 变更
- 自定义查询:添加数据库访问之外的自定义查询逻辑
- REST 集成:从 GraphQL 调用 REST API
- 类型安全:在 GraphQL SDL 中定义输入/输出类型
- 处理程序:你的 HTTP 端点接收 GraphQL 变量
常见使用场景:
- 支付处理
- 复杂验证
- 调用第三方 API
- 自定义算法
- 文件上传
- 发送邮件
远程 Schema
远程 Schema通过合并外部 GraphQL API 实现 Schema 拼接:
- Schema 拼接:统一多个 GraphQL 服务
- 类型扩展:使用远程 Schema 中的字段扩展类型
- 权限:对远程 Schema 应用基于角色的权限
- 命名空间:隔离远程 Schema,避免冲突
- 使用场景:微服务、旧版 GraphQL API、第三方服务
实时订阅
Hasura 提供原生 GraphQL 订阅:
- 实时查询:数据发生更改时自动推送更新
- WebSocket 协议:高效的双向通信
- 多路复用:为大量并发客户端优化订阅
- 筛选:订阅特定的数据子集
- 轮询回退:为受限网络提供基于 HTTP 的流式传输
权限系统深入介绍
行级安全
行级安全使用布尔检查表达式筛选可访问的行:
示例:用户只能查看自己的数据
{
"check": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
示例:多租户数据隔离
{
"check": {
"tenant_id": {
"_eq": "X-Hasura-Tenant-Id"
}
}
}
示例:复杂访问规则
{
"check": {
"_or": [
{
"user_id": {
"_eq": "X-Hasura-User-Id"
}
},
{
"is_public": {
"_eq": true
}
}
]
}
}
列级安全
控制每个角色可见的列:
示例:隐藏敏感的用户字段
select:
columns:
- id
- username
- email
# password_hash is hidden
# created_at is hidden
示例:为不同角色提供不同视图
# Admin role sees all columns
select:
columns: "*"
# User role sees limited columns
select:
columns:
- id
- username
- profile_picture
插入权限
控制可插入的数据:
示例:从会话中设置 user_id
{
"check": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
},
"set": {
"user_id": "X-Hasura-User-Id"
}
}
示例:插入前验证所有权
{
"check": {
"project": {
"owner_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
}
更新权限
控制可更新的行以及可设置的值:
示例:只能更新自己的数据
{
"filter": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
},
"check": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
},
"set": {
"updated_at": "now()"
}
}
筛选条件:可选中以进行更新的行
检查条件:更新完成后的验证
设置:自动设置列值
删除权限
控制可删除的行:
示例:只能删除自己的数据
{
"filter": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
身份验证集成
JWT 模式配置
配置 Hasura 以验证 JWT 令牌:
环境变量:
HASURA_GRAPHQL_JWT_SECRET='{
"type": "RS256",
"key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
}'
JWT 声明结构:
{
"sub": "user123",
"iat": 1633024800,
"exp": 1633111200,
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user", "admin"],
"x-hasura-user-id": "user123",
"x-hasura-org-id": "org456"
}
}
必需的声明:
x-hasura-default-role:请求中未指定时使用的默认角色x-hasura-allowed-roles:用户可采用的角色数组- 用于权限检查的自定义声明,例如
x-hasura-user-id
Auth0 集成
用于添加 Hasura 声明的 Auth0 规则:
function (user, context, callback) {
const namespace = "https://hasura.io/jwt/claims";
context.idToken[namespace] = {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.user_id
};
callback(null, user, context);
}
客户端用法:
const token = await auth0Client.getTokenSilently();
const response = await fetch('https://my-hasura.app/v1/graphql', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, variables })
});
Firebase 集成
Firebase 自定义声明:
// Admin SDK
const admin = require('firebase-admin');
async function setCustomClaims(uid) {
await admin.auth().setCustomUserClaims(uid, {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': uid
}
});
}
Webhook 模式
JWT 的替代方案——Hasura 会针对每个请求调用你的 Webhook:
Webhook 端点:
app.post('/auth-webhook', async (req, res) => {
const authHeader = req.headers['authorization'];
// Validate token (your logic)
const user = await validateToken(authHeader);
if (!user) {
return res.status(401).json({ message: 'Unauthorized' });
}
// Return session variables
res.json({
'X-Hasura-User-Id': user.id,
'X-Hasura-Role': user.role,
'X-Hasura-Org-Id': user.orgId
});
});
Hasura 配置:
HASURA_GRAPHQL_AUTH_HOOK=https://myapp.com/auth-webhook
HASURA_GRAPHQL_AUTH_HOOK_MODE=POST
事件触发器
创建事件触发器
事件触发器会在数据库发生更改时调用 Webhook:
通过控制台:
- 前往 Events 标签页
- 创建触发器
- 选择表和操作(INSERT、UPDATE、DELETE)
- 提供 Webhook URL
- 配置重试和超时设置
通过 Metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "create_event_trigger",
"args": {
"name": "user_created",
"table": {
"name": "users",
"schema": "public"
},
"webhook": "https://myapp.com/webhooks/user-created",
"insert": {
"columns": "*"
},
"retry_conf": {
"num_retries": 3,
"interval_sec": 10,
"timeout_sec": 60
}
}
}
事件负载结构
Webhook 会接收结构化的 JSON 负载:
{
"event": {
"session_variables": {
"x-hasura-role": "user",
"x-hasura-user-id": "123"
},
"op": "INSERT",
"data": {
"old": null,
"new": {
"id": "uuid-here",
"email": "[email protected]",
"created_at": "2025-01-15T10:30:00Z"
}
}
},
"created_at": "2025-01-15T10:30:00.123456Z",
"id": "event-id",
"trigger": {
"name": "user_created"
},
"table": {
"schema": "public",
"name": "users"
}
}
事件触发器用例
发送欢迎邮件:
// Webhook handler
app.post('/webhooks/user-created', async (req, res) => {
const { event } = req.body;
const user = event.data.new;
await sendEmail({
to: user.email,
subject: 'Welcome!',
template: 'welcome',
data: { name: user.name }
});
res.json({ success: true });
});
同步到 Elasticsearch:
app.post('/webhooks/product-updated', async (req, res) => {
const { event } = req.body;
const product = event.data.new;
await esClient.index({
index: 'products',
id: product.id,
body: product
});
res.json({ success: true });
});
触发工作流:
app.post('/webhooks/order-placed', async (req, res) => {
const { event } = req.body;
const order = event.data.new;
// Trigger payment processing
await processPayment(order.id);
// Notify inventory system
await updateInventory(order.items);
// Send confirmation email
await sendOrderConfirmation(order);
res.json({ success: true });
});
Actions(自定义业务逻辑)
定义 Actions
Actions 使用自定义 mutation 和 query 扩展 GraphQL:
GraphQL SDL 定义:
type Mutation {
login(username: String!, password: String!): LoginResponse
}
type LoginResponse {
accessToken: String!
refreshToken: String!
user: User!
}
Action 配置:
- name: login
definition:
kind: synchronous
handler: https://myapp.com/actions/login
forward_client_headers: true
headers:
- name: X-API-Key
value: secret-key
permissions:
- role: anonymous
Action 处理程序实现
Express.js 处理程序:
app.post('/actions/login', async (req, res) => {
const { input, session_variables } = req.body;
const { username, password } = input;
// Validate credentials
const user = await validateCredentials(username, password);
if (!user) {
return res.status(401).json({
message: 'Invalid credentials'
});
}
// Generate tokens
const accessToken = generateJWT(user);
const refreshToken = generateRefreshToken(user);
// Return action response
res.json({
accessToken,
refreshToken,
user: {
id: user.id,
username: user.username,
email: user.email
}
});
});
Action 权限
控制哪些角色可以执行 Actions:
通过 Metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "create_action_permission",
"args": {
"action": "insertAuthor",
"role": "user"
}
}
多个角色:
permissions:
- role: user
- role: admin
- role: anonymous
Action 类型
同步 Actions:
- 客户端等待响应
- 用于:登录、支付、验证
- 超时:可配置(默认 30s)
异步 Actions:
- 立即返回 Action ID
- 用于:长时间运行的任务、批处理
- 轮询完成状态或使用 Webhook
高级 Action 模式
支付处理:
type Mutation {
processPayment(
orderId: ID!
amount: Float!
currency: String!
paymentMethod: String!
): PaymentResponse
}
type PaymentResponse {
success: Boolean!
transactionId: String
error: String
}
文件上传:
type Mutation {
uploadFile(
file: String! # Base64 encoded
fileName: String!
mimeType: String!
): FileUploadResponse
}
type FileUploadResponse {
url: String!
fileId: ID!
}
复杂验证:
type Mutation {
createProject(
name: String!
description: String!
teamMembers: [ID!]!
): CreateProjectResponse
}
type CreateProjectResponse {
project: Project
errors: [ValidationError!]
}
type ValidationError {
field: String!
message: String!
}
远程 Schema
添加远程 Schema
集成外部 GraphQL API:
通过 Metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "add_remote_schema",
"args": {
"name": "auth0_api",
"definition": {
"url": "https://myapp.auth0.com/graphql",
"headers": [
{
"name": "Authorization",
"value": "Bearer ${AUTH0_TOKEN}"
}
],
"forward_client_headers": false,
"timeout_seconds": 60
}
}
}
自定义远程 Schema
自定义类型和字段名称以避免冲突:
{
"type": "add_remote_schema",
"args": {
"name": "countries",
"definition": {
"url": "https://countries.trevorblades.com/graphql",
"customization": {
"root_fields_namespace": "countries_api",
"type_names": {
"prefix": "Countries_",
"suffix": "_Type"
},
"field_names": [
{
"parent_type": "Country",
"prefix": "country_"
}
]
}
}
}
}
远程 Schema 权限
对远程 Schema 应用基于角色的权限:
原始远程 Schema:
type User {
id: ID!
first_name: String!
last_name: String!
phone: String!
email: String!
}
type Query {
user(id: ID!): User
get_users_by_name(first_name: String!, last_name: String): [User]
}
为 'public' 角色限制的 Schema:
type User {
first_name: String!
last_name: String!
}
type Query {
get_users_by_name(first_name: String!, last_name: String): [User]
}
通过 Metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "add_remote_schema_permissions",
"args": {
"remote_schema": "user_api",
"role": "public",
"definition": {
"schema": "type User { first_name: String! last_name: String! } type Query { get_users_by_name(first_name: String!, last_name: String): [User] }"
}
}
}
远程 Schema 参数预设
将会话变量自动注入远程 Schema 查询:
会话变量预设:
type Query {
get_user(id: ID! @preset(value: "x-hasura-user-id")): User
get_user_activities(user_id: ID!, limit: Int!): [Activity]
}
静态值预设:
type Query {
get_user(id: ID! @preset(value: "x-hasura-user-id")): User
get_user_activities(
user_id: ID!
limit: Int! @preset(value: 10)
): [Activity]
}
字面字符串(非会话变量):
type Query {
hello(text: String! @preset(value: "x-hasura-hello", static: true))
}
远程关系
将本地数据库表连接到远程 Schema:
示例:将本地客户关联到远程支付 API
SQL 表:
CREATE TABLE customer (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
远程 Schema(支付 API):
type Transaction {
customer_id: Int!
amount: Int!
time: String!
merchant: String!
}
type Query {
transactions(customer_id: String!, limit: Int): [Transaction]
}
远程关系定义:
- table:
name: customer
schema: public
remote_relationships:
- name: customer_transactions_history
definition:
remote_schema: payments
hasura_fields:
- id
remote_field:
transactions:
arguments:
customer_id: $id
带有远程关系的 GraphQL 查询:
query {
customer {
name
customer_transactions_history {
amount
time
}
}
}
生产环境部署
Docker 部署
docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:15
restart: always
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: postgrespassword
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
hasura:
image: hasura/graphql-engine:v2.36.0
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
restart: always
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_DEV_MODE: "true"
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256","key":"super-secret-jwt-signing-key-min-32-chars"}'
HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous
volumes:
db_data:
Kubernetes 部署
hasura-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hasura
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: hasura
template:
metadata:
labels:
app: hasura
spec:
containers:
- name: hasura
image: hasura/graphql-engine:v2.36.0
ports:
- containerPort: 8080
env:
- name: HASURA_GRAPHQL_DATABASE_URL
valueFrom:
secretKeyRef:
name: hasura-secrets
key: database-url
- name: HASURA_GRAPHQL_ADMIN_SECRET
valueFrom:
secretKeyRef:
name: hasura-secrets
key: admin-secret
- name: HASURA_GRAPHQL_JWT_SECRET
valueFrom:
secretKeyRef:
name: hasura-secrets
key: jwt-secret
- name: HASURA_GRAPHQL_ENABLE_CONSOLE
value: "false"
- name: HASURA_GRAPHQL_ENABLE_TELEMETRY
value: "false"
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: hasura
namespace: production
spec:
type: ClusterIP
selector:
app: hasura
ports:
- port: 80
targetPort: 8080
环境变量(生产环境)
生产环境必需配置:
# Database
HASURA_GRAPHQL_DATABASE_URL=postgres://user:password@host:5432/dbname
# Security
HASURA_GRAPHQL_ADMIN_SECRET=strong-random-secret
HASURA_GRAPHQL_JWT_SECRET='{"type":"RS256","key":"..."}'
HASURA_GRAPHQL_UNAUTHORIZED_ROLE=anonymous
# Performance
HASURA_GRAPHQL_ENABLE_CONSOLE=false
HASURA_GRAPHQL_DEV_MODE=false
HASURA_GRAPHQL_ENABLE_TELEMETRY=false
# Logging
HASURA_GRAPHQL_ENABLED_LOG_TYPES=startup,http-log,webhook-log,websocket-log
# Rate Limiting
HASURA_GRAPHQL_RATE_LIMIT_PER_MINUTE=1000
# CORS
HASURA_GRAPHQL_CORS_DOMAIN=https://myapp.com,https://admin.myapp.com
# Connections
HASURA_GRAPHQL_PG_CONNECTIONS=50
HASURA_GRAPHQL_PG_TIMEOUT=60
监控与可观测性
健康检查端点:
curl http://hasura:8080/healthz
# Returns: OK
Prometheus 指标:
HASURA_GRAPHQL_ENABLE_METRICS=true
HASURA_GRAPHQL_METRICS_SECRET=metrics-secret
# Access at: http://hasura:8080/v1/metrics
结构化日志:
HASURA_GRAPHQL_ENABLED_LOG_TYPES=startup,http-log,webhook-log,websocket-log,query-log
HASURA_GRAPHQL_LOG_LEVEL=info
APM 集成(Datadog 示例):
env:
- name: HASURA_GRAPHQL_ENABLE_APM
value: "true"
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: DD_SERVICE
value: "hasura-graphql"
- name: DD_ENV
value: "production"
迁移与版本控制
Hasura CLI 设置
初始化 Hasura 项目:
hasura init my-project --endpoint https://hasura.myapp.com
cd my-project
项目结构:
my-project/
├── config.yaml # Hasura CLI config
├── metadata/ # Metadata files
│ ├── databases/
│ │ └── default/
│ │ ├── tables/
│ │ │ ├── public_users.yaml
│ │ │ └── public_posts.yaml
│ ├── actions.yaml
│ ├── remote_schemas.yaml
│ └── version.yaml
└── migrations/ # Database migrations
└── default/
├── 1642531200000_create_users_table/
│ └── up.sql
└── 1642531300000_create_posts_table/
└── up.sql
创建迁移
通过控制台(自动跟踪):
# Start console with migration tracking
hasura console
# Make changes in console UI
# Migrations auto-generated in migrations/ folder
手动迁移:
# Create migration
hasura migrate create create_users_table --database-name default
# Edit generated SQL files
# migrations/default/{timestamp}_create_users_table/up.sql
# migrations/default/{timestamp}_create_users_table/down.sql
迁移示例(up.sql):
CREATE TABLE public.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
username TEXT NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_users_email ON public.users(email);
CREATE INDEX idx_users_username ON public.users(username);
迁移示例(down.sql):
DROP TABLE IF EXISTS public.users CASCADE;
应用迁移
应用迁移:
# Apply all pending migrations
hasura migrate apply --database-name default
# Apply specific version
hasura migrate apply --version 1642531200000 --database-name default
# Check migration status
hasura migrate status --database-name default
导出和导入 Metadata
导出 Metadata:
hasura metadata export
# Exports to metadata/ folder
应用 Metadata:
hasura metadata apply
# Applies metadata from metadata/ folder
重新加载 Metadata:
hasura metadata reload
CI/CD 集成
GitHub Actions 示例:
name: Deploy Hasura
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Hasura CLI
run: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
- name: Apply Migrations
env:
HASURA_GRAPHQL_ENDPOINT: ${{ secrets.HASURA_ENDPOINT }}
HASURA_GRAPHQL_ADMIN_SECRET: ${{ secrets.HASURA_ADMIN_SECRET }}
run: |
cd hasura
hasura migrate apply --database-name default
hasura metadata apply
- name: Reload Metadata
env:
HASURA_GRAPHQL_ENDPOINT: ${{ secrets.HASURA_ENDPOINT }}
HASURA_GRAPHQL_ADMIN_SECRET: ${{ secrets.HASURA_ADMIN_SECRET }}
run: |
cd hasura
hasura metadata reload
最佳实践
安全最佳实践
-
在生产环境中始终使用 ADMIN_SECRET
- 切勿在未进行身份验证的情况下暴露管理 API
- 定期轮换密钥
- 使用强随机密钥(至少 32 个字符)
-
实施适当的 JWT 验证
- 在生产环境中使用 RS256(非对称)
- 设置适当的令牌过期时间
- 验证 issuer 和 audience 声明
-
应用最小权限原则
- 从无访问权限开始,按需添加权限
- 对所有表使用行级安全性
- 对未授权角色隐藏敏感列
-
在生产环境中禁用控制台
HASURA_GRAPHQL_ENABLE_CONSOLE=false- 使用 Metadata 文件和 CLI 进行更改
-
启用速率限制
- 防范 DoS 攻击
- 必要时设置每个角色的限制
- 根据使用情况进行监控和调整
-
验证 Webhook 负载
- 为事件触发器使用 Webhook 密钥
- 验证 Action 输入
- 清理所有用户输入
性能最佳实践
-
优化数据库查询
- 创建合适的索引
- 使用数据库视图处理复杂查询
- 利用 PostgreSQL 性能调优
-
使用查询缓存
- 为开销较大的查询启用 @cached 指令
- 设置合适的 TTL 值
- 尽可能在 CDN 层进行缓存
-
限制查询深度和复杂度
- 设置最大查询深度限制
- 限制深层嵌套查询
- 对大型结果集使用分页
-
配置连接池
- 调优
HASURA_GRAPHQL_PG_CONNECTIONS - 监控连接使用情况
- 大规模部署时使用 PgBouncer
- 调优
-
优化订阅
- 使用订阅多路复用
- 限制每个客户端的并发订阅数
- 对时效性要求较低的数据考虑使用轮询
开发工作流最佳实践
-
对元数据进行版本控制
- 将 metadata/ 文件夹提交到 Git
- 对所有架构变更使用迁移
- 在 PR 中审查元数据变更
-
环境隔离
- 设置开发、预发布和生产环境
- 为每个环境使用不同的管理员密钥
- 先在预发布环境中测试迁移
-
测试策略
- 彻底测试权限
- 对事件触发器进行集成测试
- 独立测试 Action 处理程序
-
文档
- 记录自定义 Action 及其输入/输出
- 说明复杂的权限规则
- 为 API 使用者维护 API 文档
-
监控与告警
- 监控查询性能
- 对失败的 Webhook/事件触发器发出告警
- 跟踪错误率和延迟
常见模式与示例
模式 1:多租户 SaaS
架构:
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL
);
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
organization_id UUID NOT NULL REFERENCES organizations(id)
);
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
organization_id UUID NOT NULL REFERENCES organizations(id)
);
权限(users 表):
{
"filter": {
"organization_id": {
"_eq": "X-Hasura-Org-Id"
}
}
}
JWT 声明:
{
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user", "org-admin"],
"x-hasura-user-id": "user-uuid",
"x-hasura-org-id": "org-uuid"
}
}
模式 2:社交媒体应用
架构:
CREATE TABLE users (
id UUID PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
bio TEXT,
avatar_url TEXT
);
CREATE TABLE posts (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
content TEXT NOT NULL,
is_public BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE follows (
follower_id UUID REFERENCES users(id),
following_id UUID REFERENCES users(id),
PRIMARY KEY (follower_id, following_id)
);
CREATE TABLE likes (
user_id UUID REFERENCES users(id),
post_id UUID REFERENCES posts(id),
PRIMARY KEY (user_id, post_id)
);
权限:查看帖子(用户可以查看自己的帖子、公开帖子以及关注用户的帖子):
{
"filter": {
"_or": [
{
"user_id": {
"_eq": "X-Hasura-User-Id"
}
},
{
"is_public": {
"_eq": true
}
},
{
"user": {
"followers": {
"follower_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
}
]
}
}
模式 3:电子商务平台
架构:
CREATE TABLE products (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT NOT NULL,
is_active BOOLEAN DEFAULT true
);
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
status TEXT NOT NULL,
total DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE order_items (
id UUID PRIMARY KEY,
order_id UUID REFERENCES orders(id),
product_id UUID REFERENCES products(id),
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL
);
事件触发器:订单确认邮件
app.post('/webhooks/order-created', async (req, res) => {
const { event } = req.body;
const order = event.data.new;
// Fetch order details with items
const orderDetails = await fetchOrderDetails(order.id);
// Send confirmation email
await sendEmail({
to: orderDetails.user.email,
template: 'order-confirmation',
data: orderDetails
});
res.json({ success: true });
});
Action:处理付款
type Mutation {
processPayment(
orderId: ID!
paymentMethodId: String!
): PaymentResponse
}
type PaymentResponse {
success: Boolean!
orderId: ID!
transactionId: String
error: String
}
模式 4:实时协作
架构:
CREATE TABLE documents (
id UUID PRIMARY KEY,
title TEXT NOT NULL,
content JSONB NOT NULL DEFAULT '{}'::jsonb,
owner_id UUID NOT NULL,
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE document_collaborators (
document_id UUID REFERENCES documents(id),
user_id UUID NOT NULL,
permission TEXT NOT NULL, -- 'read', 'write', 'admin'
PRIMARY KEY (document_id, user_id)
);
权限:访问文档(自己的文档或协作的文档):
{
"filter": {
"_or": [
{
"owner_id": {
"_eq": "X-Hasura-User-Id"
}
},
{
"collaborators": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
]
}
}
GraphQL 订阅:实时更新
subscription DocumentUpdates($documentId: uuid!) {
documents_by_pk(id: $documentId) {
id
title
content
updated_at
}
}
模式 5:带分析功能的管理面板
用于分析的自定义 SQL 函数:
CREATE OR REPLACE FUNCTION get_user_stats(user_row users)
RETURNS TABLE (
total_posts INT,
total_followers INT,
total_following INT,
engagement_rate DECIMAL
) AS $$
SELECT
(SELECT COUNT(*) FROM posts WHERE user_id = user_row.id)::INT,
(SELECT COUNT(*) FROM follows WHERE following_id = user_row.id)::INT,
(SELECT COUNT(*) FROM follows WHERE follower_id = user_row.id)::INT,
(SELECT AVG(like_count) FROM posts WHERE user_id = user_row.id)::DECIMAL
$$ LANGUAGE SQL STABLE;
在 Hasura 中跟踪函数:
- function:
name: get_user_stats
schema: public
configuration:
custom_root_fields:
function: getUserStats
GraphQL 查询:
query UserWithStats {
users {
id
username
get_user_stats {
total_posts
total_followers
total_following
engagement_rate
}
}
}
故障排除
常见问题与解决方案
问题:JWT 验证失败
Solution:
1. Verify JWT secret configuration matches your auth provider
2. Check JWT contains required Hasura claims
3. Ensure claims are in correct namespace (https://hasura.io/jwt/claims)
4. Validate JWT hasn't expired
5. Check issuer and audience if configured
问题:权限被拒绝错误
Solution:
1. Check role is in allowed_roles
2. Verify permission rules allow the operation
3. Test with admin role to isolate permission issue
4. Check session variables are being sent correctly
5. Review both row-level and column-level permissions
问题:事件触发器未触发
Solution:
1. Check webhook is accessible from Hasura
2. Verify table name and operation match trigger config
3. Check webhook returns 200 status
4. Review event trigger logs in Hasura console
5. Ensure database triggers are enabled
问题:Action 返回错误
Solution:
1. Verify action handler URL is accessible
2. Check request/response format matches action definition
3. Review action handler logs
4. Test action handler independently
5. Verify permissions allow the role to execute action
问题:远程架构未加载
Solution:
1. Verify remote GraphQL endpoint is accessible
2. Check authentication headers if required
3. Test remote schema independently
4. Review timeout settings
5. Check for type name conflicts
问题:订阅连接断开
Solution:
1. Check WebSocket support on hosting platform
2. Verify connection timeout settings
3. Implement reconnection logic in client
4. Check for firewall/proxy blocking WebSockets
5. Monitor connection pool limits
更多资源
官方文档
- Hasura 文档:https://hasura.io/docs
- Hasura GraphQL API 参考:https://hasura.io/docs/latest/api-reference
- Hasura Cloud:https://hasura.io/cloud
学习资源
- Hasura 学习资源:https://hasura.io/learn
- Hasura 博客:https://hasura.io/blog
- Hasura YouTube:https://youtube.com/hasurahq
社区
- Discord:https://discord.gg/hasura
- GitHub:https://github.com/hasura/graphql-engine
- 论坛:https://github.com/hasura/graphql-engine/discussions
工具与集成
- Hasura CLI:https://hasura.io/docs/latest/hasura-cli/overview
- Hasura Cloud 控制台:https://cloud.hasura.io
- GraphQL 代码生成器:https://www.graphql-code-generator.com
技能版本:1.0.0 最后更新:2025 年 1 月 技能类别:后端、GraphQL、API 开发、实时、数据库 兼容对象:PostgreSQL、Auth0、Firebase、Cognito、Kubernetes、Docker