Skip to main content

GraphQL for the LLM era. Simple. Powerful. Rust-fast. Production-ready GraphQL API framework for PostgreSQL with CQRS, JSONB optimization, and type-safe mutations

Project description

FraiseQL

Quality Gate Documentation Release Python License: MIT Version Status

๐Ÿ“ You are here: Main FraiseQL Framework (v1.8.3) - Stable Release

Current Version: v1.8.3 | Status: Stable | Python: 3.13+ | PostgreSQL: 13+


GraphQL for the LLM era. Simple. Powerful. Rust-fast.

PostgreSQL returns JSONB. Rust transforms it. Zero Python overhead.

# Complete GraphQL API in ~15 lines
from fraiseql import type, query
from fraiseql.fastapi import create_fraiseql_app

@fraiseql.type(sql_source="v_user", jsonb_column="data")
class User:
    id: int
    name: str
    email: str

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    return await db.find("v_user")

app = create_fraiseql_app(
    database_url="postgresql://localhost/mydb",
    types=[User],
    queries=[users]
)

Why FraiseQL?

  • โšก Rust pipeline - No Python JSON overhead, compiled performance
  • ๐Ÿ”’ Secure by design - Explicit field contracts prevent data leaks
  • ๐Ÿค– AI-native - LLMs generate correct code on first try
  • ๐Ÿ’ฐ Save $5-48K/year - Eliminate Redis, Sentry, APM tools
  • ๐Ÿ”„ GraphQL Cascade - Automatic cache updates and side effect tracking
  • โœจ Auto-populated mutations - status, message, errors handled automatically (50-60% less boilerplate)
  • ๐ŸŽฏ Auto-wired query params - where, orderBy, limit, offset added automatically to list queries
  • ๐Ÿ” Advanced filtering - Full-text search, JSONB queries, array operations, regex
  • ๐Ÿง  Vector search - pgvector integration for semantic search, RAG, recommendations (6 distance operators)

๐Ÿค” Is this for me?

FraiseQL is for production teams building high-performance GraphQL APIs with PostgreSQL.

โœ… You should use FraiseQL if you:

  • Build customer-facing APIs with PostgreSQL
  • Need sub-millisecond query performance
  • Want enterprise-grade security and monitoring
  • Have 2-50 developers on your team
  • Are tired of Python serialization overhead

โŒ Consider alternatives if you:

  • Need multi-database support (FraiseQL is PostgreSQL-only)
  • Are building your first GraphQL API (start with simpler frameworks)
  • Don't use JSONB columns in PostgreSQL

See detailed audience guide for complete user profiles.


โšก The Rust Advantage

The problem with traditional GraphQL frameworks:

PostgreSQL โ†’ Rows โ†’ ORM deserialize โ†’ Python objects โ†’ GraphQL serialize โ†’ JSON โ†’ Response
                    โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Unnecessary roundtrip โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

FraiseQL's exclusive Rust pipeline:

PostgreSQL โ†’ JSONB โ†’ Rust field selection โ†’ HTTP Response
             โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Zero Python overhead โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Why This Matters

No Python serialization overhead:

# Traditional framework (Strawberry + SQLAlchemy)
user = db.query(User).first()        # SQL query
user_dict = user.__dict__             # Python object โ†’ dict
json_str = json.dumps(user_dict)      # dict โ†’ JSON string (slow!)

# FraiseQL
SELECT data FROM v_user LIMIT 1       # Returns JSONB
# Rust transforms JSONB โ†’ HTTP response (7-10x faster than Python)

Architectural benefits:

  • PostgreSQL composes JSONB once - No N+1 query problems
  • Rust selects fields - Respects GraphQL query shape in compiled code
  • Direct HTTP response - Zero-copy path from database to client
  • No ORM abstraction - Database returns final data structure

Security benefits:

  • Explicit field exposure - Only fields in JSONB view are accessible (no accidental leaks)
  • Clear data contracts - JSONB structure defines exactly what's exposed
  • No ORM over-fetching - Can't accidentally expose hidden columns
  • SQL injection protection - PostgreSQL prepared statements + typed parameters
  • Audit trail by design - Every mutation function can log explicitly
  • No mass assignment risks - Input types define allowed fields precisely

Other frameworks can't do this. They're locked into Python-based serialization because ORM returns Python objects. ORMs can accidentally expose fields you didn't mean to serialize, or fetch entire rows when only requesting specific fields.

FraiseQL is database-first, so data is already JSON. Rust just makes it fast and secure.


๐Ÿ”’ Security by Architecture

Traditional ORM-based frameworks have inherent security risks:

The ORM Security Problem

# Traditional ORM (SQLAlchemy + Strawberry)
class User(Base):
    id = Column(Integer, primary_key=True)
    email = Column(String)
    password_hash = Column(String)  # Sensitive!
    is_admin = Column(Boolean)      # Sensitive!
    api_key = Column(String)        # Sensitive!

