Skip to content

Quick Start Guide

Get started with FraiseQL in 5 minutes! This guide will walk you through creating a simple note-taking GraphQL API.

Prerequisites

  • Python 3.13+ (required for FraiseQL's Rust pipeline and advanced features)
  • PostgreSQL 13+

Step 1: Install FraiseQL

pip install fraiseql[all]

Step 2: Create Database

Create a PostgreSQL database for your notes:

createdb quickstart_notes

Step 3: Set Up Database Schema

Create a file called schema.sql with this content:

-- Simple notes table with trinity pattern
CREATE TABLE tb_note (
    pk_note INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,  -- Internal fast joins
    id UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),         -- Public API
    identifier TEXT UNIQUE,                                     -- Optional human-readable identifier
    title VARCHAR(200) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Notes view for GraphQL queries
CREATE VIEW v_note AS
SELECT
    id,
    jsonb_build_object(
        'id', id,                            -- UUID for GraphQL API
        'title', title,
        'content', content,
        'created_at', created_at
    ) AS data
FROM tb_note;

-- SQL function for creating notes (CQRS pattern)
CREATE OR REPLACE FUNCTION fn_create_note(input jsonb)
RETURNS jsonb AS $$
DECLARE
    new_id uuid;
BEGIN
    INSERT INTO tb_note (title, content)
    VALUES (input->>'title', input->>'content')
    RETURNING id INTO new_id;

    RETURN jsonb_build_object('success', true, 'id', new_id);
END;
$$ LANGUAGE plpgsql;

-- Sample data
INSERT INTO tb_note (title, content) VALUES
    ('Welcome to FraiseQL', 'This is your first note!'),
    ('Type-safe queries', 'Automatic GraphQL schema from PostgreSQL views'),
    ('Database-first design', 'Views compose data for optimal performance');

Run the schema:

psql quickstart_notes < schema.sql

Step 4: Create Your GraphQL API

Create a file called app.py with this complete code:

from datetime import datetime
import uvicorn
import fraiseql
from fraiseql.fastapi import create_fraiseql_app
from fraiseql.types import ID  # Use ID for entity identifiers

# Define GraphQL types
@fraiseql.type(sql_source="v_note", jsonb_column="data")
class Note:
    """A simple note with title and content.

    Fields:
        id: Unique note identifier
        title: Note title (max 200 characters)
        content: Note content in plain text
        created_at: When the note was created
    """
    id: ID  # External UUID identifier (Trinity pattern)
    title: str
    content: str | None
    created_at: datetime

# Define input types
@fraiseql.input
class CreateNoteInput:
    """Input for creating a new note."""
    title: str
    content: str | None = None

# Define success/error types
@fraiseql.success
class CreateNoteSuccess:
    """Success response for note creation."""
    note: Note
    # Note: @success auto-injects: status, message, updated_fields, id

@fraiseql.error
class ValidationError:
    """Validation error."""
    message: str
    code: str = "VALIDATION_ERROR"

# Queries
@fraiseql.query
async def notes(info) -> list[Note]:
    """Get all notes."""
    db = info.context["db"]
    # field_name auto-inferred from function name "notes"
    return await db.find("v_note", order_by=[("created_at", "DESC")])

@fraiseql.query
async def note(info, id: ID) -> Note | None:
    """Get a single note by ID."""
    db = info.context["db"]
    # field_name auto-inferred from function name "note"
    return await db.find_one("v_note", id=id)

# Mutations
@fraiseql.mutation
class CreateNote:
    """Create a new note."""
    input: CreateNoteInput
    success: CreateNoteSuccess
    failure: ValidationError

    async def resolve(self, info) -> CreateNoteSuccess | ValidationError:
        db = info.context["db"]

        try:
            # Call SQL function (CQRS pattern)
            result = await db.execute_function("fn_create_note", {
                "title": self.input.title,
                "content": self.input.content,
            })

            if not result.get("success"):
                return ValidationError(message="Failed to create note")

            # Fetch the created note via Rust pipeline
            note = await db.find_one("v_note", id=result["id"])
            return CreateNoteSuccess(note=note)

        except Exception as e:
            return ValidationError(message=f"Failed to create note: {e!s}")

# Create the app
QUICKSTART_TYPES = [Note]
QUICKSTART_QUERIES = [notes, note]
QUICKSTART_MUTATIONS = [CreateNote]

if __name__ == "__main__":
    import os

    # Database URL (override with DATABASE_URL environment variable)
    database_url = os.getenv("DATABASE_URL", "postgresql://localhost/quickstart_notes")

    app = create_fraiseql_app(
        database_url=database_url,
        types=QUICKSTART_TYPES,
        queries=QUICKSTART_QUERIES,
        mutations=QUICKSTART_MUTATIONS,
        title="Notes API",
        description="Simple note-taking GraphQL API",
        production=False,  # Enable GraphQL playground
    )

    print("🚀 Notes API running at http://localhost:8000/graphql")
    print("📖 GraphQL Playground: http://localhost:8000/graphql")

    uvicorn.run(app, host="0.0.0.0", port=8000)

Step 5: Run Your API

python app.py

Visit http://localhost:8000/graphql to open the GraphQL Playground!

Step 6: Try Your First Queries

Get all notes:

query {
  notes {
    id
    title
    content
    createdAt
  }
}

Get a specific note:

query {
  note(id: "550e8400-e29b-41d4-a716-446655440000") {
    id
    title
    content
    createdAt
  }
}

Note: Replace the UUID with an actual ID from your database. You can get IDs from the notes query above.

Create a new note:

mutation {
  createNote(input: { title: "My New Note", content: "GraphQL mutations made simple" }) {
    ... on CreateNoteSuccess {
      note {
        id
        title
        content
        createdAt
      }
      message
    }
    ... on ValidationError {
      message
      code
    }
  }
}

What Just Happened?

🎉 Congratulations! You just built a complete GraphQL API with:

  • Database Schema: PostgreSQL table and JSONB view
  • GraphQL Types: Note type with proper typing
  • Queries: Get all notes and get note by ID
  • Mutations: Create new notes with success/failure handling
  • FastAPI Integration: Ready-to-deploy web server

💡 Best Practices

Import Style

Always use namespaced imports to avoid shadowing Python builtins:

# ✅ RECOMMENDED (safe, clear)
import fraiseql

@fraiseql.type(sql_source="v_user")
class User: ...

@fraiseql.input
class CreateUserInput: ...

Alternative with explicit imports:

# ✅ SAFE (explicit names)
from fraiseql import fraise_type, fraise_input

@fraise_type(sql_source="v_user")
class User: ...

@fraise_input
class CreateUserInput: ...

⚠️ AVOID - Shadows Python builtins:

# ❌ DANGEROUS - shadows builtin type() and input()
from fraiseql import type, input

@type  # Now you can't use Python's type() function!
class User: ...

Auto-Inference Magic

FraiseQL automatically infers parameters to reduce boilerplate:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    # field_name auto-inferred from function name "users"
    # info parameter auto-injected from GraphQL context
    return await db.find("v_user")

The GraphQL response will automatically use the correct field name:

{
  "data": {
    "users": [...]  // ← Matches function name
  }
}

Next Steps

Continue Learning

Decision Support

Practical Guides

Need Help?


Continue with the First Hour Tutorial for hands-on examples.