Home > Software design >  Why the data appears on mutation, but does not appear on query?
Why the data appears on mutation, but does not appear on query?

Time:05-23

I have students and they can enroll on different courses. When I create a new student, I have courses ids (array) as an argument to get the course information by the id. I am passing the information to the student courses array. The problem is that when I run the mutation, the new created student has courses data (shown in the picture below): enter image description here

But when I call the students query to see all the students, it gives an empty array for the courses of the newly created data. enter image description here

I used console.log() to check the data before and after the line -> await newStudent.save();. Before calling save it is an empty array, but after calling it the course appears. What is the reason for that?

Students entity:

@ObjectType()
export class Student {
    @Field()
    readonly _id: ObjectId;

    @Field()
    @Prop({ required: true })
    name: string;

    @Field()
    @Prop({ required: true, unique: true })
    email: string;

    @Field()
    @Prop({ required: true })
    password: string;

    @Field()
    @Prop({ required: true, unique: true })
    facultyNumber: number;

    @Field()
    @Prop({ default: Date.now() })
    lastLogin?: number;

    @Field(type => [Course], {nullable: true})
    @Prop({default: []})
    courses?: Course[]

}

Courses entity:

@ObjectType()
export class Course {
  @Field()
  readonly _id: ObjectId;

  @Field()
  @Prop({ required: true })
  title: string;

  @Field()
  @Prop({ required: true })
  description: string;

  @Field()
  @Prop({ required: true })
  subject: string;

  @Field()
  @Prop({ required: true })
  credits: number;

  @Field()
  @Prop({ required: true })
  tutor: string;

}

export const CourseModel = getModelForClass(Course);

Students resolver:

@Resolver()
export class StudentResolver {
    @Query(returns => Student)
    async student(@Arg("_id") _id: string): Promise<Student> {
        return await StudentModel.findById(_id);
    }

    @Query(returns => [Student])
    async students(): Promise<Student[]> {
        return await StudentModel.find({});
    }

    @Mutation(returns => Student)
    async createStudent(@Arg("data") data: StudentInput): Promise<Student> {
        const a: CourseResolver = new CourseResolver();

        const studentData = { ...data, password: bcryptjs.hashSync(data.password, 5) }
        const newStudent = new StudentModel(studentData);
        if(newStudent.courses == undefined){
            newStudent.courses = [];
        }
        data.coursesIDs.map((id) => {
            a.course(id).then((res) => {
                newStudent.courses.push(res);
            });
        });
        console.log("before saving courses", newStudent)
        await newStudent.save();
        console.log("after saving coures", newStudent)
        return newStudent;
    }
}

Course Resolver:

@Resolver()
export class CourseResolver {
    @Query(returns => Course)
    async course(@Arg("_id") _id: string): Promise<Course> {
        return await CourseModel.findById(_id);
    }
}

Students arguments:

@InputType({ description: "New student data" })
export class StudentInput {

    @Field()
    @MaxLength(50)
    name: string;

    @Field()
    @IsEmail()
    @MaxLength(30)
    email: string;

    @Field()
    @MinLength(6)
    password: string;

    @Field()
    @IsInt()
    @IsPositive()
    @Min(1000000000)
    facultyNumber: number;

    @Field(type => [String], { nullable: true})
    coursesIDs?: string[]
}

Solution:

await Promise.all(data.coursesIDs.map(async(id) => {
            const promise: Promise<Course>  = CourseModel.findById(id).then((res) => { 
                newStudent.courses.push(res); 
                return res;
            });
            await Promise.resolve(promise);
        }));

CodePudding user response:

Did you check your database to see if the courses are linked to the student?

The reason why it's not is because in your map, you're again executing a promise, which you should return, and then await all of these promises, before you save the records.

...
const promises = data.coursesIDs.map((id) => {
  return a.course(id).then((res) => {
    newStudent.courses.push(res);
  });
});
await Promise.all(promises)
...

Short version

await Promise.all(data.coursesIDs.map(id => a.course(id).then(newStudent.courses.push)))

When you query the database, you should also populate the relationships.

StudentModel.findById(_id).populate('courses')

and

StudentModel.find({}).populate('courses')


Also check the usage of shared services, which you can inject into the resolvers (Dependency Injection) and call these services to resolve your courses, instead of creating an new instance of the CourseResolver within your mutation.

Create a 'CourseService' and inject in both the StudentResolver and CourseRolvers. The 'CourseService' should have a method like findOneByID, which calls the method on the model or through a repository.

  • Related