# Strawberry type
@strawberry.type
class UserType:
    id: int
    email: str
    # Developer forgot to exclude password_hash, is_admin, api_key!

# Risk: ORM object has ALL columns accessible
# One mistake in serialization = data leak

Common ORM vulnerabilities:

  • โŒ Accidental field exposure - ORM loads all columns, easy to forget exclusions
  • โŒ Mass assignment attacks - ORM objects can be updated with any field
  • โŒ Over-fetching - Fetching entire rows increases attack surface
  • โŒ Hidden relationships - Lazy loading can expose unintended data
  • โŒ Implicit behavior - ORM magic makes security audits difficult

FraiseQL's Explicit Security

-- PostgreSQL view explicitly defines what's exposed
CREATE VIEW v_user AS
SELECT
    id,
    jsonb_build_object(
        'id', id,
        'email', email
        -- password_hash, is_admin, api_key NOT included
        -- Impossible to accidentally expose them!
    ) as data
FROM tb_user;
# Python type mirrors EXACT view structure
@type(sql_source="v_user", jsonb_column="data")
class User:
    id: int
    email: str
    # That's it. No other fields exist in this contract.

FraiseQL security advantages:

  • โœ… Explicit field whitelisting - Only fields in JSONB view can be queried
  • โœ… Impossible to over-fetch - View defines the complete data structure
  • โœ… Fixed recursion depth - View defines max nesting, prevents depth attacks
  • โœ… Protected against N+1 bombs - One query regardless of GraphQL complexity
  • โœ… Clear audit trail - Database view + Python type = two-layer verification
  • โœ… SQL injection protection - Prepared statements + typed parameters always
  • โœ… Mass assignment prevention - Input types define allowed fields precisely
  • โœ… Row-level security - PostgreSQL RLS integrates directly with views
  • โœ… Cryptographic audit logging - Built-in SHA-256 + HMAC audit chains

Recursion Depth Attack Protection

Traditional GraphQL vulnerability:

# Malicious query - can crash traditional servers
query {
  user(id: 1) {
    posts {           # 10 posts
      author {        # โ†’ 10 queries
        posts {       # โ†’ 10 ร— 10 = 100 queries
          author {    # โ†’ 100 queries
            posts {   # โ†’ 1,000 queries
              # ... 10 levels = 10^10 queries = server crash
            }
          }
        }
      }
    }
  }
}

Traditional framework response:

  • Each resolver level executes database queries
  • N+1 problem multiplies exponentially with depth
  • Requires query complexity middleware (can be bypassed)
  • DataLoader reduces but doesn't eliminate the problem

FraiseQL's built-in protection:

-- View defines MAXIMUM recursion depth
CREATE VIEW v_user AS
SELECT
    id,
    jsonb_build_object(
        'id', id,
        'name', name,
        'posts', (
            SELECT jsonb_agg(jsonb_build_object(
                'id', p.id,
                'title', p.title
                -- NO 'author' field here!
                -- Recursion is STRUCTURALLY IMPOSSIBLE
            ))
            FROM tb_post p
            WHERE p.user_id = tb_user.id
            LIMIT 100  -- Hard limit on array size
        )
    ) as data
FROM tb_user;

What happens when attacker tries deep query:

query {
  user {
    posts {
      author {  # โ† GraphQL schema validation FAILS
        # Field 'author' doesn't exist on Post type
        # because v_post view doesn't include it
      }
    }
  }
}

Protection layers:

  1. Schema validation - GraphQL rejects queries for non-existent fields
  2. View structure - Database defines allowed nesting depth
  3. Hard limits - LIMIT clauses prevent array size attacks
  4. One query - PostgreSQL executes entire JSONB in single query

Result: Attackers cannot exceed the depth you define in views. No middleware needed.


๐Ÿ” Security Features

FraiseQL includes enterprise-grade security features designed for global regulatory compliance and production deployment:

๐Ÿ“‹ Software Bill of Materials (SBOM)

  • Automated generation via fraiseql sbom generate
  • Global compliance: US EO 14028, EU NIS2/CRA, PCI-DSS 4.0, ISO 27001
  • CycloneDX 1.5 format with cryptographic signing
  • CI/CD integration for continuous compliance

๐Ÿ”‘ Key Management Service (KMS)

  • HashiCorp Vault: Production-ready with transit engine
  • AWS KMS: Native integration with GenerateDataKey
  • GCP Cloud KMS: Envelope encryption support
  • Local Provider: Development-only with warnings

๐Ÿ›ก๏ธ Security Profiles

  • STANDARD: Default protections for general applications
  • REGULATED: PCI-DSS/HIPAA/SOC 2 compliance
  • RESTRICTED: Government, defence, critical infrastructure
    • ๐Ÿ‡บ๐Ÿ‡ธ FedRAMP, DoD, NIST 800-53
    • ๐Ÿ‡ช๐Ÿ‡บ NIS2 Essential Entities, EU CRA
    • ๐Ÿ‡จ๐Ÿ‡ฆ CPCSC (defence contractors)
    • ๐Ÿ‡ฆ๐Ÿ‡บ Essential Eight Level 3
    • ๐Ÿ‡ธ๐Ÿ‡ฌ Singapore CII operators

