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
List of SQL schema statements from your Schema.sql
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])]
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!
}
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:
| PostgreSQL | GraphQL |
|---|
text, varchar | String |
uuid (primary key) | ID |
uuid (other) | UUID (custom scalar) |
int, int2, int4 | Int |
int8 | BigInt |
boolean | Boolean |
timestamp, timestamptz | Timestamp (custom scalar) |
real, double precision | Float |
numeric | Float |
date | Date |
time | Time |
jsonb | JSON |
inet | IPv4 |
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
-
Use variables for dynamic values:
# Good
query GetProject($id: ID!) {
projects(id: $id) { ... }
}
# Avoid hardcoding
query {
projects(id: "abc-123") { ... }
}
-
Request only needed fields: GraphQL compiles to optimized SQL based on requested fields
-
Leverage relationships: Foreign keys automatically create GraphQL relationships
-
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