hasura-graphql-engine について
Hasura GraphQL Engine で GraphQL API を構築する開発者向けのスキルです。データベースからの API 生成、アクセス制御、認証、イベントトリガー、カスタム Actions、リモートスキーマ、サブスクリプションを解説します。
主な機能
- 01PostgreSQL から GraphQL API を生成
- 02ロール・行・列単位の権限設定
- 03JWT または Webhook 認証の統合
- 04データベースイベントで Webhook を起動
- 05Actions によるビジネスロジックの拡張
- 06リモートスキーマとサブスクリプションの統合
活用シーン
- 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 の即時生成、きめ細かな権限設定、認証の統合、イベント駆動型アーキテクチャ、カスタムビジネスロジック、リモートスキーマの統合を習得し、モダンなアプリケーションを構築します。
このスキルを使う場面
次のような場合に Hasura GraphQL Engine を使用します。
- バックエンドコードを書かずに GraphQL API を迅速に構築する
- 既存の PostgreSQL データベースから CRUD API をすぐに作成する
- 行レベルおよび列レベルのきめ細かなセキュリティを実装する
- GraphQL サブスクリプションを使ったリアルタイムアプリケーションを構築する
- 複数のデータソース(データベース、REST API、GraphQL サービス)を統合する
- データベーストリガーを使ったイベント駆動型アーキテクチャを構築する
- Actions を介してカスタムビジネスロジックで GraphQL を拡張する
- API 層で認証と認可を実装する
- 管理パネル、ダッシュボード、社内ツールを迅速に構築する
- バックエンドを書き換えずに REST から GraphQL に移行する
- 本番環境に対応した機能(キャッシュ、レート制限、モニタリング)が必要である
- ロールベースのアクセス制御を備えたマルチテナント SaaS アプリケーションを構築する
主な概念
GraphQL API の即時生成
Hasura の主な価値は、データベーススキーマから GraphQL API を自動生成できることです。
- テーブルのトラッキング:Hasura を PostgreSQL のテーブルに接続すると、クエリ、ミューテーション、サブスクリプションをすぐに利用できます
- リレーションシップの検出:外部キーによるリレーションシップを GraphQL の接続として自動的に推論します
- 型安全性:データベーススキーマが GraphQL の型に直接変換されます
- コード不要:リゾルバーの記述、ORM の設定、ボイラープレートは不要です
- デフォルトでリアルタイム:すべてのクエリに対応するサブスクリプションが自動的に用意されます
仕組み:
- Hasura を PostgreSQL データベースに接続します
- Hasura Console でテーブルをトラッキングします
- 次の機能を備えた GraphQL API をすぐに利用できます。
query- フィルタリング、並べ替え、ページネーションを使ってデータを取得しますmutation- 挿入、更新、削除の操作を行いますsubscription- WebSocket を介してリアルタイムにデータを更新します
メタデータ駆動型アーキテクチャ
Hasura はコード駆動ではなく、メタデータ駆動型です。
- メタデータ:API を定義する JSON/YAML 設定
- 宣言的:実装方法ではなく、実現したい内容を定義します
- バージョン管理:メタデータファイルを Git にコミットできます
- CLI によるマイグレーション:Hasura CLI でメタデータとマイグレーションを管理します
- プログラムによる制御:自動化に使用するメタデータ API
主なメタデータの構成要素:
- テーブルのトラッキングとリレーションシップ
- 権限ルール
- リモートスキーマ
- 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 トリガー
- 信頼性の高い配信:少なくとも 1 回の配信と再試行
- ペイロード:古い行と新しい行のデータを JSON で提供
- 非同期処理:長時間実行されるタスクや外部連携
- ユースケース:メールの送信、Elasticsearch との同期、キャッシュの更新、ワークフローのトリガー
アクション
アクションを使用すると、カスタムのビジネスロジックで Hasura を拡張できます。
- カスタムミューテーション:コードで処理する GraphQL ミューテーションを定義
- カスタムクエリ:データベースアクセス以外のカスタムクエリロジックを追加
- REST 連携:GraphQL から REST API を呼び出す
- 型安全性:GraphQL SDL で入力型と出力型を定義
- ハンドラー:HTTP エンドポイントで GraphQL 変数を受信
一般的なユースケース:
- 決済処理
- 複雑な検証
- サードパーティ API の呼び出し
- カスタムアルゴリズム
- ファイルのアップロード
- メールの送信
リモートスキーマ
リモートスキーマを使用すると、外部の GraphQL API を統合してスキーマをステッチできます。
- スキーマステッチング:複数の GraphQL サービスを統合
- 型拡張:リモートスキーマのフィールドで型を拡張
- 権限:リモートスキーマにロールベースの権限を適用
- 名前空間:競合を避けるためにリモートスキーマを分離
- ユースケース:マイクロサービス、レガシーな 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:更新対象として選択できる行 check:更新完了後の検証 set:列の値を自動的に設定
削除権限
削除可能な行を制御します。
例:自身のデータのみを削除する場合
{
"filter": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
認証の連携
JWT モードの設定
JWT トークンを検証するように Hasura を設定します。
環境変数:
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 を指定します
- 再試行とタイムアウトの設定を行います
メタデータ 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 });
});
アクション(カスタムビジネスロジック)
アクションの定義
アクションは、カスタムのミューテーションとクエリで GraphQL を拡張します。
GraphQL SDL の定義:
type Mutation {
login(username: String!, password: String!): LoginResponse
}
type LoginResponse {
accessToken: String!
refreshToken: String!
user: User!
}
アクションの設定:
- 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
アクションハンドラーの実装
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
}
});
});
アクションの権限
アクションを実行できるロールを制御します。
メタデータ 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
アクションの種類
同期アクション:
- クライアントは応答を待ちます
- 用途:ログイン、決済、バリデーション
- タイムアウト:設定可能(デフォルトは 30s)
非同期アクション:
- アクション ID をすぐに返します
- 用途:長時間実行されるタスク、バッチ処理
- 完了までポーリングするか、Webhook を使用します
高度なアクションパターン
決済処理:
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!
}
リモートスキーマ
リモートスキーマの追加
外部の GraphQL API を統合します。
メタデータ 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
}
}
}
リモートスキーマのカスタマイズ
競合を避けるため、型名とフィールド名をカスタマイズします。
{
"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_"
}
]
}
}
}
}
リモートスキーマの権限
リモートスキーマにロールベースの権限を適用します。
元のリモートスキーマ:
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' ロール用に制限されたスキーマ:
type User {
first_name: String!
last_name: String!
}
type Query {
get_users_by_name(first_name: String!, last_name: String): [User]
}
メタデータ 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] }"
}
}
}
リモートスキーマ引数のプリセット
セッション変数をリモートスキーマのクエリに自動的に挿入します。
セッション変数のプリセット:
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))
}
リモートリレーションシップ
ローカルデータベースのテーブルをリモートスキーマに接続します。
例:ローカルの顧客をリモートの決済 API にリンクする
SQL テーブル:
CREATE TABLE customer (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
リモートスキーマ(決済 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
メタデータのエクスポートとインポート
メタデータをエクスポート:
hasura metadata export
# Exports to metadata/ folder
メタデータを適用:
hasura metadata apply
# Applies metadata from metadata/ folder
メタデータを再読み込み:
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(非対称)を使用してください
- 適切なトークン有効期限を設定してください
- 発行者とオーディエンスのクレームを検証してください
-
最小権限の原則に基づいて権限を適用する
- アクセス権なしの状態から始め、必要に応じて権限を追加してください
- すべてのテーブルで行レベルセキュリティを使用してください
- 権限のないロールから機密列を非表示にしてください
-
本番環境ではコンソールを無効にする
HASURA_GRAPHQL_ENABLE_CONSOLE=false- 変更にはメタデータファイルと CLI を使用してください
-
レート制限を有効にする
- DoS 攻撃から保護してください
- 必要に応じてロールごとの制限を設定してください
- 使用状況を監視し、それに応じて調整してください
-
Webhook のペイロードを検証する
- イベントトリガーには Webhook シークレットを使用してください
- アクションの入力を検証してください
- すべてのユーザー入力をサニタイズしてください
パフォーマンスに関するベストプラクティス
-
データベースクエリを最適化する
- 適切なインデックスを作成します
- 複雑なクエリにはデータベースビューを使用します
- PostgreSQL のパフォーマンスチューニングを活用します
-
クエリキャッシュを使用する
- 負荷の高いクエリでは @cached ディレクティブを有効にします
- 適切な TTL 値を設定します
- 可能な場合は CDN レベルでキャッシュします
-
クエリの深さと複雑さを制限する
- クエリの最大深度を設定します
- 深くネストされたクエリを制限します
- 大規模な結果セットにはページネーションを使用します
-
コネクションプーリングを設定する
HASURA_GRAPHQL_PG_CONNECTIONSを調整します- コネクションの使用状況を監視します
- 大規模なデプロイでは PgBouncer を使用します
-
サブスクリプションを最適化する
- サブスクリプションの多重化を使用します
- クライアントごとの同時サブスクリプション数を制限します
- 時間的な制約が比較的少ないデータには、ポーリングを検討します
開発ワークフローに関するベストプラクティス
-
メタデータをバージョン管理する
- metadata/ フォルダーを Git にコミットします
- すべてのスキーマ変更にマイグレーションを使用します
- PR でメタデータの変更をレビューします
-
環境を分離する
- 開発、ステージング、本番の環境を用意します
- 環境ごとに異なる管理者シークレットを使用します
- まずステージングでマイグレーションをテストします
-
テスト戦略
- パーミッションを徹底的にテストします
- イベントトリガーを統合テストします
- アクションハンドラーを個別にテストします
-
ドキュメント
- カスタムアクションとその入力/出力を文書化します
- 複雑なパーミッションルールを説明します
- 利用者向けの API ドキュメントを維持します
-
監視とアラート
- クエリのパフォーマンスを監視します
- 失敗したウェブフック/イベントトリガーに対してアラートを設定します
- エラー率とレイテンシを追跡します
よくあるパターンと例
パターン 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:E コマースプラットフォーム
スキーマ:
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 });
});
アクション:支払いを処理する
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
問題:アクションがエラーを返す
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 Learn: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 Code Generator:https://www.graphql-code-generator.com
スキルのバージョン:1.0.0 最終更新日:2025 年 1 月 スキルのカテゴリ:バックエンド、GraphQL、API 開発、リアルタイム、データベース 互換性:PostgreSQL、Auth0、Firebase、Cognito、Kubernetes、Docker