๐Ÿ“Š Observability

  • OpenTelemetry tracing with sensitive data sanitization
  • Security event logging
  • Audit trail support

๐Ÿ”’ Advanced Security Controls

  • Rate limiting for API endpoints and GraphQL operations
  • CSRF protection for mutations and forms
  • Security headers middleware for defense in depth
  • Input validation and sanitization
  • Field-level authorization with role inheritance
  • Row-level security via PostgreSQL RLS

๐Ÿ” Security Configuration โ€ข ๐ŸŒ Global Compliance Guide โ€ข ๐Ÿ“‹ KMS Architecture


๐Ÿค– Built for AI-Assisted Development

FraiseQL is the first GraphQL framework designed for the LLM era.

Clear Context in SQL Functions

CREATE OR REPLACE FUNCTION fn_create_user(
    p_email TEXT,
    p_name TEXT
) RETURNS JSONB AS $$
DECLARE
    v_user_id UUID;
BEGIN
    -- AI can see exactly what happens here
    -- No hidden ORM magic, no abstraction layers

    -- Validate email
    IF p_email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$' THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'Invalid email format'
        );
    END IF;

    -- Insert user
    INSERT INTO tb_user (email, name)
    VALUES (p_email, p_name)
    RETURNING id INTO v_user_id;

    -- Log for observability
    INSERT INTO audit_log (action, details, timestamp)
    VALUES ('user_created', jsonb_build_object('user_id', v_user_id), NOW());

    -- Return clear JSONB contract
    RETURN jsonb_build_object(
        'success', true,
        'user_id', v_user_id,
        'message', 'User created successfully'
    );
END;
$$ LANGUAGE plpgsql;

The entire business logic is in one place. LLMs don't need to guess about hidden ORM behavior.

Explicit Contracts

@input
class CreateUserInput:
    email: str  # AI sees exact input structure
    name: str

@success
class UserCreated:
    user_id: str  # AI sees success response
    message: str

@error
class ValidationError:
    error: str    # AI sees failure cases
    code: str = "VALIDATION_ERROR"

@fraiseql.mutation(function="fn_create_user", schema="public")
class CreateUser:
    input: CreateUserInput
    success: UserCreated
    failure: ValidationError

# That's it! FraiseQL automatically:
# 1. Calls public.fn_create_user(input) with input as dict
# 2. Parses JSONB result into UserCreated or ValidationError

Why AI Loves This

  • โœ… SQL + Python - Massively trained languages (no proprietary DSLs)
  • โœ… JSONB everywhere - Clear data structures, obvious contracts
  • โœ… Database functions - Complete context in one file
  • โœ… Explicit logging - AI can trace execution without debugging
  • โœ… No abstraction layers - What you see is what executes

Real Impact: Claude Code, GitHub Copilot, and ChatGPT generate correct FraiseQL code on first try.


๐Ÿ“– Core Concepts

New to FraiseQL? Understanding these core concepts will help you make the most of the framework:

๐Ÿ“š Concepts & Glossary - Essential terminology and mental models:

  • CQRS Pattern - Separate read models (views) from write models (functions)
  • Trinity Identifiers - Three-tier ID system (pk_*, id, identifier) for performance and UX
  • JSONB Views - PostgreSQL composes data once, eliminating N+1 queries
  • Database-First Architecture - Start with PostgreSQL, GraphQL follows
  • Explicit Sync Pattern - Table views (tv_*) for complex queries

Quick links:


โœจ See How Simple It Is

Complete CRUD API in 20 Lines

from uuid import UUID
from fraiseql import type, query, mutation, input, success
from fraiseql.fastapi import create_fraiseql_app

# Step 1: Map PostgreSQL view to GraphQL type
@fraiseql.type(sql_source="v_note", jsonb_column="data")
class Note:
    id: UUID
    title: str
    content: str | None

# Step 2: Define queries
@fraiseql.query
async def notes(info) -> list[Note]:
    """Get all notes."""
    db = info.context["db"]
    return await db.find("v_note")

@fraiseql.query
async def note(info, id: UUID) -> Note | None:
    """Get a note by ID."""
    db = info.context["db"]
    return await db.find_one("v_note", id=id)

# Step 3: Define mutations
@input
class CreateNoteInput:
    title: str
    content: str | None = None

@fraiseql.mutation
class CreateNote:
    input: CreateNoteInput
    success: Note

# Step 4: Create app
app = create_fraiseql_app(
    database_url="postgresql://localhost/mydb",
    types=[Note],
    queries=[notes, note],
    mutations=[CreateNote]
)

That's it. Your GraphQL API is ready.

The Database-First Pattern

