IDOf
The @idOf directive binds an ID field or argument to a specific GraphQL type, allowing Viaduct to perform automatic type validation and Global ID decoding. It ensures that the ID belongs to the expected type before invoking your resolver, preventing mismatched or malformed identifiers at runtime.
Why it matters¶
In GraphQL, all ID values are strings. Without additional metadata, there’s no way to know which entity type an ID represents. @idOf introduces type awareness by declaring that a given ID corresponds to a specific GraphQL type.
This allows Viaduct to:
- Validate incoming IDs before they reach resolver logic.
- Decode Global IDs on behalf of application code, keeping their serialization format encapsulated.
- Reject mismatched IDs (for example, passing a
PlanetID to aCharacterresolver). - Generate type-safe schemas that tools can reason about statically.
Basic usage¶
Apply @idOf to any ID argument or field that represents a Global ID.
type Character implements Node @scope(to: ["default"]) @resolver(isBatching: true) {
"""
The GlobalID of this character - uniquely identifies this Character in the graph (internal use only)
"""
id: ID!
How it works at runtime¶
When a client calls a query such as:
Viaduct will:
- Decode the (intentionally opaque) string
"Q2hhcmFjdGVyOjE="into its global ID components, which happens to be typeCharacterand internal id1. - Validate that the type for the argument to
Query.character's argument (whichCharacter) matches the type in the encoded ID (which it does). - Pass the decoded
GlobalIDvalue toCharacterNodeResolver, where you can access theidfield type-safely as a KotlinGlobalID.
@Resolver
class CharacterNodeResolver
@Inject
constructor(
private val characterRepository: CharacterRepository
) : NodeResolvers.Character() {
// Node resolvers can also be batched to optimize multiple requests
// tag::node_batch_resolver_example[21] Example of a node resolver
override suspend fun batchResolve(contexts: List<Context>): List<FieldValue<Character>> {
// Extract all unique character IDs from the contexts
val characterIds = contexts.map { it.id.internalID }
// Perform a single batch query to get film counts for all characters
// We only compute one time for each character, despite multiple requests
val characters = characterIds.mapNotNull {
characterRepository.findById(it)
}
// For each context gets the character ID and map to the viaduct object
return contexts.map { ctx ->
val characterId = ctx.id.internalID
characters.firstOrNull { it.id == characterId }?.let {
FieldValue.ofValue(
CharacterBuilder(ctx).build(it)
)
} ?: FieldValue.ofError(IllegalArgumentException("Character not found: $characterId"))
}
}
}
This pattern helps ensure that only valid, correctly-typed IDs reach your business logic.
Viaduct has no way to prevent malicious clients from manufacturing global ID strings that conform to its serialization format but contain arbitrary internal id values. Be sure to code defensively.
Advantages¶
- Encapsulates the decoding of serialized global IDs.
- Prevents runtime errors caused by type mismatches.
- Simplifies schema introspection and static analysis.
- Makes field-level validation explicit and discoverable in the schema.
Common mistakes¶
1. Forgetting @idOf on inputs that expect Global IDs¶
If an argument or input field represents a Global ID but lacks @idOf, Viaduct treats it as a plain string, skipping type validation and decoding. Always add @idOf when your resolvers depend on typed IDs.
2. Mixing raw IDs with Global IDs¶
All ID arguments using @idOf are expected to be encoded Global IDs, not raw database identifiers. Passing unencoded values will fail decoding or validation.
3. Misdeclaring the target type¶
Ensure the type name in @idOf(type: "X") matches the GraphQL type exactly, including case. "character" and "Character" are not equivalent.
Do and don’t¶
- Do use
@idOfon everyIDfield or argument that carries a Global ID. - Do rely on
ctx.id.internalIDfor the decoded internal ID in resolvers. - Don’t attempt to parse or decode IDs manually.
- Don’t use
@idOfon non-ID fields.
For more on Global IDs and
@idOf, see the Global IDs developer reference.