Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the json to graphql schema engine, best practices for implementation, and data security standards.
GraphQL's type system is stricter than TypeScript's — every field must be explicitly typed, non-null fields must be declared with !, and the distinction between nullable and required is enforced by the schema, not convention. Converting a JSON response to GraphQL SDL gives you the object type definitions you need to start a GraphQL API, migrate from REST, or prototype a schema before writing resolvers.
Before generating a schema, understand where GraphQL and TypeScript diverge:
// TypeScript interface — optional means "may be absent"
interface User {
id: string; // required
name: string; // required
email?: string; // optional (undefined)
avatar: string | null; // required, but nullable
}
# GraphQL SDL — ! means non-null (required)
type User {
id: String! # non-null = required
name: String! # non-null = required
email: String # nullable = may be null or absent in resolver
avatar: String # nullable
}
# Key insight: in GraphQL, "nullable" and "optional" are the same concept.
# A field without ! can return null. There's no undefined.
// Input JSON (API response)
{
"id": "usr_01HXYZ",
"name": "Ada Lovelace",
"email": "ada@example.com",
"created_at": "2026-01-15T10:30:00Z",
"subscription": {
"plan": "pro",
"expires_at": "2027-01-15T10:30:00Z",
"seats": 5
},
"tags": ["engineering", "mathematics"]
}
# Generated GraphQL SDL
type Subscription {
plan: String!
expires_at: String!
seats: Int!
}
type User {
id: String!
name: String!
email: String!
created_at: String!
subscription: Subscription!
tags: [String!]!
}
type Query {
user(id: String!): User
users: [User!]!
}
GraphQL uses separate input types for mutation arguments — you can't reuse object types directly:
# Object type — what resolvers return
type Product {
id: ID!
sku: String!
name: String!
price_cents: Int!
in_stock: Boolean!
created_at: String!
}
# Input type for create — id and created_at are server-generated
input CreateProductInput {
sku: String!
name: String!
price_cents: Int!
in_stock: Boolean
}
# Input type for update — everything optional except id
input UpdateProductInput {
sku: String
name: String
price_cents: Int
in_stock: Boolean
}
type Mutation {
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product
deleteProduct(id: ID!): Boolean!
}
Once you have the SDL, generate TypeScript resolver types with graphql-codegen or write them manually:
// Manually typed resolvers (without codegen)
import { GraphQLResolveInfo } from 'graphql';
type Context = {
db: Database;
user: AuthUser | null;
};
// Match the shape of your SDL types
type ProductGQL = {
id: string;
sku: string;
name: string;
price_cents: number;
in_stock: boolean;
created_at: string;
};
type Resolvers = {
Query: {
product: (_: unknown, args: { id: string }, ctx: Context) => Promise<ProductGQL | null>;
products: (_: unknown, args: { page?: number; limit?: number }, ctx: Context) => Promise<ProductGQL[]>;
};
Mutation: {
createProduct: (
_: unknown,
args: { input: { sku: string; name: string; price_cents: number; in_stock?: boolean } },
ctx: Context
) => Promise<ProductGQL>;
};
};
// Implementation
const resolvers: Resolvers = {
Query: {
product: async (_, { id }, { db }) => db.products.findById(id),
products: async (_, { page = 1, limit = 20 }, { db }) => db.products.list({ page, limit }),
},
Mutation: {
createProduct: async (_, { input }, { db, user }) => {
if (!user) throw new Error('Unauthorized');
return db.products.create(input);
},
},
};
# GraphQL enum — values are unquoted identifiers, conventionally SCREAMING_SNAKE_CASE
enum OrderStatus {
PENDING
PAID
SHIPPED
CANCELLED
}
type Order {
id: ID!
status: OrderStatus!
total: Int!
}
# In TypeScript resolvers, GraphQL enums become string literals
type OrderStatusGQL = 'PENDING' | 'PAID' | 'SHIPPED' | 'CANCELLED';
// Map your database enum to GraphQL enum values
function toGQLStatus(dbStatus: 'pending' | 'paid' | 'shipped' | 'cancelled'): OrderStatusGQL {
return dbStatus.toUpperCase() as OrderStatusGQL;
}
GraphQL's null handling is a design decision, not just a type annotation. A common pattern is to make top-level query fields nullable (so fetching a missing resource returns null, not an error), but object fields non-null:
type Query {
# Returns null if user doesn't exist — not an error
user(id: ID!): User
# Returns null if product is out of catalog — expected scenario
product(id: ID!): Product
# Always returns a list (empty array, never null)
products: [Product!]!
}
type User {
# These fields always exist on a valid User object
id: ID!
name: String!
email: String!
# Profile is optional — user may not have filled it in
profile: UserProfile
}
String for IDs: GraphQL has a built-in ID scalar specifically for identifiers. It serializes identically to String but signals intent to the schema consumer and codegen tools. Always use ID! for primary keys.[String], [String!], [String]!, and [String!]! are four different types. [String!]! means the list is always present and never contains nulls — usually what you want for collections of domain objects.createProduct: Product!) rather than a boolean or void. This lets clients update their cache with the server's canonical version and avoids a follow-up query.Order type has a customer: User! field, a naive resolver fetches one user per order — N+1. Use DataLoader to batch user fetches into a single query.Q: Should I use code-first or schema-first GraphQL?
A: Schema-first (write SDL, then implement resolvers) keeps the API contract visible and language-agnostic. Code-first (generate SDL from code using Nexus or Pothos) is better for complex TypeScript-driven schemas where SDL duplication becomes painful. For teams with multiple consumers or a public API, schema-first is generally clearer.
Q: How do I handle pagination in the GraphQL schema?
A: Use the Relay Cursor Connection spec: type ProductConnection { edges: [ProductEdge!]! pageInfo: PageInfo! }. This is the standard that Apollo Client and Relay expect, and it handles forward/backward pagination cleanly.
Q: Can I use the same JSON-derived types for both my REST and GraphQL APIs?
A: Define the core domain types once (as TypeScript interfaces) and adapt them separately for REST responses and GraphQL resolvers. Don't let the GraphQL type system leak into your domain model — the nullable/non-null semantics are specific to GraphQL.
Q: How do I represent dates in GraphQL?
A: The built-in scalars don't include Date. Either use String with ISO 8601 format (simple, interoperable) or define a custom DateTime scalar with a spec that serializes to ISO 8601. Libraries like graphql-scalars provide common custom scalars with validation.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.
Why pasting proprietary company data into third-party web tools is a major liability, and how to stay safe.
A deep dive into combining Zod, React Query, and TypeScript for bulletproof API integration.
Code generation is just the beginning. Discover how a schema-first approach can eliminate 90% of your integration bugs.