-- PostgreSQL view explicitly defines what's exposed
CREATE VIEW v_user AS
SELECT
    id,
    jsonb_build_object(
        'id', id,
        'name', name,
        'email', email,
        -- password_hash, is_admin, api_key NOT included
        -- Impossible to accidentally expose them!
    ) as data
FROM tb_user;
# Python type mirrors EXACT view structure
@fraiseql.type(sql_source="v_user", jsonb_column="data")
class User:
    id: int
    name: str
    email: str
    posts: list[Post]  # Nested relations! No N+1 queries!

# Step 3: Query it
@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    return await db.find("v_user")

No ORM. No complex resolvers. PostgreSQL composes data, Rust transforms it.

Mutations with Business Logic

CREATE OR REPLACE FUNCTION fn_publish_post(p_post_id UUID) RETURNS JSONB AS $$
DECLARE
    v_post RECORD;
BEGIN
    -- Get post with user info (Trinity pattern: JOIN on pk_user)
    SELECT p.*, u.email as user_email
    INTO v_post
    FROM tb_post p
    JOIN tb_user u ON p.fk_user = u.pk_user  -- โœ… Trinity: INTEGER FK to pk_user
    WHERE p.id = p_post_id;

    -- Validate post exists
    IF NOT FOUND THEN
        RETURN jsonb_build_object('success', false, 'error', 'Post not found');
    END IF;

    -- Validate not already published
    IF v_post.published_at IS NOT NULL THEN
        RETURN jsonb_build_object('success', false, 'error', 'Post already published');
    END IF;

    -- Update post
    UPDATE tb_post
    SET published_at = NOW()
    WHERE id = p_post_id;

    -- Sync projection table
    PERFORM fn_sync_tv_post(p_post_id);

    -- Log event
    INSERT INTO audit_log (action, details)
    VALUES ('post_published', jsonb_build_object('post_id', p_post_id, 'user_email', v_post.user_email));

    -- Return success
    RETURN jsonb_build_object('success', true, 'post_id', p_post_id);
END;
$$ LANGUAGE plpgsql;

Business logic, validation, logging - all in the database function. Crystal clear for humans and AI.

Selective CASCADE Querying

Request only the CASCADE data you need:

mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    post { id title }

    # Option 1: No CASCADE (smallest payload)
    # Just omit the cascade field

    # Option 2: Metadata only
    cascade {
      metadata { affectedCount }
    }

    # Option 3: Full CASCADE
    cascade {
      updated { __typename id entity }
      deleted { __typename id }
      invalidations { queryName }
      metadata { affectedCount }
    }
  }
}

Performance: Not requesting CASCADE reduces response size by 2-10x.


๐Ÿ’ฐ In PostgreSQL Everything

Replace 4 services with 1 database.

Cost Savings Calculator

Traditional Stack FraiseQL Stack Annual Savings
PostgreSQL: $50/mo PostgreSQL: $50/mo -
Redis Cloud: $50-500/mo โœ… In PostgreSQL $600-6,000/yr
Sentry: $300-3,000/mo โœ… In PostgreSQL $3,600-36,000/yr
APM Tool: $100-500/mo โœ… In PostgreSQL $1,200-6,000/yr
Total: $500-4,050/mo Total: $50/mo $5,400-48,000/yr

How It Works

Caching (Replaces Redis)

from fraiseql.caching import PostgresCache

cache = PostgresCache(db_pool)
await cache.set("user:123", user_data, ttl=3600)

# Uses PostgreSQL UNLOGGED tables
# - No WAL overhead = fast writes
# - Shared across instances
# - TTL-based expiration
# - Pattern-based deletion

Error Tracking (Replaces Sentry)

from fraiseql.monitoring import init_error_tracker

tracker = init_error_tracker(db_pool, environment="production")
await tracker.capture_exception(error, context={...})

# Features:
# - Automatic error fingerprinting and grouping
# - Full stack trace capture
# - OpenTelemetry trace correlation
# - Custom notifications (Email, Slack, Webhook)

Observability (Replaces APM)

-- All traces and metrics stored in PostgreSQL
SELECT * FROM monitoring.traces
WHERE error_id = 'error-123'
  AND trace_id = 'trace-xyz';

Grafana Dashboards Pre-built dashboards in grafana/ query PostgreSQL directly:

  • Error monitoring dashboard
  • Performance metrics dashboard
  • OpenTelemetry traces dashboard

Operational Benefits

  • โœ… 70% fewer services to deploy and monitor
  • โœ… One database to backup (not 4 separate systems)
  • โœ… No Redis connection timeouts or cluster issues
  • โœ… No Sentry quota surprises or rate limiting
  • โœ… ACID guarantees for everything (no eventual consistency)
  • โœ… Self-hosted - full control, no vendor lock-in

๐Ÿ—๏ธ Architecture Deep Dive

