Mutations
Change the cats through the schema: input types that describe what a client may send, validated by class-validator, a partial update derived from the create input, and Nest's exceptions as GraphQL errors.
What you'll learn
- Declare what a client may send with @InputType() classes, and take them as a mutation's argument
- Write @Mutation() resolvers that change state and return what changed
- Validate input types with the global ValidationPipe and class-validator, and read a refusal as a GraphQL error
- Derive an update input with PartialType from @nestjs/graphql, which keeps the fields' GraphQL types and their validation
The shelter's staff app needs to register a new cat, correct a name, and take a cat off the list once it has gone home. Queries only read. In GraphQL, changes go through mutations: root fields like queries, declared in the same schema, but run one after another in the order the client wrote them, because each may change what the next one sees. A mutation takes arguments and returns something, usually the object it changed, so the client can ask in the same request for exactly the fields it needs to update its screen.
Input types
Arguments can be scalars, but a mutation that creates a cat takes an object. An object sent to the server is an input type, a separate kind of type in GraphQL: it has no field resolvers and may not contain output types. In code first it is a class with @InputType(), and its properties carry @Field() with the same type rules as before:
@InputType()
export class NewShiftInput {
@Field()
volunteer: string;
@Field(() => Int)
hours: number;
@Field({ nullable: true })
note?: string;
}
@Mutation(() => Shift)
addShift(@Args('input') input: NewShiftInput): Shift { ... }
The client writes the object inline or passes it as a variable of type NewShiftInput!. GraphQL checks its shape against the schema before anything runs: a missing non-null field is refused with a 400 and Field "NewShiftInput.hours" of required type "Int!" was not provided. A class decorated with @ObjectType() cannot be used as an argument; Nest says so at startup.
Validation
The schema checks types, not rules: -1 is a perfectly good Int. The rules come from class-validator, as over HTTP. Decorate the input class's properties and turn on the ValidationPipe, globally with app.useGlobalPipes(new ValidationPipe()). It runs on every @Args(), as it runs on every @Body().
A refusal is a BadRequestException, and in GraphQL an exception thrown by a resolver or a pipe does not change the HTTP status. The response is 200 with data set to null and an errors entry. Its message is Bad Request Exception. Its extensions carry the code BAD_REQUEST and, under originalError, the body Nest would have sent over HTTP, the constraint messages included. Other exceptions work the same way. A NotFoundException from the service arrives as an error with its status of 404 in extensions; Apollo has no code for "not found", so its code is INTERNAL_SERVER_ERROR. A client reads the outcome from errors, never from the HTTP status.
Deriving the update input
An update takes the same fields, all optional. Writing the class twice invites drift, so @nestjs/graphql provides the mapped types you met for DTOs, GraphQL-aware:
@InputType()
export class ShiftChangesInput extends PartialType(NewShiftInput) {}
PartialType copies every field, makes each nullable in the schema, and copies the class-validator rules with @IsOptional() added, so a value that is given is still checked. PickType, OmitType and IntersectionType exist too. Import them from @nestjs/graphql, not @nestjs/mapped-types: the latter knows nothing about @Field(), and the schema generation fails on the class it returns.
Your task
CatsService has create(), update() (which changes only the fields the input carries, and throws NotFoundException for an unknown id) and remove(). The input classes carry their class-validator rules already.
- Turn on validation for every input.
- Make
CreateCatInputan input type.name,age(an integer) andbreedare required,nicknameis optional. - Make
UpdateCatInputan input type with the same fields asCreateCatInput, every one optional, with the same validation, without repeating them. - Add three mutations:
createCat(input)returning the new cat,updateCat(id, input)with an integer id returning the updated cat, andremoveCat(id)returningtrue.
Then send an update with only a nickname, and one with a negative age.
When it fails
- A negative age is accepted: no
ValidationPipeis running. Class-validator's decorators do nothing by themselves. - An update with a negative age is accepted while a create is refused:
UpdateCatInputdoes not inherit the rules. Derive it withPartialType. Schema generation error (code-first approach)at startup:PartialTypecame from@nestjs/mapped-types. Import it from@nestjs/graphql.Cannot use "CreateCatInput" as a GraphQL input type for the "input" field because it is decorated with @ObjectType(): an argument needs@InputType().Cannot determine a GraphQL input type ("UpdateCatInput") for the "input": the derived class still needs its own@InputType().
Remember
- Mutations change state and return what changed; they run in order.
@InputType()describes what a client may send;@ObjectType()what it gets back.- The schema checks types, class-validator checks rules; errors arrive in
errorswith a200. PartialTypefrom@nestjs/graphqlkeeps field types and validation, every field optional.
Stuck? Show a hint
main.ts: app.useGlobalPipes(new ValidationPipe()). create-cat.input.ts: @InputType() on the class and @Field() on each property, @Field(() => Int) on age, @Field({ nullable: true }) on nickname. update-cat.input.ts: @InputType() export class UpdateCatInput extends PartialType(CreateCatInput) {}, PartialType from @nestjs/graphql. The resolver: @Mutation(() => Cat) createCat(@Args('input') input: CreateCatInput), updateCat with an Int id and the input, @Mutation(() => Boolean) removeCat.