For Developers
Build APIs the way you think about data
Your Problem
You're tired of:
- Writing field resolvers for every relationship
- Fighting N+1 queries with DataLoaders
- Debugging why a simple query runs 50 database hits
- Spending more time optimizing than building features
You know SQL. You understand your database. You just want to serve it as GraphQL.
What FraiseQL Gives You
✅ Think in SQL
Write SQL views. Compose JSONB. You understand exactly what's happening.
No magic. No ORM generating queries you didn't ask for.
✅ Map to Types
Define types in your language. FraiseQL validates that types match views.
# Your schema matches your database
@fraiseql.type
class User:
id: ID
name: str
email: str ✅ No Field Resolvers
No per-field queries. No N+1 prevention logic. Just queries and mutations.
# One query. One database hit. Done.
@fraiseql.query(sql_source="v_user")
def user(id: ID) -> User:
pass ✅ Deploy with Confidence
Views compile at deploy time. Errors caught before production.
No runtime schema interpretation. Just execute.
How FraiseQL vs Traditional GraphQL
❌ Traditional GraphQL
# Fetch user
@resolver
async def user(info, id) -> User:
return db.find_user(id)
# Fetch user + posts (needs resolver)
@resolver
async def posts(parent, info) -> List[Post]:
posts = db.find_posts(parent.id) # Separate query!
return posts
# Result: N+1 problem
# 1 user query + 1 posts query per user = 1 + N queries ✅ FraiseQL
# SQL view pre-composes everything
CREATE VIEW v_user_with_posts AS
SELECT id, jsonb_build_object(
'name', name,
'posts', (SELECT jsonb_agg(...))
) AS data
FROM tb_user;
# Single resolver
@fraiseql.query(sql_source="v_user_with_posts")
def user(id: ID) -> User:
pass
# Result: 1 query for user + posts Your Learning Path
Understand: 20 minutes
Learn how FraiseQL works under the hood and why it's different.
How It Works →Think: 30 minutes
Explore the philosophy. Why database-first? Why CQRS? Why compilation?
Philosophy →What You Can Build
REST → GraphQL Migration
Have a REST API? Add GraphQL without rewriting everything.
Define views for existing tables. Map to types. Done.
Dashboards & Analytics
Complex queries with multiple tables? Pre-compose in views.
Serve aggregations as GraphQL.
Real-time APIs
Use subscriptions on top of pre-composed views.
Performance scales naturally.
Multi-tenant SaaS
Filter at database level with views.
Security and performance in one place.
Mobile Backends
Mobile apps need efficient APIs. FraiseQL delivers.
One query per request. Minimal bandwidth.
Complex Relationships
Posts with comments, likes, author info?
Compose once in views. Query once from API.
Choose Your Language
Python
Most popular for data work
@fraiseql.query(sql_source="v_users")
def users(limit: int = 100) -> list[User]:
pass TypeScript
Full type safety in Node
@fraiseqlQuery({ sqlSource: "v_users" })
function users(limit: number = 100): User[] {
// authoring only
} Go
Performance and simplicity
func (r *Query) Users(ctx context.Context) ([]*User, error) {
// authoring only
} PHP
For existing PHP backends
#[FraiseQLQuery(sqlSource: "v_users")]
function users(int $limit = 100): array {
// authoring only
} Works With Your Stack
Databases
- PostgreSQL
- MySQL / MariaDB
- SQLite
Authentication
- Auth0
- Firebase
- Clerk
- Custom JWT
Caching
- Redis
- Memcached
- In-memory
Deployment
- Docker
- Kubernetes
- Railway
- Fly.io
- AWS
Common Questions
Q: How long to learn?
A: 10-30 minutes to understand. 1-2 hours to build something real. You already know SQL and your language.
Q: Can I use existing databases?
A: Yes! Add views to your existing schema. No migration needed.
Q: What about complex logic?
A: Compose data in views. Implement logic in SQL functions (fn_*) that back your mutations — the database is where the logic lives.
Q: How do I debug?
A: Views are SQL. Run EXPLAIN ANALYZE to see query plans. One query per request means clear debugging.
Q: Is it production-ready?
A: Yes. Used in production at several companies. Compile-time validation means fewer bugs.