
Security News
Browserslist-rs Gets Major Refactor, Cutting Binary Size by Over 1MB
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
A GraphQL-to-PostgreSQL translator with a CQRS architecture
Views for queries. Functions for mutations. GraphQL for developers.
FraiseQL is a Python framework that translates GraphQL queries directly into PostgreSQL queries, embracing a CQRS (Command Query Responsibility Segregation) architecture where database views handle queries and PostgreSQL functions handle mutations.
Getting Started | Reference | Learn More |
---|---|---|
š Getting Started Guide | š API Reference | šļø Architecture |
š Query Patterns | š§ Common Patterns | š Migration Guide |
š Filtering Patterns | š” Examples | š Contributing |
š WHERE Types Guide | š Partial Instantiation | |
ā Troubleshooting |
FraiseQL has four fundamental patterns:
@fraise_type
class User:
id: UUID
name: str
email: str
@fraiseql.query
async def get_user(info, id: UUID) -> User:
# 'info' is ALWAYS first parameter
db = info.context["db"]
return await db.find_one("user_view", id=id)
CREATE VIEW user_view AS
SELECT
id, -- For filtering
tenant_id, -- For access control
jsonb_build_object(
'id', id,
'name', name,
'email', email
) as data -- REQUIRED: All object data here
FROM users;
# No manual connection management needed
db = info.context["db"] # FraiseQLRepository
user = await db.find_one("user_view", id=user_id)
Feature | Description |
---|---|
š Simple Patterns | Decorator-based API with no resolver classes |
š JSONB-First | All data flows through JSONB columns for consistency |
š Type-Safe | Full type hints with Python 3.11+ |
š”ļø SQL Injection Safe | Parameterized queries throughout |
š Pluggable Auth | Built-in Auth0, easy to add others |
ā” FastAPI Integration | Production-ready ASGI application |
šļø High Performance | Direct SQL queries, no N+1 problems |
šÆ CQRS Architecture | Views for queries, functions for mutations |
pip install fraiseql
# With optional features:
pip install "fraiseql[auth0]" # Auth0 authentication
pip install "fraiseql[tracing]" # OpenTelemetry tracing
pip install "fraiseql[dev]" # Development dependencies
ā ļø Breaking Changes:
- v0.1.0a14: All database views must now return data in a JSONB
data
column. See the Migration Guide- v0.1.0a18: Partial object instantiation is now supported in development mode, allowing nested queries to request only specific fields
import fraiseql
from datetime import datetime
from uuid import UUID, uuid4
# Define a type
@fraise_type
class Book:
id: UUID
title: str
author: str
published: datetime
# Create a query (NOT a resolver!)
@fraiseql.query
async def books(info) -> list[Book]:
"""Get all books."""
# 'info' is ALWAYS the first parameter
return [
Book(
id=uuid4(),
title="The Great Gatsby",
author="F. Scott Fitzgerald",
published=datetime(1925, 4, 10)
)
]
# Create the app
app = fraiseql.create_fraiseql_app(
types=[Book],
production=False # Enables GraphQL Playground
)
# Run with: uvicorn app:app --reload
# Visit: http://localhost:8000/graphql
# First, create your database view with JSONB data column:
"""
CREATE VIEW book_view AS
SELECT
id, -- For filtering
author, -- For author queries
published, -- For date filtering
jsonb_build_object(
'id', id,
'title', title,
'author', author,
'published', published
) as data -- REQUIRED: All object data here!
FROM books;
"""
# Then create your query:
@fraiseql.query
async def books(info, author: str | None = None) -> list[Book]:
"""Get books, optionally filtered by author."""
db = info.context["db"] # FraiseQLRepository
if author:
return await db.find("book_view", author=author)
return await db.find("book_view")
# Create app with database
app = fraiseql.create_fraiseql_app(
database_url="postgresql://localhost/mydb",
types=[Book],
production=False
)
# ā WRONG: Don't use resolver classes
class Query:
async def resolve_users(self, info):
pass
# ā
CORRECT: Use @fraiseql.query decorator
@fraiseql.query
async def users(info) -> list[User]:
db = info.context["db"]
return await db.find("user_view")
# ā WRONG: Don't forget the data column
CREATE VIEW bad_view AS
SELECT id, name, email FROM users;
# ā
CORRECT: Always include JSONB data column
CREATE VIEW good_view AS
SELECT id, jsonb_build_object(
'id', id, 'name', name, 'email', email
) as data FROM users;
š See the Getting Started Guide for a complete walkthrough
FraiseQL now supports partial object instantiation for nested queries in development mode. This means you can request only the fields you need from nested objects without errors:
query GetUsers {
users {
id
name
profile {
avatar # Only request avatar, not all profile fields
}
}
}
@fraise_type
class Profile:
id: UUID
avatar: str
email: str # Required but not requested - no error!
bio: str # Required but not requested - no error!
This brings FraiseQL closer to GraphQL's promise of "ask for what you need, get exactly that". See the Partial Instantiation Guide for details.
With partial instantiation, you can now build efficient queries that traverse multiple levels of relationships:
query BlogDashboard {
posts(where: { published_at: { neq: null } }) {
id
title
published_at
author {
name
profile {
avatar # Only need avatar for display
}
}
comments {
id
content
author {
name # Only need commenter's name
}
}
}
}
All nested objects will be properly instantiated with only the requested fields, avoiding errors from missing required fields in the type definitions.
Traditional GraphQL servers require complex resolver hierarchies, N+1 query problems, and lots of boilerplate.
Topic | Description |
---|---|
Query Patterns | How to write queries the FraiseQL way |
JSONB Pattern | Why all data goes in a JSONB column |
Multi-Tenancy | Building SaaS applications |
Authentication | Adding auth to your API |
Testing | Our unified container approach |
Here's how you might structure a blog application:
@fraise_type
class Post:
id: UUID
title: str
content: str
author: User
comments: list['Comment']
tags: list[str]
published_at: datetime | None
@fraise_type
class Comment:
id: UUID
content: str
author: User
created_at: datetime
With a corresponding view:
CREATE VIEW post_details AS
SELECT
p.id, -- For filtering
p.author_id, -- For joins
p.published_at, -- For filtering by date
jsonb_build_object(
'id', p.id,
'title', p.title,
'content', p.content,
'author', (
SELECT data FROM user_profile
WHERE id = p.author_id -- Use id column for filtering
),
'comments', (
SELECT jsonb_agg(
jsonb_build_object(
'id', c.id,
'content', c.content,
'author', (
SELECT data FROM user_profile
WHERE id = c.author_id -- Use id column for filtering
),
'created_at', c.created_at
)
ORDER BY c.created_at
)
FROM comments c
WHERE c.post_id = p.id
),
'tags', p.tags,
'published_at', p.published_at
) as data -- All object data in 'data' column
FROM posts p;
FraiseQL includes a pluggable authentication system:
from fraiseql.auth.decorators import requires_auth
from fraiseql.auth.auth0 import Auth0Config
@fraise_type
class Query:
@requires_auth
async def me(self, info) -> User:
# info.context["user"] contains authenticated user info
user_id = info.context["user"].user_id
# Fetch and return user...
app = create_fraiseql_app(
database_url="postgresql://localhost/mydb",
auth=Auth0Config(
domain="your-domain.auth0.com",
api_identifier="https://api.example.com"
),
types=[User, Query],
)
FraiseQL's design makes it exceptionally well-suited for AI-assisted development:
@fraise_type
, @fraise_input
)When you ask an LLM to generate a FraiseQL API, it can reliably produce:
# LLMs can easily generate this pattern
@fraise_type
class Product:
id: UUID
name: str
price: Decimal
in_stock: bool
# And the corresponding SQL view
"""
CREATE VIEW product_catalog AS
SELECT
id, -- For filtering
category_id, -- For joins
jsonb_build_object(
'id', id,
'name', name,
'price', price,
'in_stock', quantity > 0
) as data -- All product data in 'data' column
FROM products;
"""
This simplicity means:
# Clone the repo
git clone https://github.com/fraiseql/fraiseql.git
cd fraiseql
# Create virtual environment
python -m venv .venv
source .venv/bin/activate
# Install in development mode
pip install -e ".[dev]"
# Run tests (uses unified container for performance)
pytest # Automatically detects available container runtime
# Or explicitly with Podman (recommended for socket performance)
TESTCONTAINERS_PODMAN=true pytest
# Skip container-based tests if no runtime available
pytest -m "not docker"
FraiseQL uses a unified container approach for testing - a single PostgreSQL container runs for the entire test session with socket-based communication, providing 5-10x faster test execution.
Tests requiring containers are automatically skipped if neither is available. See docs/testing/unified-container-testing.md for architecture details.
# Linting
ruff check src/
# Type checking
pyright
# Format code
ruff format src/ tests/
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
"Fraise" is French for strawberry. This project was heavily inspired by the excellent Strawberry GraphQL library, whose elegant API design showed us how delightful Python GraphQL development could be. While we take a different architectural approach, we aim to preserve that same developer-friendly experience.
FraiseQL is in active development. We're working on:
MIT License - see LICENSE for details.
FraiseQL: Where GraphQL meets PostgreSQL. š
FAQs
Lightweight GraphQL-to-PostgreSQL query builder using jsonb
We found that fraiseql demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Ā It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
Research
Security News
Eight new malicious Firefox extensions impersonate games, steal OAuth tokens, hijack sessions, and exploit browser permissions to spy on users.
Security News
The official Go SDK for the Model Context Protocol is in development, with a stable, production-ready release expected by August 2025.