Rust-First Execution

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   GraphQL       โ”‚ โ†’  โ”‚   PostgreSQL     โ”‚ โ†’  โ”‚   Rust          โ”‚
โ”‚   Request       โ”‚    โ”‚   JSONB Query    โ”‚    โ”‚   Transform     โ”‚
โ”‚                 โ”‚    โ”‚                  โ”‚    โ”‚   (7-10x faster)โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                                         โ†“
                                                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                                โ”‚   FastAPI       โ”‚
                                                โ”‚   HTTP Response โ”‚
                                                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Unified path for all queries:

  1. GraphQL query arrives at FastAPI
  2. Python resolver calls PostgreSQL view/function
  3. PostgreSQL returns pre-composed JSONB
  4. Rust pipeline transforms JSONB based on GraphQL selection
  5. FastAPI returns bytes directly (zero Python serialization)

CQRS Pattern

FraiseQL implements Command Query Responsibility Segregation:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚         GraphQL API                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚   QUERIES        โ”‚   MUTATIONS      โ”‚
โ”‚   (Reads)        โ”‚   (Writes)       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  v_* views       โ”‚  fn_* functions  โ”‚
โ”‚  tv_* tables     โ”‚  tb_* tables     โ”‚
โ”‚  JSONB ready     โ”‚  Business logic  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Queries use views:

  • v_* - Real-time views with JSONB computation
  • tv_* - Denormalized tables with generated JSONB columns (for complex queries)

Mutations use functions:

  • fn_* - Business logic, validation, side effects
  • tb_* - Base tables for data storage

๐Ÿ“Š Detailed Architecture Diagrams

Key Innovations

1. Exclusive Rust Pipeline

  • PostgreSQL โ†’ Rust โ†’ HTTP (no Python JSON processing)
  • 7-10x faster JSON transformation vs Python
  • No GIL contention, compiled performance

2. JSONB Views

  • Database composes data once
  • Rust selects fields based on GraphQL query
  • No N+1 query problems

3. Table Views (tv_*)

