hasura
Expert guidance for Hasura, the GraphQL engine that gives the agent instant, real-time GraphQL APIs over PostgreSQL (and other databases). Helps developers set up Hasura, configure permissions, write custom business logic with Actions and Event Triggers, and optimize GraphQL queries for production.
Usage
Getting Started
- Install the skill using the command above
- Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
- Reference the skill in your prompt
- The AI will use the skill's capabilities automatically
Example Prompts
- "Review the open pull requests and summarize what needs attention"
- "Generate a changelog from the last 20 commits on the main branch"
Documentation
Overview
Hasura, the GraphQL engine that gives the user instant, real-time GraphQL APIs over PostgreSQL (and other databases). Helps developers set up Hasura, configure permissions, write custom business logic with Actions and Event Triggers, and optimize GraphQL queries for production.
Instructions
Quick Start
# Docker Compose for local development
cat > docker-compose.yml << 'EOF'
version: "3.6"
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgrespassword
volumes:
- pgdata:/var/lib/postgresql/data
hasura:
image: hasura/graphql-engine:v2.42.0
ports:
- "8080:8080"
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
HASURA_GRAPHQL_ADMIN_SECRET: mysecretkey
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_DEV_MODE: "true"
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup,http-log,query-log
depends_on:
- postgres
volumes:
pgdata:
EOF
docker compose up -d
# Console at http://localhost:8080/console
Auto-Generated API
# After creating a "users" table in PostgreSQL,
# Hasura auto-generates these operations:
# Query all users with filtering, sorting, pagination
query {
users(
where: { plan: { _eq: "pro" }, created_at: { _gte: "2026-01-01" } }
order_by: { created_at: desc }
limit: 20
offset: 0
) {
id
email
plan
created_at
# Nested relationship (auto-detected from foreign keys)
posts(where: { published: { _eq: true } }) {
id
title
}
posts_aggregate {
aggregate { count }
}
}
# Aggregation
users_aggregate(where: { plan: { _eq: "pro" } }) {
aggregate {
count
avg { lifetime_value }
}
}
}
# Insert
mutation {
insert_users_one(object: {
email: "new@example.com"
plan: "free"
}) {
id
}
}
# Update
mutation {
update_users(
where: { id: { _eq: "user-123" } }
_set: { plan: "pro" }
) {
affected_rows
returning { id plan }
}
}
# Real-time subscription
subscription {
users(where: { online: { _eq: true } }) {
id
email
last_seen
}
}
Permissions (Row-Level Security)
# metadata/databases/default/tables/public_orders.yaml
# Fine-grained permissions per role
table:
name: orders
schema: public
# Select: users can only see their own orders
select_permissions:
- role: user
permission:
columns: [id, amount, status, created_at]
filter:
user_id: { _eq: X-Hasura-User-Id }
limit: 100
# Admin role: see all orders
- role: admin
permission:
columns: "*"
filter: {}
allow_aggregations: true
# Insert: users can create orders for themselves
insert_permissions:
- role: user
permission:
columns: [amount, items, shipping_address]
set:
user_id: X-Hasura-User-Id # Auto-set from auth token
status: pending # Force initial status
check:
amount: { _gt: 0 } # Validate: positive amount
# Update: users can only cancel their pending orders
update_permissions:
- role: user
permission:
columns: [status]
filter:
user_id: { _eq: X-Hasura-User-Id }
status: { _eq: "pending" }
set: {}
check:
status: { _eq: "cancelled" } # Can only set to cancelled
Actions (Custom Business Logic)
# metadata/actions.yaml — Extend GraphQL with custom resolvers
actions:
- name: processPayment
definition:
kind: synchronous
handler: http://api:3000/actions/process-payment
type: mutation
arguments:
- name: order_id
type: uuid!
- name: payment_method
type: String!
output_type: PaymentResult
permissions:
- role: user
// api/actions/process-payment.ts — Action handler
export default async function handler(req: Request) {
const { input, session_variables } = await req.json();
const userId = session_variables["x-hasura-user-id"];
const { order_id, payment_method } = input;
// Verify order belongs to user
const order = await db.query("SELECT * FROM orders WHERE id = $1 AND user_id = $2", [order_id, userId]);
if (!order.rows[0]) return Response.json({ error: "Not found" }, { status: 404 });
// Process payment via Stripe
const charge = await stripe.charges.create({
amount: order.rows[0].amount,
currency: "usd",
source: payment_method,
});
// Update order status
await db.query("UPDATE orders SET status = 'paid', payment_id = $1 WHERE id = $2", [charge.id, order_id]);
return Response.json({ success: true, payment_id: charge.id });
}
Event Triggers
# metadata/databases/default/tables/public_orders.yaml
event_triggers:
- name: on_order_paid
definition:
enable_manual: false
insert:
columns: "*"
update:
columns: [status]
retry_conf:
num_retries: 3
interval_sec: 10
webhook: http://api:3000/webhooks/order-paid
headers:
- name: x-webhook-secret
value_from_env: WEBHOOK_SECRET
// api/webhooks/order-paid.ts — Event trigger handler
export default async function handler(req: Request) {
const { event } = await req.json();
const { old: oldRow, new: newRow } = event.data;
// Only process when status changes to "paid"
if (newRow.status === "paid" && oldRow?.status !== "paid") {
// Send confirmation email
await sendEmail(newRow.user_id, "Order Confirmed", `Order #${newRow.id} paid.`);
// Update inventory
await decrementInventory(newRow.items);
// Notify fulfillment
await notifyFulfillment(newRow);
}
return Response.json({ success: true });
}
Migrations
# Apply metadata and migrations from code
hasura metadata apply
hasura migrate apply --database-name default
# Create a new migration
hasura migrate create add_orders_table --database-name default
# Export current state
hasura metadata export
hasura migrate create init --from-server --database-name default
Installation
# CLI
npm install -g hasura-cli
# Docker
docker pull hasura/graphql-engine:latest
# Hasura Cloud (managed)
# https://cloud.hasura.io
Examples
Example 1: Building a feature with Hasura
User request:
Add a real-time collaborative quick start to my React app using Hasura.
The agent installs the package, creates the component with proper Hasura initialization, implements the quick start with event handling and state management, and adds TypeScript types for the integration.
Example 2: Migrating an existing feature to Hasura
User request:
I have a basic auto-generated api built with custom code. Migrate it to use Hasura for better auto-generated api support.
The agent reads the existing implementation, maps the custom logic to Hasura's API, rewrites the components using Hasura's primitives, preserves existing behavior, and adds features only possible with Hasura (like Permissions, Actions).
Guidelines
- Permissions on every table — Tables without permissions are invisible to non-admin roles; define permissions as part of your schema
- Use relationships — Define foreign key relationships; Hasura auto-generates nested queries (no N+1 problem)
- Event triggers for side effects — Use event triggers (not polling) for email, notifications, and external API calls
- Actions for business logic — Complex operations (payments, multi-step workflows) go in Actions, not in client code
- Migrations in CI/CD — Export metadata and migrations; apply via CLI in your deployment pipeline
- Admin secret ≠ user auth — Use JWT or webhook auth for users; admin secret is for CI/CD and internal tools only
- Subscriptions for real-time — Use GraphQL subscriptions instead of polling; Hasura handles WebSocket efficiently
- Use views for complex queries — Create PostgreSQL views for complex aggregations; track them as tables in Hasura
Information
- Version
- 1.0.0
- Author
- terminal-skills
- Category
- Development
- License
- Apache-2.0