There are two main operations for GraphQL - queries and mutations. If you look at the main schema.gql file on the backend, you will see a query and mutation object defined. These are the root level operations a client can make.

type Query {
  users: [User!]!
  user(id: Int!): User!
}

type Mutation {
  createUser(createUserInput: CreateUserInput!): User!
  updateUser(updateUserInput: UpdateUserInput!): User!
  removeUser(id: Int!): User!
}

type User {
  exampleField: String!
  exampleNum: Int!
}

input CreateUserInput {
  exampleField: String!
}

input UpdateUserInput {
  exampleField: String
  id: String!
}

Semantically, queries are for requesting data and mutations are for creating modifications. In the example above, we can either query users or user. The ! means the type is required, otherwise it is nullable.

In order to respond to these queries and mutations, we use resolvers. In our case, resolvers are classes that tell GraphQL how to respond to these queries. So a resolver for the users query must return an array of users.

// @Query = Generate a query
// () => [User] = Return type is [User]
// { name: "users" } = name the query "users"
@Query(() => [User], { name: "users" })
findAll() {
  // assume usersService returns an array of Users.
  return this.usersService.findAll();
}

Our backend uses Code-First GraphQL. That means you should not modify the schema.gql file by hand. Instead, use the nest g command to generate GraphQL resources. Modify them as necessary. Our application will use these classes to dynamically generate the schema file.

Input and Object Types

To generate a type like User, we use @ObjectType():

@ObjectType()
export class User {
  @Field(() => String, { description: "Example field (placeholder)" })
  exampleField: string;
  @Field(() => Int, { description: "Example field (placeholder)" })
  exampleNum: number;
}

For input types, we can similarly use @InputType().

Resolvers

Resolvers generally aim to resolve an @ObjectType.

@Resolver(() => User)
export class UsersResolver implements ResolverInterface<User> {
  @Query(() => User, { name: "user" })
  async findOne() {
    return {
      exampleNum: 4,
    };
  }

  @ResolveField()
  exampleField(@Root() user: User) {
    return user.exampleNum + ": exampleField";
  }

  @ResolveField()
  exampleNum(@Root() user: User) {
    return 5;
  }
}

Execution Order

Lets say we made the following query:

query GetUser {
  user {
    exampleField
    exampleNum 
  }
}