-- Denormalized JSONB table with explicit sync
CREATE TABLE tv_user (
    id INT PRIMARY KEY,
    data JSONB NOT NULL,  -- Regular column, not generated
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Sync function populates tv_* from v_* view
CREATE FUNCTION fn_sync_tv_user(p_user_id INT) RETURNS VOID AS $$
BEGIN
    INSERT INTO tv_user (id, data)
    SELECT id, data FROM v_user WHERE id = p_user_id
    ON CONFLICT (id) DO UPDATE SET
        data = EXCLUDED.data,
        updated_at = NOW();
END;
$$ LANGUAGE plpgsql;

-- Mutations call sync explicitly
CREATE FUNCTION fn_create_user(p_name TEXT) RETURNS JSONB AS $$
DECLARE v_user_id INT;
BEGIN
    INSERT INTO tb_user (name) VALUES (p_name) RETURNING id INTO v_user_id;
    PERFORM fn_sync_tv_user(v_user_id);  -- โ† Explicit sync call
    RETURN (SELECT data FROM tv_user WHERE id = v_user_id);
END;
$$ LANGUAGE plpgsql;

Benefits: Instant lookups, embedded relations, explicitly synchronized

4. Zero-Copy Response

  • Direct RustResponseBytes to FastAPI
  • No Python serialization overhead
  • Optimal for high-throughput APIs

๐ŸŽฏ How FraiseQL Is Different

Execution Path Comparison

Framework Data Flow JSON Processing Recursion Protection Security Model
FraiseQL PostgreSQL JSONB โ†’ Rust โ†’ HTTP โœ… Rust (compiled) โœ… View-enforced โœ… Explicit contracts
Strawberry + SQLAlchemy PostgreSQL โ†’ ORM โ†’ Python dict โ†’ JSON โŒ Python (2 steps) โš ๏ธ Middleware required โŒ ORM over-fetching risk
Hasura PostgreSQL โ†’ Haskell โ†’ JSON โš ๏ธ Haskell โš ๏ธ Middleware required โš ๏ธ Complex permission system
PostGraphile PostgreSQL โ†’ Node.js โ†’ JSON โš ๏ธ JavaScript โš ๏ธ Middleware required โš ๏ธ Plugin-based

FraiseQL's Unique Advantages

  • โœ… Database returns final structure (JSONB views)
  • โœ… Rust handles field selection (compiled performance)
  • โœ… No Python in hot path (zero serialization overhead)
  • โœ… No ORM abstraction (SQL functions are business logic)
  • โœ… Built-in recursion protection (view defines max depth, no middleware needed)
  • โœ… Secure by design (explicit field contracts prevent data leaks)
  • โœ… AI-readable (clear contracts, full context visible)
  • โœ… PostgreSQL-native (caching, monitoring, APQ in one database)

๐ŸŽฏ Advanced Features

Automatic Persisted Queries (APQ)

Enterprise-grade APQ with pluggable storage backends:

from fraiseql import FraiseQLConfig

# Memory backend (zero configuration)
config = FraiseQLConfig(apq_storage_backend="memory")

# PostgreSQL backend (multi-instance coordination)
config = FraiseQLConfig(
    apq_storage_backend="postgresql",
    apq_storage_schema="apq_cache"
)

How it works:

  1. Client sends query hash instead of full query
  2. FraiseQL checks storage backend for cached query
  3. PostgreSQL โ†’ Rust โ†’ HTTP (same fast path)
  4. Bandwidth reduction with large queries

โšก APQ Details

Specialized Type System

Advanced operators for network types, hierarchical data, ranges, and nested arrays:

query {
  servers(where: {
    ipAddress: { eq: "192.168.1.1" }          # โ†’ ::inet casting
    port: { gt: 1024 }                        # โ†’ ::integer casting
    location: { ancestor_of: "US.CA" }        # โ†’ ltree operations
    dateRange: { overlaps: "[2024-01-01,2024-12-31)" }

    # Nested array filtering with logical operators
    printServers(where: {
      AND: [
        { operatingSystem: { in: ["Linux", "Windows"] } }
        { OR: [
            { nTotalAllocations: { gte: 100 } }
            { NOT: { ipAddress: { isnull: true } } }
          ]
        }
      ]
    }) {
      hostname operatingSystem
    }
  }) {
    id name ipAddress port
  }
}

50+ Specialized Scalar Types:

Financial & Trading:

  • CUSIP, ISIN, SEDOL, MIC, LEI - Security identifiers
  • Money, Percentage, ExchangeRate - Financial values
  • CurrencyCode, StockSymbol - Trading symbols

Network & Infrastructure:

  • IPv4, IPv6, CIDR, MACAddress - Network addresses with subnet operations
  • Hostname, DomainName, Port, EmailAddress - Internet identifiers
  • APIKey, HashSHA256 - Security tokens

Geospatial & Location:

  • Coordinate, Latitude, Longitude - Geographic coordinates with distance calculations
  • PostalCode, Timezone - Location data

Business & Logistics:

  • ContainerNumber, FlightNumber, TrackingNumber, VIN - Asset identifiers
  • IBAN, LicensePlate - Financial & vehicle identifiers
  • PhoneNumber, LocaleCode, LanguageCode - Contact & localization

Technical & Data:

  • UUID, JSON, Date, DateTime, Time, DateRange - Standard types with validation
  • LTree - Hierarchical data with ancestor/descendant queries
  • SemanticVersion, Color, MIMEType, File, Image - Specialized formats
  • HTML, Markdown - Rich text content

Advanced Filtering: Full-text search, JSONB queries, array operations, regex, vector similarity search on all types

Scalar Type Usage Examples

from fraiseql import type
from fraiseql.types import (
    EmailAddress, PhoneNumber, Money, Percentage,
    CUSIP, ISIN, IPv4, MACAddress, LTree, DateRange
)

@fraiseql.type(sql_source="v_financial_data")
class FinancialRecord:
    id: int
    email: EmailAddress           # Validated email addresses
    phone: PhoneNumber           # International phone numbers
    balance: Money               # Currency amounts with precision
    margin: Percentage           # Percentages (0.00-100.00)
    security_id: CUSIP | ISIN    # Financial instrument identifiers

@fraiseql.type(sql_source="v_network_devices")
class NetworkDevice:
    id: int
    ip_address: IPv4             # IPv4 addresses with subnet operations
    mac_address: MACAddress      # MAC addresses with validation
    location: LTree              # Hierarchical location paths
    maintenance_window: DateRange # Date ranges with overlap queries
# Advanced filtering with specialized types
query {
  financialRecords(where: {
    balance: { gte: "1000.00" }           # Money comparison
    margin: { between: ["5.0", "15.0"] }   # Percentage range
    security_id: { eq: "037833100" }       # CUSIP validation
  }) {
    id balance margin security_id
  }

  networkDevices(where: {
    ip_address: { inSubnet: "192.168.1.0/24" }  # CIDR operations
    location: { ancestor_of: "US.CA.SF" }       # LTree hierarchy
    maintenance_window: { overlaps: "[2024-01-01,2024-12-31)" }
  }) {
    id ip_address location
  }
}

๐Ÿ“– Nested Array Filtering Guide

Enterprise Security

from fraiseql import authorized

@fraiseql.authorized(roles=["admin", "editor"])
@fraiseql.mutation
class DeletePost:
    """Only admins and editors can delete posts."""
    input: DeletePostInput
    success: DeleteSuccess
    failure: PermissionDenied

# Features:
# - Field-level authorization with role inheritance
# - Row-level security via PostgreSQL RLS
# - Unified audit logging with cryptographic chain (SHA-256 + HMAC)
# - Multi-tenant isolation
# - Rate limiting and CSRF protection

Trinity Identifiers

Three types of identifiers per entity for different purposes:

@fraiseql.type(sql_source="posts")
class Post(TrinityMixin):
    """
    Trinity Pattern:
    - pk_post (int): Internal SERIAL key (NOT exposed, only in database)
    - id (UUID): Public API key (exposed, stable)
    - identifier (str): Human-readable slug (exposed, SEO-friendly)
    """

    # GraphQL exposed fields
    id: UUID                  # Public API (stable, secure)
    identifier: str | None    # Human-readable (SEO-friendly, slugs)
    title: str
    content: str
    # ... other fields

    # pk_post is NOT a field - accessed via TrinityMixin.get_internal_pk()

Why three?

  • pk_*: Fast integer joins (PostgreSQL only, never in GraphQL schema)
  • id: Public API stability (UUID, exposed, never changes)
  • identifier: Human-friendly URLs (exposed, SEO, readability)

๐Ÿš€ Get Started in 5 Minutes

# Install
pip install fraiseql

# Create project
fraiseql init my-api
cd my-api

# Setup database
createdb my_api
psql my_api < schema.sql

# Start server
fraiseql dev

Your GraphQL API is live at http://localhost:8000/graphql ๐ŸŽ‰

Next Steps

๐Ÿ“š First Hour Guide - Build a complete blog API (60 minutes, hands-on) ๐Ÿง  Understanding FraiseQL - Architecture deep dive (10 minute read) โšก 5-Minute Quickstart - Copy, paste, run ๐Ÿ“– Full Documentation - Complete guides and references

Prerequisites

  • Python 3.13+ (required for Rust pipeline integration and advanced type features)
  • PostgreSQL 13+

๐Ÿ“– Detailed Installation Guide - Platform-specific instructions, troubleshooting


๐Ÿšฆ Is FraiseQL Right for You?

โœ… Perfect For

  • PostgreSQL-first teams already using PostgreSQL extensively
  • Performance-critical APIs requiring efficient data access
  • Multi-tenant SaaS with per-tenant isolation needs
  • Cost-conscious startups ($5-48K annual savings vs traditional stack)
  • AI-assisted development teams using Claude/Copilot/ChatGPT
  • Operational simplicity - one database for everything
  • Self-hosted infrastructure - full control, no vendor lock-in

โŒ Consider Alternatives

  • Multi-database support - FraiseQL is PostgreSQL-specific
  • Simple CRUD APIs - Traditional REST may be simpler
  • Non-PostgreSQL databases - FraiseQL requires PostgreSQL
  • Microservices - Better for monolithic or database-per-service

๐Ÿ› ๏ธ CLI Commands

# Project management
fraiseql init <name>           # Create new project
fraiseql dev                   # Development server with hot reload
fraiseql check                 # Validate schema and configuration

# Code generation
fraiseql generate schema       # Export GraphQL schema
fraiseql generate types        # Generate TypeScript definitions

# Database utilities
fraiseql sql analyze <query>   # Analyze query performance
fraiseql sql explain <query>   # Show PostgreSQL execution plan

๐Ÿ“š Learn More


๐Ÿค Contributing

We welcome contributions! See CONTRIBUTING.md for:

  • Development setup and testing
  • Architecture decisions and patterns
  • Code style and review process

Quick Start

git clone https://github.com/fraiseql/fraiseql
cd fraiseql && make setup-dev

Pre-commit with prek (7-10x faster)

FraiseQL uses prek - a Rust-based replacement for pre-commit:

# Install prek (faster than pre-commit)
brew install j178/tap/prek      # macOS
cargo install prek              # or via Rust

# Setup git hooks
prek install

# Run before committing
prek run --all

Why prek? โšก 7-10x faster than pre-commit, single binary, zero Python dependencies.

For more details: See .claude/CLAUDE.md or run make prek-list


๐Ÿ™ Acknowledgments

FraiseQL draws inspiration from:

  • Strawberry GraphQL - Excellent Python GraphQL library ("Fraise" = French for strawberry)
  • Harry Percival's "Architecture Patterns with Python" - Clean architecture and repository patterns
  • Eric Evans' "Domain-Driven Design" - Database-centric domain modeling
  • PostgreSQL community - For building the world's most advanced open source database

๐Ÿ‘จโ€๐Ÿ’ป About

FraiseQL is created by Lionel Hamayon (@evoludigit), a self-taught developer and founder of ร‰volution digitale.

Started: April 2025

The Origin Story

I built FraiseQL out of frustration with a stupid inefficiency: PostgreSQL returns JSON โ†’ Python deserializes to objects โ†’ GraphQL serializes back to JSON.

Why are we doing this roundtrip?

After years moving through Django, Flask, FastAPI, and Strawberry GraphQL with SQLAlchemy, I realized the entire approach was wrong. Just let PostgreSQL return the JSON directly. Skip the ORM. Skip the object mapping.

But I also wanted something designed for the LLM era. SQL and Python are two of the most massively trained languagesโ€”LLMs understand them natively. Why not make a framework where AI can easily get context and generate correct code?

FraiseQL is the result:

  • Database-first CQRS where PostgreSQL does what it does best
  • Rust pipeline for compiled performance (7-10x faster than Python JSON)
  • Python stays minimal - just decorators and type hints
  • LLM-readable by design - clear contracts, explicit logic

Full disclosure: I built this while compulsively preparing for scale I didn't have. But that obsession led somewhere realโ€”zero N+1 queries, efficient architecture, and a framework that both humans and AI can understand.

Connect:

Support FraiseQL:

  • โญ Star fraiseql/fraiseql
  • ๐Ÿ’ฌ Join discussions and share feedback
  • ๐Ÿค Contribute to the project

๐Ÿ“„ License

MIT License - see LICENSE for details.


๐Ÿ“‹ Project Navigation

Version Overview

Version Location Status Purpose For Users?
v1.8.0-beta.5 Root level Beta Security hardening + documentation improvements โœ… Production Ready
Rust Pipeline fraiseql_rs/ Integrated Included in v1.0+ โœ… Stable
v1.7.2 Superseded Legacy Use v1.8.0-beta.5 or wait for v1.8.0 stable โš ๏ธ Upgrade

New to FraiseQL? โ†’ First Hour Guide โ€ข Project Structure

๐Ÿ“– Complete Version Roadmap


Ready to build the most efficient GraphQL API in Python?

pip install fraiseql && fraiseql init my-api

๐Ÿš€ PostgreSQL โ†’ Rust โ†’ Production

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fraiseql-1.8.3.tar.gz (3.6 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fraiseql-1.8.3-cp314-cp314-win_amd64.whl (1.0 MB view details)

Uploaded CPython 3.14Windows x86-64

fraiseql-1.8.3-cp314-cp314-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

fraiseql-1.8.3-cp313-cp313-win_amd64.whl (1.0 MB view details)

Uploaded CPython 3.13Windows x86-64

fraiseql-1.8.3-cp313-cp313-manylinux_2_34_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

fraiseql-1.8.3-cp313-cp313-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

File details

Details for the file fraiseql-1.8.3.tar.gz.

File metadata

  • Download URL: fraiseql-1.8.3.tar.gz
  • Upload date:
  • Size: 3.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fraiseql-1.8.3.tar.gz
Algorithm Hash digest
SHA256 7e523592d4ffc3db1d8b3ece8286a445dcb0d75a43395f20012426c4e7553985
MD5 7b469cbf8999586fd0ac6c1e07b7fefd
BLAKE2b-256 cad7969a9b2958cf5097753df4a3a7a73cfd7303196984487c95296154538d5f

See more details on using hashes here.

File details

Details for the file fraiseql-1.8.3-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: fraiseql-1.8.3-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 1.0 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fraiseql-1.8.3-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 22c49b8ee92afe051ab741b7064cfcdd02e043ed62d69b2deb9ded572855f470
MD5 24480f7e2b3121e2213a86b8da356c8a
BLAKE2b-256 28ffd28e2e7bf1e83bea027a8f24aa64bebaf044301ac9a27da79dece91fc236

See more details on using hashes here.

File details

Details for the file fraiseql-1.8.3-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

  • Download URL: fraiseql-1.8.3-cp314-cp314-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.14, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fraiseql-1.8.3-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 809b2e22d7d9cd24acbc3fbe34072e7e8eed62fdaddee9f776ae42e4fcf177c9
MD5 ba894574ae3b3a377a9fd228948e9525
BLAKE2b-256 3157418f7cbf96f09114c5363a6a0502d3ff19e35c5a88b9958f32f4b1aebd54

See more details on using hashes here.

File details

Details for the file fraiseql-1.8.3-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: fraiseql-1.8.3-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 1.0 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fraiseql-1.8.3-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 20c593e8897fce163309e6159b28d9414317be1d273963647753efce6de3786e
MD5 43f88fae45d2fb2152fc66f6f07ea44e
BLAKE2b-256 42e1cc0cd39a89ef77da0f70e9c7245f7a0d6052101f98b838e89e8f15a506a4

See more details on using hashes here.

File details

Details for the file fraiseql-1.8.3-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

  • Download URL: fraiseql-1.8.3-cp313-cp313-manylinux_2_34_x86_64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.13, manylinux: glibc 2.34+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fraiseql-1.8.3-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 6ec5b4463b0b7e09b44d92803b59f2b3eac060dacd8b0b5c598896adc0f3d149
MD5 f26f4660b83fc44e9bc68fdf60ed4cca
BLAKE2b-256 e4cec66e2f57010a550b06205c653b72b70132c9779dcf475af3c24184a51754

See more details on using hashes here.

File details

Details for the file fraiseql-1.8.3-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

  • Download URL: fraiseql-1.8.3-cp313-cp313-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.13, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fraiseql-1.8.3-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9c69d56c0a9d0834891966f95bf16270621320bf1b6cf1e03378194600ef6581
MD5 4032216607bec7e9f33cb6a2d5edb262
BLAKE2b-256 9aea6d15a3ecf504a4c386ece2cf1e18866cfd71f94acea788615d818e0270c7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page