Typegraphql+TypeORM: save / update an array of entities

6.9k Views Asked by At

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

There are 2 best solutions below

1
On

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.

0
On

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.