Skip to content

FraiseQL vs Hasura

Both FraiseQL and Hasura provide GraphQL APIs over databases. Here’s how they differ.

Hasura is a runtime GraphQL engine that interprets queries and generates SQL on-the-fly.

FraiseQL is a compiled Rust binary that pre-generates optimized SQL views at build time — no runtime SQL generation, no resolver code.

AspectFraiseQLHasura
ArchitectureCompiled Rust binaryInterpreted runtime engine
Query executionPre-built SQL viewsRuntime SQL generation
N+1 handlingEliminated by designRuntime batching
ConfigurationTOMLConsole + YAML
Schema sourceCode (Python, TS, Go…)Database introspection
Database supportPostgreSQL, MySQL, SQLite, SQL ServerPostgreSQL (primary), others via connectors
DeploymentSingle binaryDocker container + metadata
PerformancePredictable, sub-msVariable, depends on query
Custom logicObservers (reactive events)Actions (HTTP), Remote Schemas
PricingOpen source (Apache 2.0)Open source core, paid cloud
REST transport✅ CQRS-derived + annotation override, OpenAPI 3.0.3, JSON envelope, filtering, ETags⚠️ RESTified Endpoints (wraps a saved GraphQL query; no OpenAPI spec, no HTTP status-code mapping, no request-body parsing)
gRPC transport✅ Transport-aware DB views, auto-generated .proto❌ Requires external gateway
OpenAPI generation✅ Auto-generated from compiled schema

Pricing information accurate as of February 2026. Verify current pricing at hasura.io/pricing.

FraiseQL serves REST and gRPC alongside GraphQL from a single binary. Hasura’s RESTified Endpoints let you expose a saved GraphQL query as a REST URL, but they are not a first-class REST transport: there is no HTTP status-code mapping, no OpenAPI spec, and no request-body parsing. For gRPC clients, Hasura requires an external gateway.

If you know Hasura, here is how its concepts map to FraiseQL equivalents:

HasuraFraiseQL Equivalent
Track tableCreate SQL view (v_*)
Permissions (YAML)PostgreSQL Row-Level Security
Event triggersObservers
ActionsSQL functions + mutations
Remote schemasFederation
Computed fieldsSQL view expressions
-- Composed views: tb_ tables, v_ views, child .data embedded in parent
CREATE VIEW v_user AS
SELECT id, jsonb_build_object('id', id, 'name', name, 'email', email) AS data
FROM tb_user;
CREATE VIEW v_post AS
SELECT p.id,
jsonb_build_object('id', p.id, 'title', p.title, 'author', vu.data) AS data
FROM tb_post p
JOIN v_user vu ON vu.id = p.fk_user;

Query execution: Single indexed view lookup

-- Generated at runtime per query
SELECT u.* FROM users u WHERE u.id = $1;
SELECT p.* FROM posts p WHERE p.author_id IN ($1, $2, ...);
-- Results assembled in memory

Query execution: Multiple queries + assembly

fraiseql.toml
[project]
name = "my-api"
[database]
url = "${DATABASE_URL}"
[server]
port = 8080
Terminal window
# JWT authentication via env var (no [auth] section in fraiseql.toml)
JWT_SECRET=your-256-bit-secret

Hasura: Console + YAML + Database Metadata

Section titled “Hasura: Console + YAML + Database Metadata”
config.yaml
version: 3
metadata_directory: metadata
actions:
handler_webhook_baseurl: http://localhost:3000

Plus separate files for:

  • tables.yaml
  • relationships.yaml
  • permissions.yaml
  • remote_schemas.yaml
  • etc.
@fraiseql.type
class User:
"""User with posts."""
id: str
name: str
email: str
posts: list['Post']
  • Full IDE support
  • Type checking
  • Refactoring tools
  • Version control friendly
  1. Create tables in database
  2. Hasura introspects schema
  3. Configure relationships in console
  4. Track tables/views
  • Quick to start
  • Limited to database capabilities
  • Configuration in metadata

FraiseQL uses observers for reactive business logic — you define conditions and actions that trigger on database changes:

fraiseql.toml
[observers]
backend = "nats"
nats_url = "nats://localhost:4222"
[[observers.rules]]
entity = "Order"
event = "INSERT"
condition = "total > 1000"
[[observers.rules.actions]]
type = "webhook"
url = "https://api.example.com/high-value-orders"
[[observers.rules.actions]]
type = "slack"
channel = "#sales"
message = "High-value order {id}: ${total}"
[[observers.rules.actions]]
type = "email"
to = "sales@example.com"
subject = "High-value order {id}"
body = "Order {id} for ${total} was created"
[observers.rules.retry]
max_attempts = 5
backoff_strategy = "exponential"

Built-in actions for webhooks, Slack, and email. No external services required.

actions.yaml
- name: processOrder
definition:
kind: synchronous
handler: http://business-logic-service:3000/process-order

Plus a separate HTTP service to handle the logic. Hasura also has event triggers, but they require external webhook handlers.

metadata/event_triggers/on_order_created.yaml
- name: on_order_created
definition:
enable_manual: false
insert:
columns: "*"
retry_conf:
num_retries: 3
interval_sec: 30
timeout_sec: 60
webhook: https://my-service.example.com/on-order-created
headers:
- name: X-Webhook-Secret
value_from_env: WEBHOOK_SECRET

Hasura calls your external HTTP webhook. You own and deploy a separate service to handle the event.

fraiseql.toml
[observers]
backend = "nats"
nats_url = "nats://localhost:4222"
[[observers.rules]]
entity = "Order"
event = "INSERT"
[[observers.rules.actions]]
type = "webhook"
url = "https://my-service.example.com/on-order-created"
[observers.rules.actions.headers]
X-Webhook-Secret = "{env.WEBHOOK_SECRET}"
[[observers.rules.actions]]
type = "slack"
channel = "#orders"
message = "New order {id} received"
[observers.rules.retry]
max_attempts = 3
interval_sec = 30
timeout_sec = 60

The key difference: Hasura event triggers require you to host an external HTTP service. FraiseQL observers are configured in fraiseql.toml — actions like webhook, slack, and email are built-in to the FraiseQL runtime, with no separate service required.

FraiseQL enforces access control during schema compilation, before any query executes:

  • Scope-based field access control — requires JWT scopes to read or write protected fields
  • No runtime overhead — access rules are baked into the compiled schema
  • Impossible to deploy invalid schemas — configuration conflicts caught at build time
  • Zero database errors — unauthorized writes never reach the database

Access control rules are declared directly on fields using fraiseql.field():

from typing import Annotated
import fraiseql
@fraiseql.input
class CreateUserInput:
email: str
age: int
phone: str
# Protected field — requires admin scope to set
role: Annotated[str, fraiseql.field(
requires_scope="admin:write",
on_deny="reject",
description="User role — admin only",
)]

Hasura relies primarily on GraphQL’s built-in type system for validation:

  • Type checking — scalar types, required fields (from GraphQL spec)
  • Basic validation — Only what the type system provides
  • Runtime only — Validation happens during query execution
  • Custom validation via Actions — HTTP webhooks for complex logic

For anything beyond GraphQL type checking, Hasura users must implement custom Actions (external HTTP services).

AspectFraiseQLHasura
Field access controlScope-based (compile-time)Permissions YAML
Compile-time enforcement✅ Yes❌ No
Cross-field validation✅ Yes (PostgreSQL CHECK constraints)❌ Custom Actions required
Database protectionUnauthorized data impossiblePossible without Actions
ConfigurationDeclarative TOMLGraphQL directives + Actions

Hasura is a better choice when:

  • You need rapid prototyping — Point at a database, get instant API
  • Your team prefers GUI configuration — Console-based setup
  • You have an existing database — Introspection works great
  • You need event triggers — Hasura’s event system is mature
  • You want managed hosting — Hasura Cloud is polished

