Typegraphql+TypeORM: save / update an array of entities

Viewed 4602

I have a graphql server and a role table, and I would like to save multiple roles within one mutation.

I searched a lot but found nothing.

How can I do something like:

mutation {
  addRoles (roles: [
    {
      name: "role1",
      description: "role 1"
    },
    {
      name: "role2",
      description: "role 2"
    },
    ...
  ])
}

In other words, how to implement addRoles and updateRoles resolver?

Is a for loop the only option? Is it possible to save all roles in one DB call?

The role model:

@Entity("role")
@ObjectType()
export class Role extends BaseEntity {
  @Field((type) => Number)
  @PrimaryGeneratedColumn()
  readonly id!: number;

  @Field()
  @Column({ length: 64 })
  name!: string;

  @Field({ nullable: true })
  @Column({ length: 512, nullable: true })
  description!: string;
}

And add and update resolver:

@Resolver((of) => Role)
export class RoleResolver {

  @Mutation((returns) => Boolean)
  async addRole(
    @Arg("role") role: AddRoleInput
  ): Promise<Boolean> {
    const roleExists = await Role.count({ name: role.name });
    if (roleExists > 0)
      throw new Error(`Role with name "${role.name}" already exists!`);

    const newRole = Role.create(role);
    await newRole.save();
    return true;
  }

  @Mutation((returns) => Boolean)
  async updateRole(
    @Arg("role") role: UpdateRoleInput
  ): Promise<Boolean> {
    const oldRole = await Role.findOneOrFail(role.id);
    Object.assign(oldRole, role);
    await oldRole.save();
    return true;
  }
}

And AddRoleInput and UpdateRoleInput


@InputType({ description: "New Role Argument" })
export class AddRoleInput implements Partial<Role> {
  @Field()
  name!: string;

  @Field({ nullable: true })
  description?: string;
}

@InputType({ description: "Update Role Argument" })
export class UpdateRoleInput implements Partial<Role> {
  @Field()
  id!: number;

  @Field()
  name!: string;

  @Field({ nullable: true })
  description?: string;
}
2 Answers

I managed to find an answer by trial&error. I'll post it here in case someone had same issue:

@Resolver((of) => Role)
export class RoleResolver {

  @Mutation((returns) => Boolean)
  async addRoles(
    @Arg("roles", type => [AddRoleInput]) roles: AddRoleInput[]
  ): Promise<Boolean> {

    // also checks at front
    const roleExists = await Role.count({
      name: In(roles.map((role) => role.name)),
    });
    if (roleExists > 0)
      throw new Error(`Role name conflict!`);
 
    const newRoles = []
    for (let i=0, i < roles.length, i++) {
        const newRole = Role.create(roles[i]);
        newRoles.push(newRole);
    }

    // this is the solution
    // turns out an entity class can be used to save instances
    // failed to found this in the docs though
    await Role.save(newRoles);

    return true;
  }
...
}

I'll keep the bounty open in case there're better answers.

The gist of what you'll need to do is define an addRoles field on the Mutation type in your schema. For example

export const RoleSchema = gql`
  type Role {
    id: Int
    # Other fields omitted for brevity
  }

  extend type Mutation {
    # This should already exist
    addRole(role: AddRoleInput!): Role!

    # To accept multiple Input Types, use the List construct
    addRoles(roles: [AddRoleInput!]!): [Role!]
  }
`;

Then define addRoles in your resolver and call the appropriate API's.

Related