Mutations
The Star Wars demo app includes several mutation operations that allow you to modify data. All mutations are available
under the Mutation root type and demonstrate how to implement data modification operations in Viaduct.
Mutation implementation patterns
Mutations in Viaduct follow similar patterns to queries but focus on data modification operations. Each mutation resolver typically:
- Validates input data using input types with appropriate constraints.
- Performs the data modification on the underlying data store.
- Returns updated entities that can be further resolved with additional fields.
- Maintains data consistency and referential integrity.
Available mutations
Create a new character
input CreateCharacterInput @scope(to: ["default"]) {
name: String!
birthYear: String
eyeColor: String
gender: String
hairColor: String
height: Int
mass: Float
homeworldId: ID @idOf(type: "Planet")
speciesId: ID @idOf(type: "Species")
}"""
Create a new Character.
"""
createCharacter(input: CreateCharacterInput!): Character @resolver
Implementation:
@Resolver
class CreateCharacterMutation
@Inject
constructor(
private val characterRepository: CharacterRepository,
private val securityAccessContext: SecurityAccessContext
) : MutationResolvers.CreateCharacter() {
override suspend fun resolve(ctx: Context): Character =
securityAccessContext.validateAccess {
val input = ctx.arguments.input
val homeworldId = input.homeworldId
val speciesId = input.speciesId
// TODO: Validate homeworld and species are valid ids
// Create and store the new character
val character = characterRepository.add(
com.example.starwars.modules.filmography.characters.models.Character(
id = "",
name = input.name,
birthYear = input.birthYear,
eyeColor = input.eyeColor,
gender = input.gender,
hairColor = input.hairColor,
height = input.height,
mass = input.mass?.toFloat(),
homeworldId = homeworldId?.internalID,
speciesId = speciesId?.internalID,
)
)
// Build and return the GraphQL Character object from the created entity
CharacterBuilder(ctx).build(character)
}
}
Execution :
mutation {
createCharacter(input: {
name: "New Jedi"
birthYear: "19BBY"
eyeColor: "blue"
gender: "male"
hairColor: "brown"
height: 180
mass: 75.5
homeworldId: "UGxhbmV0OjE=" # Tatooine
speciesId: "U3BlY2llczox" # Human
}) {
id
name
birthYear
homeworld { name }
species { name }
}
}
Implementation notes:
- Uses input types for structured data validation.
- Generates new GlobalIDs for created entities.
- Supports relationship creation via reference IDs.
- Returns the full created entity for immediate use.
- Validates security access using request-scoped context (see Request Context for details).
Update character name
"""
Update an existing Character's name.
"""
updateCharacterName(id: ID! @idOf(type: "Character"), name: String!): Character @resolver
Implementation notes:
@Resolver
class UpdateCharacterNameMutation
@Inject
constructor(
private val characterRepository: CharacterRepository,
private val securityAccessService: SecurityAccessContext
) : MutationResolvers.UpdateCharacterName() {
override suspend fun resolve(ctx: Context): Character? =
securityAccessService.validateAccess {
val id = ctx.arguments.id
val name = ctx.arguments.name
// Fetch existing character
val character = characterRepository.findById(id.internalID)
?: throw IllegalArgumentException("Character with ID ${id.internalID} not found")
// Update character's name
val updatedCharacter = character.copy(name = name)
val newCharacter = characterRepository.update(updatedCharacter)- Uses GlobalIDs for entity identification.
- Performs atomic field updates.
- Returns updated entity for verification.
Add character to film
"""
Link a Character to a Film (adds the character to that film's cast).
Allows locating the character either directly by ID or via the existing search input.
"""
addCharacterToFilm(input: AddCharacterToFilmInput!): AddCharacterToFilmPayload @resolver
}mutation {
addCharacterToFilm(input: {
filmId: "RmlsbTox" # A New Hope
characterId: "Q2hhcmFjdGVyOjU=" # Obi-Wan Kenobi
}) {
character {
name
}
film {
title
}
}
}
Implementation notes:
- Manages many-to-many relationships.
- Uses input types for relationship data.
- Returns both related entities for verification.
- Maintains bidirectional relationship consistency.
Delete character
"""
Delete a Character by ID. Returns true if a record was removed.
"""
deleteCharacter(id: ID! @idOf(type: "Character")): Boolean @resolverImplementation notes:
- Uses GlobalIDs for entity identification.
- Returns boolean success indicator.
- Handles cascading relationship cleanup.
- Maintains data integrity during deletion.
Using request context
Operations often need access to request-specific data like authentication, authorization, or tenant information. While this example focuses on mutations, request context is available to all operations — queries, mutations, and subscriptions may all use request context as needed.
Viaduct fully supports request context management through your framework’s dependency injection. For detailed information on different approaches and best practices, see the Request Context documentation.
Mutation best practices
- Use input types: structure mutation arguments with dedicated input types for validation and clarity.
- GlobalID consistency: always use encoded GlobalIDs for entity references.
- Return useful data: return updated entities or relationship objects, not just success flags.
- Validate relationships: ensure referenced entities exist before creating relationships.
- Handle errors gracefully: provide meaningful error messages for invalid operations.
- Maintain consistency: update all related data structures atomically.
- Leverage request context: use framework-provided request scoping for authentication, authorization, and tenant isolation.
- Inject dependencies: prefer dependency injection over global state or manual context threading.
Note: when using mutations, make sure to use properly encoded GlobalIDs.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.