FraiseQL is a better choice when:

  • You want predictable performance — Compiled queries, no surprises
  • You prefer code over configuration — Schema in your language
  • You need multi-database support — PostgreSQL, MySQL, SQLite, SQL Server
  • You want simple deployment — Single Rust binary, no orchestration
  • You value readability — TOML over YAML, one file over many
  1. Start Hasura locally:

    Terminal window
    docker run -d --name hasura \
    -p 8080:8080 \
    -e HASURA_GRAPHQL_DATABASE_URL=postgresql://user:pass@host/db \
    hasura/graphql-engine:latest

    Open the console at http://localhost:8080/console

    Manual steps required:

    • Click “Data” → “Manage”
    • Track each table manually
    • Configure relationships in UI
    • Set permissions via YAML
    • Export metadata for version control
  2. Create the same API with FraiseQL:

    # fraiseql.toml - single file
    [database]
    url = "${DATABASE_URL}"
    # schema.py - code-first
    import fraiseql
    @fraiseql.type
    class User:
    id: str
    name: str
    email: str
    posts: list['Post']
    @fraiseql.type
    class Post:
    id: str
    title: str
    author: User
    Terminal window
    fraiseql run

    No manual configuration — schema derived from code automatically.

  3. Compare query performance:

    Hasura generates SQL at runtime:

    -- Generated per request
    SELECT * FROM users WHERE ...;
    SELECT * FROM posts WHERE user_id IN (...);

    FraiseQL uses pre-built views:

    -- Single view query
    SELECT data FROM v_user WHERE id = $1;
  4. Test GraphQL introspection:

    Terminal window
    # Both expose same introspection
    curl -X POST http://localhost:8080/v1/graphql \
    -H "Content-Type: application/json" \
    -d '{"query": "{ __schema { types { name } } }"}'
    curl -X POST http://localhost:8080/graphql \
    -H "Content-Type: application/json" \
    -d '{"query": "{ __schema { types { name } } }"}'
Terminal window
# Get your Hasura table definitions
hasura metadata export

Hasura table:

tables.yaml
- table:
name: users
schema: public
object_relationships:
- name: posts
using:
foreign_key_constraint_on:
column: author_id
table:
name: posts

FraiseQL equivalent:

@fraiseql.type
class User:
id: str
name: str
email: str
posts: list['Post']
@fraiseql.type
class Post:
id: str
title: str
author: User

Hasura Action:

- name: processPayment
definition:
kind: synchronous
handler: http://payments:3000/process

FraiseQL observer (TOML-based configuration):

fraiseql.toml
[[observers.rules]]
entity = "Order"
event = "UPDATE"
condition = "status = 'pending_payment'"
[[observers.rules.actions]]
type = "webhook"
url = "https://payments.internal/process"
[observers.rules.actions.body]
order_id = "{id}"
amount = "{total}"

Already on Hasura and want to try FraiseQL? Here are the three core steps:

  1. Install the FraiseQL CLI

    Terminal window
    # Download the FraiseQL binary
    curl -fsSL https://install.fraiseql.dev | sh
    # Verify installation
    fraiseql --version
  2. Convert your Hasura YAML to a FraiseQL schema

    Export your Hasura metadata, then rewrite each table definition as a FraiseQL type and write the corresponding SQL views (v_*) to enforce permissions:

    Terminal window
    # Export existing Hasura configuration
    hasura metadata export
    # Inspect generated metadata
    ls metadata/databases/default/tables/

    For each Hasura table, create a FraiseQL type and a SQL view. See the examples above for the conversion pattern, or refer to the full migration guide.

  3. Run FraiseQL

    Terminal window
    # Start the FraiseQL server pointing at your existing database
    fraiseql run --config fraiseql.toml

    FraiseQL will read your schema types, connect to your database, and serve the GraphQL API on the configured port. You can keep Hasura running in parallel during the transition.

For a complete walkthrough including subscription migration and permission replication, see the Migrating from Hasura guide.

Migrating from Hasura? See the step-by-step migration guide.

ChooseWhen
HasuraRapid prototyping, existing databases, GUI preference
FraiseQLPredictable performance, code-first, multi-database

Both are excellent tools. Choose based on your team’s preferences and requirements.


Performance Benchmarks

See how FraiseQL performs with real numbers. View Benchmarks

Migrate from Hasura

Step-by-step guide to moving your existing Hasura setup. Migration Guide

How FraiseQL Works

Understand the compiled, database-first architecture. Core Concepts