iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Scalar Types

GraphQL is strongly typed. The schema declares object types, scalars, enums, interfaces, unions, input types. Clients and servers both validate against it — no docs drift.

Scalars, objects, enums, interfaces, unions, inputs

EXAMPLE
# 1) Built-in scalars
#   Int, Float, String, Boolean, ID

# 2) Object types — the bulk of a schema
type User {
    id:        ID!         # ! = non-null
    email:     String!
    name:      String
    age:       Int
    posts:     [Post!]!    # array of non-null Posts, array itself non-null
    createdAt: DateTime!
}

type Post {
    id:     ID!
    title:  String!
    body:   String!
    author: User!
    tags:   [String!]!
}

# 3) Custom scalars — for DateTime, JSON, URL, etc.
scalar DateTime
scalar JSON
scalar URL

# Apollo Server — wire up a custom scalar resolver
import { GraphQLScalarType, Kind } from 'graphql';

const DateTimeScalar = new GraphQLScalarType({
    name: 'DateTime',
    description: 'ISO 8601 date-time',
    serialize(value)   { return value instanceof Date ? value.toISOString() : value; },
    parseValue(value)  { return new Date(value); },
    parseLiteral(ast)  { return ast.kind === Kind.STRING ? new Date(ast.value) : null; },
});

# 4) Enums
enum Role { ADMIN, EDITOR, USER, GUEST }
enum OrderStatus { PENDING, PAID, SHIPPED, CANCELLED }

type User {
    role: Role!
}

# 5) Interfaces — types that share fields
interface Node {
    id: ID!
}

interface Timestamped {
    createdAt: DateTime!
    updatedAt: DateTime!
}

type Post implements Node & Timestamped {
    id:        ID!
    title:     String!
    createdAt: DateTime!
    updatedAt: DateTime!
}

# Resolver — must include __resolveType so the server knows which type a row is
const resolvers = {
    Node: {
        __resolveType(obj) {
            if (obj.title)   return 'Post';
            if (obj.email)   return 'User';
            return null;
        },
    },
};

# 6) Unions — when types don't share fields
union SearchResult = User | Post | Comment

type Query {
    search(q: String!): [SearchResult!]!
}

# Client must use inline fragments
query {
    search(q: "docker") {
        __typename
        ... on User    { id name email }
        ... on Post    { id title body }
        ... on Comment { id text author { name } }
    }
}

# 7) Input types — for mutation arguments
input NewUserInput {
    email:    String!
    name:     String
    role:     Role = USER             # default value
}

input PostFilter {
    search:     String
    tags:       [String!]
    after:      DateTime
    before:     DateTime
    authorIds:  [ID!]
}

type Mutation {
    createUser(input: NewUserInput!): User!
}

type Query {
    posts(filter: PostFilter, first: Int = 20, after: String): PostConnection!
}

# 8) Connection / Edge — Relay-style pagination
type PostConnection {
    edges:    [PostEdge!]!
    pageInfo: PageInfo!
}

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

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

# 9) Nullability rules — read them carefully
# String   = nullable string
# String!  = non-null string
# [String] = nullable array of nullable strings
# [String!] = nullable array of non-null strings
# [String!]! = non-null array of non-null strings

# Server returning null when type says non-null = error. Bubble up nullable parents.

# 10) Type extensions — schema stitching / modular schemas
extend type Query {
    me: User
}

extend type User {
    subscription: Subscription
}

# 11) Schema directives
directive @auth(role: Role = USER) on FIELD_DEFINITION
directive @deprecated(reason: String) on FIELD_DEFINITION

type Query {
    me:      User! @auth(role: USER)
    secrets: [String!]! @auth(role: ADMIN)
    legacyField: String @deprecated(reason: "Use newField instead")
}

# 12) Code-first vs schema-first
# Schema-first (SDL files):    .graphql files + resolver map. Tools: graphql-tools, codegen.
# Code-first (TypeScript):     Pothos, Nexus, TypeGraphQL. Types flow from code.

# 13) Type generation — keep client + server in sync
# graphql-codegen reads the schema + ops, emits TS types.
# Set up:
#   yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
# codegen.ts:
#   schema: 'schema.graphql'
#   documents: 'src/**/*.graphql'
#   generates: { 'src/types.ts': { plugins: ['typescript', 'typescript-operations'] } }

# 14) Best practices
# - Non-null EVERYTHING that's never null on success (id, createdAt) — better client ergonomics
# - Use ID for opaque identifiers — never reveal that they're integers
# - Pluralize lists: `posts`, not `postList`
# - Use input types for mutations — additive evolution
# - Pagination via Connection — first/after, not page/per_page
# - Default values on inputs reduce client noise
# - Mark deprecated, don't break — give clients a migration window

Why it matters

Non-null aggressively, use input types for mutations, paginate with Connection — the three habits that make a GraphQL schema feel professional from day one.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Scalars: Int Float String Boolean ID
# Custom: scalar DateTime
Try it Yourself »

Discussion

Loading…