GraphQL Schema Writer
The GraphQL writer converts RelSpec's internal database model into GraphQL Schema Definition Language (SDL) files.
Features
- Table to Type mapping: Database tables become GraphQL types
- Column to Field mapping: Table columns become type fields
- Enum support: Database enums are preserved
- Custom scalar declarations: Automatically declares DateTime, JSON, Date scalars
- Implicit relationships: Generates relationship fields from foreign keys
- Many-to-many support: Handles junction tables intelligently
- Clean output: Proper formatting, field ordering, and comments
Type Mappings
SQL to GraphQL
| SQL Type | GraphQL Type | Notes |
|---|---|---|
| bigint, integer, serial (PK) | ID | Primary keys map to ID |
| bigint, integer, int | Int | |
| text, varchar, char | String | |
| uuid (PK) | ID | UUID primary keys also map to ID |
| uuid | String | Non-PK UUIDs map to String |
| double precision, numeric, float | Float | |
| boolean | Boolean | |
| timestamp, timestamptz | DateTime | Custom scalar |
| jsonb, json | JSON | Custom scalar |
| date | Date | Custom scalar |
| Enum types | Enum | Preserves enum name |
| Arrays (e.g., text[]) | [Type] | Mapped to GraphQL lists |
Relationship Handling
The writer intelligently generates relationship fields based on foreign key constraints:
Forward Relationships (FK on this table)
-- Post table has authorId FK to User.id
CREATE TABLE post (
id bigint PRIMARY KEY,
title text NOT NULL,
author_id bigint NOT NULL REFERENCES user(id)
);
type Post {
id: ID!
title: String!
author: User! # Generated from authorId FK
}
Reverse Relationships (FK on other table)
type User {
id: ID!
email: String!
posts: [Post!]! # Reverse relationship (Post has FK to User)
}
Many-to-Many Relationships
Junction tables (tables with only PKs and FKs) are automatically detected and hidden:
CREATE TABLE post_tag (
post_id bigint NOT NULL REFERENCES post(id),
tag_id bigint NOT NULL REFERENCES tag(id),
PRIMARY KEY (post_id, tag_id)
);
type Post {
id: ID!
tags: [Tag!]! # Many-to-many through PostTag junction table
}
type Tag {
id: ID!
posts: [Post!]! # Reverse many-to-many
}
# Note: PostTag junction table is NOT included in output
Usage
Basic Usage
import (
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
"git.warky.dev/wdevs/relspecgo/pkg/writers/graphql"
)
opts := &writers.WriterOptions{
OutputPath: "schema.graphql",
}
writer := graphql.NewWriter(opts)
err := writer.WriteDatabase(db)
With Metadata Options
opts := &writers.WriterOptions{
OutputPath: "schema.graphql",
Metadata: map[string]any{
"includeScalarDeclarations": true, // Include scalar declarations
"includeComments": true, // Include field/table comments
},
}
writer := graphql.NewWriter(opts)
err := writer.WriteDatabase(db)
Write to Stdout
opts := &writers.WriterOptions{
OutputPath: "", // Empty path writes to stdout
}
writer := graphql.NewWriter(opts)
err := writer.WriteDatabase(db)
CLI Usage
# Convert PostgreSQL database to GraphQL
relspec convert --from pgsql \
--from-conn "postgres://user:pass@localhost:5432/mydb" \
--to graphql --to-path schema.graphql
# Convert GORM models to GraphQL
relspec convert --from gorm --from-path ./models \
--to graphql --to-path schema.graphql
# Convert JSON to GraphQL
relspec convert --from json --from-path schema.json \
--to graphql --to-path schema.graphql
Output Format
The generated GraphQL schema follows this structure:
- Header comment (if enabled)
- Custom scalar declarations (if any custom scalars are used)
- Enum definitions (alphabetically sorted)
- Type definitions (with fields ordered: ID first, then scalars alphabetically, then relationships)
Example Output
# Generated GraphQL Schema
# Database: myapp
scalar DateTime
scalar JSON
scalar Date
enum Role {
ADMIN
USER
MODERATOR
}
type User {
id: ID!
createdAt: DateTime!
email: String!
name: String!
role: Role!
posts: [Post!]!
profile: Profile
}
type Post {
id: ID!
content: String
published: Boolean!
publishedAt: Date
title: String!
author: User!
tags: [Tag!]!
}
type Tag {
id: ID!
name: String!
posts: [Post!]!
}
Metadata Options
| Option | Type | Description | Default |
|---|---|---|---|
includeScalarDeclarations |
bool | Include scalar DateTime, etc. declarations |
true |
includeComments |
bool | Include table/field descriptions as comments | true |
preservePKType |
bool | Use Int/String for PKs instead of ID | false |
Field Naming Conventions
- FK columns: Foreign key columns like
authorIdare removed from the output; instead, a relationship fieldauthoris generated - Relationship pluralization: Reverse one-to-many relationships are pluralized (e.g.,
posts,tags) - CamelCase: Field names are kept in their original casing from the database
Junction Table Detection
A table is considered a junction table if it:
- Has exactly 2 foreign key constraints
- All columns are either primary keys or foreign keys
- Has a composite primary key on the FK columns
Junction tables are automatically hidden from the GraphQL output, and many-to-many relationship fields are generated on the related types instead.
Limitations
- All tables in all schemas are flattened into a single GraphQL schema
- No support for GraphQL-specific features like directives, interfaces, or unions
- Nullable vs non-nullable is determined solely by the
NOT NULLconstraint
Example Conversion
Input (Database Schema):
CREATE TABLE user (
id bigint PRIMARY KEY,
email text NOT NULL,
created_at timestamp NOT NULL
);
CREATE TABLE post (
id bigint PRIMARY KEY,
title text NOT NULL,
author_id bigint NOT NULL REFERENCES user(id)
);
Output (GraphQL Schema):
scalar DateTime
type User {
id: ID!
createdAt: DateTime!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}