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¶
Step 2: Create Database¶
Create a PostgreSQL database for your 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:
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¶
Visit http://localhost:8000/graphql to open the GraphQL Playground!
Step 6: Try Your First Queries¶
Get all notes:¶
Get a specific note:¶
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:
Next Steps¶
Continue Learning¶
- First Hour Guide - Progressive hands-on tutorial
- Core Concepts & Glossary - Understand CQRS, Trinity pattern, and JSONB views
- Auto-Inference Guide - Learn how FraiseQL reduces boilerplate
Decision Support¶
- Decision Matrices - Choose the right patterns (views vs projection tables, mutation patterns, etc.)
- Understanding FraiseQL - Architecture and philosophy
Practical Guides¶
- Blog API Tutorial - Build a complete API from scratch
- Troubleshooting - Common issues and solutions
- Examples - More complete examples
Need Help?¶
Continue with the First Hour Tutorial for hands-on examples.