Skip to content

Beginner Learning Path

Complete pathway from zero to building production GraphQL APIs with FraiseQL.

Time: 2-3 hours Prerequisites: Python 3.13+, PostgreSQL 13+, basic SQL knowledge

📍 Navigation: ← QuickstartCore Concepts → • Examples (../../examples/)

Learning Journey

Phase 1: Quick Start (15 minutes)

  1. 5-Minute Quickstart
  2. Build working API immediately
  3. Understand basic pattern
  4. Test in GraphQL Playground

  5. Verify Your Setup

    # Check installations
    python --version  # 3.11+
    psql --version    # PostgreSQL client
    
    # Test quickstart
    python app.py
    # Open http://localhost:8000/graphql
    

You should see: GraphQL Playground with your API schema


Phase 2: Core Concepts (30 minutes)

  1. Database API (Focus: select_from_json_view)
  2. Repository pattern
  3. QueryOptions for filtering
  4. Pagination with PaginationInput
  5. Ordering with OrderByInstructions

  6. Types and Schema (Focus: @type decorator)

  7. Python type hints → GraphQL types
  8. Optional fields with | None
  9. Lists with list[Type]

Practice Exercise:

import fraiseql
from fraiseql.types import ID

# Create a simple Note API
@fraiseql.type(sql_source="v_note")
class Note:
    id: ID
    title: str
    content: str
    created_at: datetime

@fraiseql.query
def notes() -> list[Note]:
    """Get all notes."""
    pass  # Implementation handled by framework


Phase 3: N+1 Prevention (30 minutes)

  1. Database Patterns (Focus: JSONB Composition)
  2. Composed views prevent N+1 queries
  3. jsonb_build_object pattern
  4. COALESCE for empty arrays

Key Pattern:

-- Instead of N queries, compose in view:
CREATE VIEW v_user_with_posts AS
SELECT
    u.id,
    jsonb_build_object(
        'id', u.id,
        'name', u.name,
        'posts', COALESCE(
            (SELECT jsonb_agg(jsonb_build_object(
                'id', p.id,
                'title', p.title
            ) ORDER BY p.created_at DESC)
            FROM tb_post p WHERE p.fk_author = u.pk_user),
            '[]'::jsonb
        )
    ) AS data
FROM tb_user u;

Practice: Add comments to your Note API using composition


Phase 4: Mutations (30 minutes)

  1. Blog API Tutorial (Focus: Mutations section)
  2. PostgreSQL functions for business logic
  3. fn_ naming convention
  4. Calling functions from Python

Mutation Pattern:

-- PostgreSQL function
CREATE FUNCTION fn_create_note(
    p_user_id UUID,
    p_title TEXT,
    p_content TEXT
) RETURNS UUID AS $$
DECLARE
    v_note_id UUID;
    v_user_pk INT;
BEGIN
    -- Get user's internal pk
    SELECT pk_user INTO v_user_pk FROM tb_user WHERE id = p_user_id;

    INSERT INTO tb_note (fk_user, title, content)
    VALUES (v_user_pk, p_title, p_content)
    RETURNING id INTO v_note_id;

    RETURN v_note_id;
END;
$$ LANGUAGE plpgsql;

import fraiseql

# Python mutation
@fraiseql.mutation
def create_note(title: str, content: str) -> Note:
    """Create a new note."""
    pass  # Implementation handled by framework

Phase 5: Complete Example (45 minutes)

  1. Blog API Tutorial (Complete walkthrough)
  2. Users, posts, comments
  3. Threaded comments
  4. Production patterns

Build the full blog API - This solidifies everything you've learned.


Skills Checklist

After completing this path:

✅ Create PostgreSQL views with JSONB data column ✅ Define GraphQL types with Python type hints ✅ Write queries using repository pattern ✅ Prevent N+1 queries with view composition ✅ Implement mutations via PostgreSQL functions ✅ Use GraphQL Playground for testing ✅ Understand CQRS architecture ✅ Handle pagination and filtering

Common Beginner Mistakes

❌ Mistake 1: No ID column in view

-- WRONG: Can't filter efficiently
CREATE VIEW v_user AS
SELECT jsonb_build_object(...) AS data
FROM tb_user;

-- CORRECT: Include ID for WHERE clauses
CREATE VIEW v_user AS
SELECT
    id,  -- ← Include this!
    jsonb_build_object(...) AS data
FROM tb_user;

❌ Mistake 2: Missing return type

import fraiseql

# WRONG: No type hint
@fraiseql.query
async def users(info):
    ...

# CORRECT: Always specify return type
@fraiseql.query
async def users(info) -> list[User]:
    ...

❌ Mistake 3: Not handling NULL

import fraiseql

# WRONG: Crashes on NULL
@fraiseql.type
class User:
    bio: str  # What if bio is NULL?

# CORRECT: Use | None for nullable fields
@fraiseql.type
class User:
    bio: str | None

❌ Mistake 4: Forgetting COALESCE in arrays

-- WRONG: Returns NULL instead of empty array
'posts', (SELECT jsonb_agg(...) FROM tb_post)

-- CORRECT: Use COALESCE
'posts', COALESCE(
    (SELECT jsonb_agg(...) FROM tb_post),
    '[]'::jsonb
)

Quick Reference Card

Essential Pattern

import fraiseql
from fraiseql.types import ID

# 1. Define type
@fraiseql.type(sql_source="v_item")
class Item:
    id: ID
    name: str

# 2. Create view (in PostgreSQL)
CREATE VIEW v_item AS
SELECT
    id,
    jsonb_build_object(
        '__typename', 'Item',
        'id', id,
        'name', name
    ) AS data
FROM tb_item;

# 3. Query
@fraiseql.query
def items() -> list[Item]:
    """Get all items."""
    pass  # Implementation handled by framework

Essential Commands

# Install
pip install fraiseql fastapi uvicorn

# Create database
createdb myapp

# Run app
python app.py
# Open http://localhost:8000/graphql

# Test SQL view
psql myapp -c "SELECT * FROM v_item LIMIT 1;"

Next Steps

Continue Learning

Backend Focus: - Database Patterns - tv_ pattern, entity change log - Performance - Rust transformation, APQ caching - Multi-Tenancy - Tenant isolation

Production Ready: - Production Deployment - Docker, monitoring, security - Authentication - User auth patterns - Monitoring - Observability

Practice Projects

  1. Todo API - Basic CRUD with users
  2. Recipe Manager - Nested ingredients and steps
  3. Event Calendar - Date filtering and recurring events
  4. Chat App - Real-time messages with threads
  5. E-commerce - Products, orders, inventory

Troubleshooting

"View not found" error - Check view name has v_ prefix - Verify view exists: \dv v_* in psql - Ensure view has data column

Type errors - Match Python types to PostgreSQL types - Use UUID not str for UUIDs - Add | None for nullable fields

N+1 queries detected - Compose data in views, not in resolvers - Use jsonb_agg for arrays - Check Database Patterns

Tips for Success

💡 Start simple - Master basics before advanced patterns 💡 Test SQL first - Verify views in psql before using in Python 💡 Read errors carefully - FraiseQL provides detailed error messages 💡 Use Playground - Test queries interactively before writing code 💡 Learn PostgreSQL - FraiseQL power comes from PostgreSQL features

Congratulations! 🎉

You've mastered FraiseQL fundamentals. You can now build type-safe, high-performance GraphQL APIs with PostgreSQL.

Remember: The better you know PostgreSQL, the more powerful your FraiseQL APIs become.

See Also