Performance Optimization
Optimize slow queries and reduce database load in production. Performance Guide
This guide helps you diagnose and fix common issues in FraiseQL applications.
If FraiseQL isn’t working, start with fraiseql doctor — it runs checks covering schema, config, database, secrets, and feature coherence in a single command:
fraiseql doctorIf doctor passes but the issue persists, continue with the manual checklist below. To update the schema without restarting, use kill -USR1 or the admin reload endpoint.
Check the FraiseQL version
fraiseql --versionValidate schema JSON structure
fraiseql validate schema.jsonfraiseql validate checks the compiled schema JSON for valid type references and circular dependencies. It does not connect to the database. To check your database connection, run:
psql $DATABASE_URL -c "SELECT 1"Compile schema with verbose output
fraiseql compile --verboseError example:
Error: Syntax error in schema → Line 5: expected ':'Cause: Python syntax error in schema file.
Fix: Check the indicated line for syntax issues:
# Wrong@fraiseql.typeclass User id: str
# Correct@fraiseql.typeclass User: id: strError example:
Error: Type 'Author' not found -> Referenced in Post.author but not definedCause: Referenced type doesn’t exist in schema.
Fix: Define the missing type or fix the reference:
@fraiseql.typeclass User: # Define the type id: str name: str
@fraiseql.typeclass Post: author: User # Now validError example:
Error: Circular reference detected -> User -> Post -> UserCause: Types reference each other in a cycle.
Fix: Use forward references with strings:
@fraiseql.typeclass User: id: str posts: list['Post'] # Forward reference
@fraiseql.typeclass Post: author: 'User' # Forward reference# Error: Missing colon after class definition@fraiseql.typeclass User # ← SyntaxError: expected ':' id: str
# Fix:@fraiseql.typeclass User: id: str
# Error: Wrong annotation syntax@fraiseql.typeclass Post: tags: List[str] # ← NameError: 'List' not defined
# Fix: use built-in list@fraiseql.typeclass Post: tags: list[str]// Error: Missing resolver return type@ObjectType()class User { @Field() id: string; // ← Error: field type must be explicit
// Fix: use explicit Field type decorator @Field(() => String) id: string;}
// Error: Nullable field not declared@ObjectType()class Post { @Field() publishedAt: Date; // ← Error: cannot return null for non-nullable field
// Fix: mark as nullable @Field(() => Date, { nullable: true }) publishedAt?: Date;}Error:
Error: Failed to connect to database -> Connection refused (os error 111)Causes:
Check the server is running:
pg_isready -h localhost -p 5432Test the connection string directly:
psql $DATABASE_URL -c "SELECT 1"Verify the URL format:
postgresql://user:pass@host:port/dbnameError example:
Error: Migration failed -> relation "tb_user" already existsCause: Table already exists from previous migration.
Check migration status to understand what has already been applied:
fraiseql migrate statusIf the state is inconsistent, reset the migration history (CAUTION: drops data in development only):
fraiseql migrate resetAlternatively, remove the conflicting object manually:
psql $DATABASE_URL -c "DROP TABLE IF EXISTS tb_user CASCADE"Error example:
Error: column "pk_user" does not exist -> Referenced in view tv_user definitionCause: View references non-existent column.
Fix: Check table schema matches view definition:
-- Check table columns\d tb_user
-- Ensure column existsALTER TABLE tb_user ADD COLUMN pk_user BIGINT;Error:
Error: function fn_create_user(unknown, unknown) does not existCause: Function signature mismatch.
Inspect the existing function signatures:
\df fn_create_userRecreate the function with explicit parameter types:
CREATE OR REPLACE FUNCTION fn_create_user( user_email TEXT, user_name TEXT) RETURNS mutation_response AS $$...Error:
{ "errors": [{ "message": "Cannot query field 'username' on type 'User'. Did you mean 'name'?" }]}Cause: Querying non-existent field.
Fix: Use correct field name from schema:
# Wrongquery { users { username } }
# Correctquery { users { name } }Error:
{ "errors": [{ "message": "Variable '$id' expected type 'ID!' but got 'String'" }]}Cause: Variable type doesn’t match schema.
Fix: Use correct type in query:
# Check schema for expected typequery GetUser($id: ID!) { # ID!, not String! user(id: $id) { name }}Error:
{ "errors": [{ "message": "Cannot return null for non-nullable field User.email" }]}Cause: Database returned NULL for required field.
Fix: Either fix data or update schema:
# Option 1: Make field nullableemail: str | None
# Option 2: Ensure data is never nullINSERT INTO tb_user (email, ...) VALUES ('required@email.com', ...)Error:
{ "errors": [{ "message": "Email and name are required" }]}Cause: SQL function validation rejected input.
Fix: Provide required fields:
mutation { createUser( email: "user@example.com", # Required name: "User Name" # Required ) { id }}Error:
{ "errors": [{ "message": "User with email user@example.com already exists" }]}Cause: Duplicate value for unique column.
Fix: Use unique value or update existing:
# Check if exists first, then update or createmutation { updateUser(id: "existing-id", name: "New Name") { id }}Error:
{ "errors": [{ "message": "Author not found" }]}Cause: Referenced entity doesn’t exist.
Fix: Ensure parent entity exists:
# First create usermutation { createUser(email: "...", name: "...") { id } }
# Then create post with valid authormutation { createPost(authorId: "valid-user-id", ...) { id } }Error:
{ "errors": [{ "message": "Too many variables: request contains more than 1000 variables" }]}Cause: The variables JSON object has more than 1,000 top-level keys. This is a hard security limit that cannot be configured.
Fix: Restructure your query to pass bulk data as arrays instead of individual variables:
# Instead of $id1, $id2, ... $id1500query GetUsers($ids: [ID!]!) { users(ids: $ids) { id name }}Symptom: Queries take > 100ms.
Diagnosis:
-- Enable query loggingALTER SYSTEM SET log_min_duration_statement = 100;SELECT pg_reload_conf();
-- Check slow query logtail -f /var/log/postgresql/postgresql.logCommon fixes:
CREATE INDEX idx_tv_user_email ON tv_user ((data->>'email'));query { users(limit: 20, offset: 0) { id } }# Instead of deeply nestedquery { posts { author { posts { author { ... } } } } }
# Use separate queriesquery { posts { authorId } }query { users(ids: [...]) { name } }Error example:
Error: pool exhausted -> no connections available after 30sCause: Too many concurrent queries.
Fix:
# Increase pool size[database]pool_max = 100 # Increase from default
# Or use external pooling# PgBouncer, pgpool-IISymptom: OOM errors, high memory usage.
Diagnosis:
# Check process memoryps aux | grep fraiseql
# Check PostgreSQL memorySELECT pg_size_pretty(pg_database_size('mydb'));Fixes:
[validation]max_query_depth = 5max_query_complexity = 500[query_defaults]limit = true # ensure limit argument is enabled so clients can paginateError:
{ "errors": [{ "message": "Invalid token" }]}Causes:
Fix:
# Debug tokenecho $TOKEN | cut -d. -f2 | base64 -d | jq
# Check expiry# "exp": 1704067200 (Unix timestamp)
# Verify secret matches# Server uses JWT_SECRET env varError:
{ "errors": [{ "message": "Unauthorized" }]}Cause: Missing or invalid Authorization header.
Fix:
curl -X POST http://localhost:8080/graphql \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"query": "{ users { id } }"}'Error:
{ "errors": [{ "message": "Forbidden: missing scope read:User.email" }]}Cause: Token lacks required scope.
Fix: Request token with required scopes:
# Include scope in tokenjwt.encode({ "sub": user_id, "scope": "read:User.email read:User.name"}, secret)Set the RUST_LOG environment variable before starting FraiseQL:
RUST_LOG=debug fraiseql runAlways check for partial errors:
{ "data": { "users": [...] }, "errors": [ {"message": "Some field failed"} ]}-- Log all queries (development only)ALTER SYSTEM SET log_statement = 'all';SELECT pg_reload_conf();# Test endpointcurl -v http://localhost:8080/graphql
# Check headerscurl -I http://localhost:8080/health
# Test with specific requestcurl -X POST http://localhost:8080/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ __schema { types { name } } }"}'If you can’t resolve an issue:
fraiseql --version)Performance Optimization
Optimize slow queries and reduce database load in production. Performance Guide
Testing Guide
Write tests to prevent regressions and catch issues early. Testing Guide
Deployment Guide
Production configuration and deployment best practices. Deployment Guide
GitHub Issues
Search for known bugs and report new ones with your diagnostics bundle. Open an Issue
Discord Community
Get real-time help from maintainers and community members. Join Discord
Common Issues
Comprehensive reference organised by error type and symptom. Common Issues
REST endpoint returns 404
rest_path annotation is present on the query or mutation/rest/v1/; configurable via [rest] path[rest] is enabled in schema TOML (v2.1+)REST response has unexpected format
{"data": ..., "meta": ..., "links": ...} envelope. If you expected unwrapped JSON arrays, update your client to read from the data field.{"data": {"posts": [...]}} (nested data), you may be hitting the GraphQL endpoint (/graphql) instead of the REST endpoint (/rest/v1/...).REST auth failing but GraphQL auth works
Authorization: Bearer <token> or X-API-Key: <key> header is present.require_auth in [rest] is set to true when you expect auth enforcement.gRPC connection refused or receives an HTTP/1.1 response
grpc-transport Cargo feature enabled.listen 443 ssl http2; and grpc_pass directive instead of proxy_pass.gRPC method not found
[grpc] is enabled. Verify the operation name matches what gRPC clients expect — use grpcurl -plaintext localhost:50052 list to inspect available services.See Observability for Prometheus metrics, OpenTelemetry tracing, and structured logging — these are the primary tools for diagnosing production issues.