Skip to main content

Overview

IHP GraphQL automatically generates a GraphQL API from your database schema. It compiles GraphQL queries into optimized PostgreSQL queries at runtime. Key Features:
  • Schema-driven GraphQL API generation
  • Automatic query compilation to SQL
  • Support for queries, mutations, and nested relationships
  • Type-safe query building

Installation

Add ihp-graphql to your project’s default.nix:
haskellDeps = p: with p; [
    ihp-graphql
];

Core Types

GraphQLRequest

Represents a parsed GraphQL request:
data GraphQLRequest = GraphQLRequest
    { query :: !Document
    , variables :: !Variables
    }

Document

A GraphQL document containing definitions:
newtype Document = Document { definitions :: [Definition] }

data Definition
    = ExecutableDefinition { operation :: !OperationDefinition }
    | TypeSystemDefinition { typeSystemDefinition :: !TypeSystemDefinition }
    | TypeSystemExtension
    | FragmentDefinition !Fragment

OperationDefinition

Defines a GraphQL operation (query or mutation):
data OperationDefinition = OperationDefinition
    { operationType :: !OperationType
    , name :: !(Maybe Text)
    , selectionSet :: ![Selection]
    , variableDefinitions :: ![VariableDefinition]
    }

data OperationType
    = Query
    | Mutation
    | Subscription

Schema Compilation

sqlSchemaToGraphQLSchema

Convert an IHP SQL schema to a GraphQL schema:
sqlSchemaToGraphQLSchema :: SqlSchema -> GraphQLSchema
statements
[Statement]
List of SQL schema statements from your Schema.sql
GraphQLSchema
[Definition]
Generated GraphQL schema definitions including:
  • Query type with fields for each table
  • Mutation type with create/update/delete operations
  • Object types for each table
  • Input types for creating/updating records
Example:
import IHP.GraphQL.SchemaCompiler
import IHP.Postgres.Types

let schema = sqlSchemaToGraphQLSchema sqlStatements
-- Generates Query, Mutation, and object types

Query Compilation

compileDocument

Compile a GraphQL document to SQL queries:
compileDocument :: Variables -> Document -> [(PG.Query, [PG.Action])]
variables
Variables
GraphQL query variables
document
Document
Parsed GraphQL document
queries
[(PG.Query, [PG.Action])]
List of compiled SQL queries with parameters
Example: Given this GraphQL query:
query {
    projects {
        id
        title
        tasks {
            id
            name
        }
    }
}
Compiles to:
SELECT to_json(_root.data) FROM (
    SELECT json_build_object('projects', json_agg(_projects.*))
    FROM (
        SELECT id, title, (
            SELECT ARRAY(
                SELECT to_json(_sub) FROM (
                    SELECT id, name FROM tasks 
                    WHERE tasks.project_id = projects.id
                ) AS _sub
            ) AS tasks
        )
        FROM projects
    ) AS _projects
) AS _root

Generated Schema

Query Type

For each table, a query field is generated:
type Query {
    """Returns all records from the `projects` table"""
    projects: [Project!]!
    
    tasks: [Task!]!
    users: [User!]!
}

Object Types

Each table becomes a GraphQL object type:
type Project {
    id: ID!
    title: String!
    userId: ID!
    createdAt: Timestamp!
    
    # Automatic relationship based on foreign keys
    tasks: [Task!]!
}

type Task {
    id: ID!
    projectId: ID!
    name: String!
    isCompleted: Boolean!
}

Mutation Type

CRUD operations for each table:
type Mutation {
    createProject(project: NewProject!): Project!
    updateProject(id: ID!, patch: ProjectPatch!): Project!
    deleteProject(id: ID!): Project!
    
    createTask(task: NewTask!): Task!
    updateTask(id: ID!, patch: TaskPatch!): Task!
    deleteTask(id: ID!): Task!
}

Input Types

For creating new records:
input NewProject {
    title: String!
    userId: ID!
    # createdAt is optional (has default value)
    createdAt: Timestamp
}

input NewTask {
    projectId: ID!
    name: String!
    # isCompleted is optional (has default)
    isCompleted: Boolean
}
For updating records:
input ProjectPatch {
    title: String
    userId: ID
}

input TaskPatch {
    name: String
    isCompleted: Boolean
}

Type Mapping

PostgreSQL types map to GraphQL types:
PostgreSQLGraphQL
text, varcharString
uuid (primary key)ID
uuid (other)UUID (custom scalar)
int, int2, int4Int
int8BigInt
booleanBoolean
timestamp, timestamptzTimestamp (custom scalar)
real, double precisionFloat
numericFloat
dateDate
timeTime
jsonbJSON
inetIPv4
type[][Type]

Query Examples

Simple Query

query {
    projects {
        id
        title
    }
}

Nested Relationships

query {
    projects {
        id
        title
        tasks {
            id
            name
            isCompleted
        }
    }
}

Query with Variables

query GetProject($projectId: ID!) {
    projects(id: $projectId) {
        id
        title
        tasks {
            name
        }
    }
}
Variables:
{
    "projectId": "df1f54d5-ced6-4f65-8aea-fcd5ea6b9df1"
}

Field Aliases

query {
    projects {
        projectId: id
        projectName: title
    }
}

Mutation Examples

Create Record

mutation CreateProject($project: NewProject!) {
    createProject(project: $project) {
        id
        title
    }
}
Variables:
{
    "project": {
        "title": "New Project",
        "userId": "dc984c2f-d91c-4143-9091-400ad2333f83"
    }
}
Compiles to:
INSERT INTO projects (title, user_id)
VALUES ($1, $2)
RETURNING json_build_object('id', projects.id, 'title', projects.title)

Update Record

mutation UpdateProject($id: ID!, $patch: ProjectPatch!) {
    updateProject(id: $id, patch: $patch) {
        id
        title
    }
}
Variables:
{
    "id": "df1f54d5-ced6-4f65-8aea-fcd5ea6b9df1",
    "patch": {
        "title": "Updated Title"
    }
}
Compiles to:
UPDATE projects
SET title = $2
WHERE id = $1
RETURNING json_build_object('id', projects.id, 'title', projects.title)

Delete Record

mutation DeleteProject($id: ID!) {
    deleteProject(id: $id) {
        id
        title
    }
}
Compiles to:
DELETE FROM projects
WHERE id = $1
RETURNING json_build_object('id', projects.id, 'title', projects.title)

Fragments

Define reusable field selections:
fragment ProjectFields on Project {
    id
    title
    createdAt
}

query {
    projects {
        ...ProjectFields
        tasks {
            id
            name
        }
    }
}

Best Practices

  1. Use variables for dynamic values:
    # Good
    query GetProject($id: ID!) {
        projects(id: $id) { ... }
    }
    
    # Avoid hardcoding
    query {
        projects(id: "abc-123") { ... }
    }
    
  2. Request only needed fields: GraphQL compiles to optimized SQL based on requested fields
  3. Leverage relationships: Foreign keys automatically create GraphQL relationships
  4. Use fragments for reusable selections: Reduces duplication in complex queries

Limitations

  • No support for pagination arguments (use SQL views or custom resolvers)
  • No filtering/sorting arguments on queries (use SQL views)
  • Subscriptions not yet implemented
  • Custom scalars limited to UUID and Timestamp

See Also

Build docs developers (and LLMs) love