mcpField
H
hasura-graphql-engine

hasura-graphql-engine

github.com/manutej開発62504 回インストール39 週間前に更新
リポジトリ

hasura-graphql-engine について

Hasura GraphQL Engine で GraphQL API を構築する開発者向けのスキルです。データベースからの API 生成、アクセス制御、認証、イベントトリガー、カスタム Actions、リモートスキーマ、サブスクリプションを解説します。

主な機能

  1. 01PostgreSQL から GraphQL API を生成
  2. 02ロール・行・列単位の権限設定
  3. 03JWT または Webhook 認証の統合
  4. 04データベースイベントで Webhook を起動
  5. 05Actions によるビジネスロジックの拡張
  6. 06リモートスキーマとサブスクリプションの統合

活用シーン

  1. 01既存データベース向け GraphQL API の構築
  2. 02マルチテナントのデータアクセス制御
  3. 03データベース変更後の外部ワークフロー実行
  4. 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 の設定、ボイラープレートは不要です
  • デフォルトでリアルタイム:すべてのクエリに対応するサブスクリプションが自動的に用意されます

仕組み:

  1. Hasura を PostgreSQL データベースに接続します
  2. Hasura Console でテーブルをトラッキングします
  3. 次の機能を備えた 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、カスタム認証をサポートします

認証フロー:

  1. ユーザーが認証サービス(Auth0、Firebase、カスタムサービス)で認証します
  2. 認証サービスが Hasura クレームを含む JWT を発行します
  3. クライアントが Authorization ヘッダーに JWT を含めて送信します
  4. Hasura が JWT を検証し、セッション変数を抽出します
  5. セッション変数を使って権限を評価します
  6. 適切なアクセス制御のもとで 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 を呼び出します。

コンソールから:

  1. Events タブに移動します
  2. トリガーを作成します
  3. テーブルと操作(INSERT、UPDATE、DELETE)を選択します
  4. Webhook URL を指定します
  5. 再試行とタイムアウトの設定を行います

メタデータ 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

ベストプラクティス

セキュリティのベストプラクティス
  1. 本番環境では必ず ADMIN_SECRET を使用する

    • 認証なしで管理 API を公開しないでください
    • シークレットを定期的にローテーションしてください
    • 強力でランダムなシークレットを使用してください(32 文字以上)
  2. 適切な JWT 検証を実装する

    • 本番環境では RS256(非対称)を使用してください
    • 適切なトークン有効期限を設定してください
    • 発行者とオーディエンスのクレームを検証してください
  3. 最小権限の原則に基づいて権限を適用する

    • アクセス権なしの状態から始め、必要に応じて権限を追加してください
    • すべてのテーブルで行レベルセキュリティを使用してください
    • 権限のないロールから機密列を非表示にしてください
  4. 本番環境ではコンソールを無効にする

    • HASURA_GRAPHQL_ENABLE_CONSOLE=false
    • 変更にはメタデータファイルと CLI を使用してください
  5. レート制限を有効にする

    • DoS 攻撃から保護してください
    • 必要に応じてロールごとの制限を設定してください
    • 使用状況を監視し、それに応じて調整してください
  6. Webhook のペイロードを検証する

    • イベントトリガーには Webhook シークレットを使用してください
    • アクションの入力を検証してください
    • すべてのユーザー入力をサニタイズしてください
パフォーマンスに関するベストプラクティス
  1. データベースクエリを最適化する

    • 適切なインデックスを作成します
    • 複雑なクエリにはデータベースビューを使用します
    • PostgreSQL のパフォーマンスチューニングを活用します
  2. クエリキャッシュを使用する

    • 負荷の高いクエリでは @cached ディレクティブを有効にします
    • 適切な TTL 値を設定します
    • 可能な場合は CDN レベルでキャッシュします
  3. クエリの深さと複雑さを制限する

    • クエリの最大深度を設定します
    • 深くネストされたクエリを制限します
    • 大規模な結果セットにはページネーションを使用します
  4. コネクションプーリングを設定する

    • HASURA_GRAPHQL_PG_CONNECTIONS を調整します
    • コネクションの使用状況を監視します
    • 大規模なデプロイでは PgBouncer を使用します
  5. サブスクリプションを最適化する

    • サブスクリプションの多重化を使用します
    • クライアントごとの同時サブスクリプション数を制限します
    • 時間的な制約が比較的少ないデータには、ポーリングを検討します
開発ワークフローに関するベストプラクティス
  1. メタデータをバージョン管理する

    • metadata/ フォルダーを Git にコミットします
    • すべてのスキーマ変更にマイグレーションを使用します
    • PR でメタデータの変更をレビューします
  2. 環境を分離する

    • 開発、ステージング、本番の環境を用意します
    • 環境ごとに異なる管理者シークレットを使用します
    • まずステージングでマイグレーションをテストします
  3. テスト戦略

    • パーミッションを徹底的にテストします
    • イベントトリガーを統合テストします
    • アクションハンドラーを個別にテストします
  4. ドキュメント

    • カスタムアクションとその入力/出力を文書化します
    • 複雑なパーミッションルールを説明します
    • 利用者向けの API ドキュメントを維持します
  5. 監視とアラート

    • クエリのパフォーマンスを監視します
    • 失敗したウェブフック/イベントトリガーに対してアラートを設定します
    • エラー率とレイテンシを追跡します

よくあるパターンと例

パターン 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

追加リソース

公式ドキュメント
学習リソース
コミュニティ
ツールとインテグレーション

スキルのバージョン:1.0.0 最終更新日:2025 年 1 月 スキルのカテゴリ:バックエンド、GraphQL、API 開発、リアルタイム、データベース 互換性:PostgreSQL、Auth0、Firebase、Cognito、Kubernetes、Docker

コメント