Mastering AppSync: Unions and Interfaces
Source: Dev.to
Introducing AppSync to Your Team
Introducing AppSync to your team has its challenges—especially if the team is unfamiliar with GraphQL (GQL). As the AppSync APPSYNC_JS resolver expert, you’ll be expected to explain:
- Merged APIs
- How to test changes
- Fundamentals of GQL types, schemas, and queries
In my experience, most teams pick up GQL basics quickly, but they often struggle with Union and Interface types.
When should they choose one over the other? What makes them different? Does the team need them at all?
Do We Need Interfaces or Unions at All?
Short answer: No, you don’t have to use them.
However, interfaces and unions let you express schema constraints that are otherwise hard to capture. Below is an example that illustrates why they can be useful.
Example: An Error Type
Imagine an error type that informs the user that their input is invalid. In its basic form it has the following properties:
| Field | Type | Description |
|---|---|---|
code | String! | Machine‑readable error code |
message | String! | Human‑readable error message |
In GQL this can be expressed as:
type ValidationError {
code: String!
message: String!
}
Extending the Basic Error
Specific error variants may add extra fields. Consider two concrete errors:
- MissingPropertiesError – tells which required fields are missing
- ImproperAgeError – tells the minimum allowed age
Both share code and message, but each adds its own optional field:
type MissingPropertiesError {
code: String!
message: String!
missingProperties: [String!] # e.g. ["firstName", "email"]
}
type ImproperAgeError {
code: String!
message: String!
minimumAge: Int # e.g. 18
}
The client can inspect the error type and read the extra property for more detail.
What the Simple Schema Can’t Express
The schema above does not convey that missingProperties and minimumAge are mutually exclusive—you’ll receive one error type or the other, never both.
Other API description languages (e.g., OpenAPI) have a oneOf construct for this. In GraphQL you achieve the same exclusivity with interfaces or unions.
GraphQL Interfaces
What Is an Interface?
- In OO languages, an interface is an abstraction of a class, defining methods and properties that implementing classes must support.
- In GraphQL, an interface is even simpler: it defines a set of fields that any implementing type must expose. No methods, only fields.
Defining an Interface for Errors
interface ValidationError {
code: String!
message: String!
}
Now the concrete error types implement this interface:
type MissingPropertiesError implements ValidationError {
code: String!
message: String!
missingProperties: [String!]!
}
type ImproperAgeError implements ValidationError {
code: String!
message: String!
minimumAge: Int!
}
Using the Interface in a Mutation
type Mutation {
saveInput(input: Input!): ValidationError
}
Querying with Inline Fragments
mutation Save($input: Input!) {
saveInput(input: $input) {
__typename # tells you which concrete type you got
... on ValidationError {
code
message
}
... on MissingPropertiesError {
missingProperties
}
... on ImproperAgeError {
minimumAge
}
}
}
The __typename field is automatically added by GraphQL and lets the client discriminate between the possible concrete types.
The fragments make the either‑or nature of minimumAge vs. missingProperties explicit.
GraphQL Unions
What Is a Union?
- A union groups separate, unrelated types.
- Unlike an interface, a union does not define any common fields.
When Might You Use a Union?
- When the result set can be heterogeneous (e.g., full‑text search returning articles, users, and tags).
- Generally, returning unrelated types from a single field is poor API design—think of a REST endpoint that could return shoes, ships, or sealing wax.
Defining a Union for Errors
type MissingPropertiesError {
code: String!
message: String!
missingProperties: [String!]!
}
type ImproperAgeError {
code: String!
message: String!
minimumAge: Int!
}
union ValidationError = MissingPropertiesError | ImproperAgeError
Using the Union in a Mutation
type Mutation {
saveInput(input: Input!): ValidationError
}
Querying a Union
Because a union has no common fields, you must request everything inside fragments:
mutation Save($input: Input!) {
saveInput(input: $input) {
__typename
... on MissingPropertiesError {
code
message
missingProperties
}
... on ImproperAgeError {
code
message
minimumAge
}
}
}
You still get mutual exclusivity, but you lose the convenience of shared fields.
Choosing Between Interface and Union
| Decision Factor | Interface | Union |
|---|---|---|
| Common fields exist? | ✅ Use an interface | ❌ Not suitable |
| Types are conceptually related? | ✅ Use an interface | ❌ Not ideal |
| Result set is heterogeneous (e.g., search results)? | ❌ Prefer a union | ✅ Use a union |
| Single field returns multiple, unrelated types? | ❌ | ✅ Use a union (rare) |
Rule of thumb:
If there is any shared field, prefer an interface. If the types are truly unrelated, a union may be appropriate.
GraphQL “Code Smells” for Interfaces & Unions
- Optional mutually exclusive fields – Good candidate for an interface (e.g., the overloaded
Errortype shown earlier). - Sharing only trivial fields (e.g., just
id) – Avoid creating an interface; the cognitive overhead outweighs the benefit. - Interfaces on full‑text search results – Suspicious; unions usually shine here because search can return many unrelated types.
- Unions on non‑search endpoints – Usually a smell; look for a common interface instead, unless you’re forced by an upstream API.
Recap
- Interfaces = shared fields + concrete implementations → use when there’s commonality.
- Unions = completely separate types → use for heterogeneous results (e.g., search).
- Both expose
__typenameso the client can discriminate at runtime. - Avoid over‑engineering: only add interfaces/unions when they solve a real problem (mutual exclusivity, shared fields, heterogeneous results).
With this understanding, you’ll be able to guide your team through the nuances of GraphQL’s type system while introducing AppSync’s powerful resolver model. Happy schema designing!
AWS AppSync GraphQL Developer Guide: Interfaces and Unions in GraphQL
Schemas and Types
They will look to you for answers and advice. Having examples and a few rules of thumb to guide decision‑making can go a long way toward helping your team succeed.
Happy building!