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

Interfaces

Interfaces declare a set of common fields that multiple object types must implement. Use them when types share a contract (every Node has an id) or when queries should return a polymorphic result — clients fragment on the concrete type.

Interface, implements, resolveType, Node

EXAMPLE
// 1) Declare an interface
interface Node {
    id: ID!
}

// 2) Object types implement it
type User implements Node {
    id:      ID!
    email:   String!
    name:    String
}

type Post implements Node {
    id:      ID!
    title:   String!
    author:  User!
}

type Comment implements Node {
    id:      ID!
    body:    String!
    author:  User!
}

// 3) Use as a return type
type Query {
    node(id: ID!): Node
    me: User
    feed: [Post!]!
}

// Now node(id: ...) can return any type that implements Node — perfect for global object lookup.

// 4) Querying interfaces — fragments
`
query FetchNode {
    node(id: "User:42") {
        id
        ... on User { email name }
        ... on Post { title author { name } }
        ... on Comment { body }
    }
}
`

// 5) Resolver — __resolveType
const resolvers = {
    Node: {
        __resolveType(obj) {
            if (obj.email)  return 'User';
            if (obj.title)  return 'Post';
            if (obj.body)   return 'Comment';
            return null;
        },
    },
    Query: {
        node: (_p, { id }, ctx) => {
            const [type, raw] = id.split(':');
            if (type === 'User')    return ctx.db.user.find(raw);
            if (type === 'Post')    return ctx.db.post.find(raw);
            if (type === 'Comment') return ctx.db.comment.find(raw);
            return null;
        },
    },
};

// __resolveType picks the concrete type. The server runs that type's field resolvers next.

// 6) Interface fields — implementers MUST include them (with at least the declared type)
// You can ALSO add fields beyond the interface — that's the whole point.

// 7) Multiple interfaces per type
interface Timestamped { createdAt: DateTime!  updatedAt: DateTime! }

type User implements Node & Timestamped {
    id: ID!
    email: String!
    createdAt: DateTime!
    updatedAt: DateTime!
}

// 8) Interfaces can implement other interfaces (GraphQL 2021 spec)
interface Node { id: ID! }
interface Resource implements Node {
    id: ID!
    owner: User!
}

type Document implements Resource & Node {
    id: ID!
    owner: User!
    title: String!
}

// 9) Relay-style global object identification — the canonical use case
interface Node { id: ID! }

type Query {
    node(id: ID!): Node
    viewer: User
}

// Clients ALWAYS request id on any Node. Relay caches by id, so a User from query A is the
// same User if returned from query B — automatic normalisation.

// 10) Interface vs Union — when to use which
// • Interface — shared FIELDS. All types have id, createdAt, owner, etc.
// • Union     — no shared fields. SearchResult = User | Post | Comment
//
// You can ALSO query an interface like a union (with fragments), but interfaces give clients
// guaranteed common fields without fragments.

// 11) Polymorphic field — list returns interface
type Query {
    feed: [FeedItem!]!
}

interface FeedItem {
    id:         ID!
    createdAt:  DateTime!
    actor:      User!
}

type LikedPost implements FeedItem {
    id: ID!
    createdAt: DateTime!
    actor: User!
    post:  Post!
}

type FollowedUser implements FeedItem {
    id: ID!
    createdAt: DateTime!
    actor: User!
    followed: User!
}

// Client can ask for actor + createdAt without fragments, fragment on the concrete type for specifics.

// 12) Codegen + clients
// • @graphql-codegen produces TypeScript discriminated unions from interfaces
// • Apollo Client + Relay generate typed fragments for the polymorphism
// • Use __typename in queries so clients can tell concrete types at runtime

`
query {
    feed {
        __typename
        id
        createdAt
        ... on LikedPost { post { id title } }
        ... on FollowedUser { followed { id name } }
    }
}
`

// 13) Common bugs
// • Forgot __resolveType → 'Cannot determine type of ...'
// • Implementer drops a required field → schema error at startup
// • Returning DTO without enough fields for __resolveType to discriminate — check tag or class
// • Interface field that's expensive to compute — cache or move to concrete type
// • Wide interfaces (many fields) — narrow them; one Node{id} is fine, ManyThings{...} bloats schema
// • Missing __typename on the client — can't tell variants apart at runtime
// • Adding a new implementer without updating client fragments — runtime fallback shows blank UI
// • Using interfaces when a union is cleaner (no shared fields) — readability loss

Why it matters

Interfaces let multiple types share a contract — perfect for global object identification (Node), feed-style polymorphism, and any “same shape, different details” field. Always implement __resolveType on the server so resolvers know the concrete type, and ask clients to request __typename for safe runtime discrimination.

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

Example

Example
interface Node { id: ID! }
type User implements Node { id: ID! name: String! }
Try it Yourself »

Discussion

Loading…