AI Tools Compared

GraphQL schema design is one of the tasks AI handles well — it’s structured, rule-based, and has clear conventions. Whether you’re generating a schema from existing TypeScript types, a database model, or a REST API, AI tools can produce 80-90% of a working schema in seconds. This guide tests the practical approaches.

What Makes a Good AI-Generated Schema

A well-generated GraphQL schema should:

Approach 1: TypeScript Types to GraphQL Schema

Input to Claude:

interface User {
  id: string;
  email: string;
  displayName: string;
  avatarUrl: string | null;
  createdAt: Date;
  role: 'admin' | 'member' | 'viewer';
  organizationId: string;
}

interface Post {
  id: string;
  title: string;
  content: string;
  publishedAt: Date | null;
  authorId: string;
  tags: string[];
  viewCount: number;
}

Claude’s Output:

scalar DateTime
scalar URL

enum UserRole {
  ADMIN
  MEMBER
  VIEWER
}

type User {
  id: ID!
  email: String!
  displayName: String!
  avatarUrl: URL
  createdAt: DateTime!
  role: UserRole!
  organization: Organization!
  posts(first: Int, after: String): PostConnection!
}

type Post {
  id: ID!
  title: String!
  content: String!
  publishedAt: DateTime
  author: User!
  tags: [String!]!
  viewCount: Int!
}

type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type PostEdge {
  node: Post!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type Query {
  user(id: ID!): User
  users(first: Int, after: String, role: UserRole): UserConnection!
  post(id: ID!): Post
  posts(first: Int, after: String, authorId: ID, tag: String): PostConnection!
}

input CreateUserInput {
  email: String!
  displayName: String!
  role: UserRole = MEMBER
  organizationId: ID!
}

input UpdateUserInput {
  displayName: String
  avatarUrl: URL
  role: UserRole
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
  createPost(input: CreatePostInput!): Post!
  publishPost(id: ID!): Post!
}

Claude correctly used URL scalar for the avatar, made avatarUrl nullable, added relay-style pagination connections, created separate input types for mutations, and inferred the Organization relationship from organizationId.

Approach 2: Database Schema to GraphQL

Input SQL:

CREATE TABLE products (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(255) NOT NULL,
  description TEXT,
  price_cents INTEGER NOT NULL,
  inventory_count INTEGER NOT NULL DEFAULT 0,
  category_id UUID REFERENCES categories(id),
  is_active BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

GPT-4o’s Output (notable additions):

type Product {
  id: ID!
  name: String!
  description: String
  priceCents: Int!
  """Price formatted as dollars with currency symbol"""
  priceFormatted: String!
  inventoryCount: Int!
  category: Category
  isActive: Boolean!
  createdAt: DateTime!
}

type Query {
  product(id: ID!): Product
  products(
    first: Int = 20
    after: String
    categoryId: ID
    inStock: Boolean
    minPrice: Int
    maxPrice: Int
    search: String
  ): ProductConnection!
}

GPT-4o added priceFormatted (a computed field suggestion), filter arguments like inStock and price range, and a search parameter. These additions are valuable and not derived from the SQL schema — the model is applying GraphQL API design best practices.

Automating Schema Generation

// scripts/generate-schema.ts
import Anthropic from '@anthropic-ai/sdk';
import { readFileSync, writeFileSync, readdirSync } from 'fs';
import { join } from 'path';

const client = new Anthropic();

async function generateSchema(typesDir: string): Promise<string> {
  const typeFiles = readdirSync(typesDir)
    .filter(f => f.endsWith('.ts'))
    .map(f => readFileSync(join(typesDir, f), 'utf-8'))
    .join('\n\n');

  const response = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 4096,
    messages: [{
      role: 'user',
      content: `Generate a complete GraphQL schema for these TypeScript types.
Include all queries, mutations, and pagination types.
Output only valid GraphQL SDL, no explanation.\n\n${typeFiles}`
    }]
  });

  return response.content[0].text;
}

const schema = await generateSchema('./src/types');
writeFileSync('./schema.graphql', schema);
console.log('Schema written to schema.graphql');

Run this in CI as a check: if the generated schema differs from the committed schema, fail the build to enforce schema updates when types change.

Tooling Comparison

Tool TypeScript->GQL SQL->GQL Pagination Nullability Speed
Claude Sonnet Excellent Good Relay-correct Correct 8s
GPT-4o Good Excellent Good Good 5s
GitHub Copilot Chat Good Fair Basic OK 3s

Claude has an edge on TypeScript-to-GraphQL due to better type inference. GPT-4o shines on SQL schemas with useful derived fields.

Built by theluckystrike — More at zovo.one