Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Query Result as Argument in Next Level in GraphQL

Tags:

graphql

apollo

Hullo everyone,

This has been discussed a bit before, but it's one of those things where there is so much scattered discussion resulting in various proposed "hacks" that I'm having a hard time determining what I should do.

I would like to use the result of a query as an argument for another nested query.

query {
  allStudents {
    nodes {
      courseAssessmentInfoByCourse(courseId: "2b0df865-d7c6-4c96-9f10-992cd409dedb") {
        weightedMarkAverage
        // getting result for specific course is easy enough
      }
      coursesByStudentCourseStudentIdAndCourseId {
        nodes {
          name
          // would like to be able to do something like this
          // to get a list of all the courses and their respective
          // assessment infos
          assessmentInfoByStudentId (studentId: student_node.studentId) {
            weightedMarkAverage
          }
        }
      }
    }
  }
}

Is there a way of doing this that is considered to be best practice? Is there a standard way to do it built into GraphQL now?

Thanks for any help!

like image 839
calben Avatar asked Jan 15 '19 04:01

calben


1 Answers

The only means to substitute values in a GraphQL document is through variables, and these must be declared in your operation definition and then included alongside your document as part of your request. There is no inherent way to reference previously resolved values within the same document.

If you get to a point where you think you need this functionality, it's generally a symptom of poor schema design in the first place. What follows are some suggestions for improving your schema, assuming you have control over that.

For example, minimally, you could eliminate the studentId argument on assessmentInfoByStudentId altogether. coursesByStudentCourseStudentIdAndCourseId is a field on the student node, so its resolver can already access the student's id. It can pass this information down to each course node, which can then be used by assessmentInfoByStudentId.

That said, you're probably better off totally rethinking how you've got your connections set up. I don't know what your underlying storage layer looks like, or the shape your client needs the data to be in, so it's hard to make any specific recommendations. However, for the sake of example, let's assume we have three types -- Course, Student and AssessmentInfo. A Course has many Students, a Student has many Courses, and an AssessmentInfo has a single Student and a single Course.

We might expose all three entities as root level queries:

query {
  allStudents {
    # fields
  }
  allCourses {
    # fields
  }
  allAssessmentInfos {
    # fields
  }
}

Each node could have a connection to the other two types:

query {
  allStudents {
    courses {
      edges {
        node {
          id
        }
      }
    }
    assessmentInfos {
      edges {
        node {
          id
        }
      }
    }

  }
}

If we want to fetch all students, and for each student know what courses s/he is taking and his/her weighted mark average for that course, we can then write a query like:

query {
  allStudents {
    assessmentInfos {
      edges {
        node {
          id
          course {
            id
            name
          }
        }
      }
    }

  }
}

Again, this exact schema might not work for your specific use case but it should give you an idea around how you can approach your problem from a different angle. A couple more tips when designing a schema:

  • Add filter arguments on connection fields, instead of creating separate fields for each scenario you need to cover. A single courses field on a Student type can have a variety of arguments like semester, campus or isPassing -- this is cleaner and more flexible than creating different fields like coursesBySemester, coursesByCampus, etc.
  • If you're dealing with aggregate values like average, min, max, etc. it might make sense to expose those values as fields on each connection type, in the same way a count field is sometimes available alongside the nodes field. There's a (proposal)[https://github.com/prisma/prisma/issues/1312] for Prisma that illustrates one fairly neat way to do handle these aggregate values. Doing something like this would mean if you already have, for example, an Assessment type, a connection field might be sufficient to expose aggregate data about that type (like grade averages) without needing to expose a separate AssessmentInfo type.
  • Filtering is relatively straightforward, grouping is a bit tougher. If you do find that you need the nodes of a connection grouped by a particular field, again this may be best done by exposing an additional field on the connection itself, (like Gatsby does it)[https://www.gatsbyjs.org/docs/graphql-reference/#group].
like image 79
Daniel Rearden Avatar answered Nov 09 '22 21:11

Daniel Rearden