GraphQL - What are best practices for nested queries? - javascript

I’m having an issue and not sure if it’s my understanding of GraphQL or an issue with the platform i'm using (Strapi).
Context: For my use case, I want to show a portfolio of projects (or 'cases'). Each case has a group of contributors that worked on it. This field is separate to the author / creator of a case. The issue is that for technical reasons, a Case's contents is split into two types: draft and published. Which means when I want to get all articles, I need to go one level 'deeper' in the nesting to also include the array of contributors.
Case
-- Draft
---- Caseinfo
------ Contributors
-- Published
---- CaseInfo
------ Contributors
Currently I can get allCase and get the contents of each case, plus its contributors:
// Used in Gatsby's `<StaticQuery />`
// This works fine.
allCase {
edges {
node {
contributors {
name
}
}
}
}
But when I try to include the nesting
allCase(filter: {status: {eq: "published"}}) {
edges {
node {
published_contents {
id
title
// Cannot query field "contributors" on type "CasePublished_contents".
contributors {
name
}
}
}
}
}
So as soon as the contributors field is nested one level deeper, I cannot query it?
Conceptually, can you make a deeply nested GraphQL query like this and expect it to follow all relational content types “down the tree”?

Yes, you cannot query a field of a higher-level entity on the lower-level. The contributors field only exists on the node (a CaseInfo?) and not on the CasePublished_contents type.
Just don't nest it:
allCase(filter: {status: {eq: "published"}}) {
edges {
node {
published_contents {
id
title
}
contributors {
name
}
}
}
}

Related

Query firebase realtime database where child has property

I'm trying to query my Firebase Realtime Database to find all games a user belongs to.
I have a list of games, each game has a property of players. Each child of players has a key of $uid, and within that {key: $uid, name: "joe"}
Is it possible to get all games this way? Or do I need to start keeping another index of players_games/$uid/$game?
I've tried firebase.database().ref('games').orderByChild('players').equalTo(token.uid), but this yields null
It looks like database.ref('games').orderByChild('players/${token.uid}') works, but then I'd need to give .read access to all of games, or do this server-side.
Your current data structure makes it easy to find all the users for a specific game. It does not however make it easy to find all the games for a specific user. To allow that, you'll want to add an addition data structure that inverts the information.
So that'd look something like this:
player_games: {
"XDYNyN8il6TDsM4LuttwDzNuytj1": {
"-M5vf...U5zK": true
},
"NxH14...mxY2": {
"-M5vf...U5zK": true
}
}
Also see:
Firebase query if child of child contains a value
Firebase Query Double Nested
I recommend you also study the Firebase documentation on structuring your database, specifically the section on avoiding nested data. By mixing entity types as you currently do, you'll likely run into problems with security, and scalability.
The most idiomatic way to model your many-to-many relationship in the Firebase database is with four top-level lists:
players: {
$playerId: { ... }
}
games: {
$gameId: { ... }
}
player_games: {
$playerId: {
$gameId: true
}
}
game_players: {
$gameId: {
$playerId: true
}
}
Also see:
Many to Many relationship in Firebase

Using Apollo's writeFragment to update nested list

I am working on a application in which a ship can be configured using rudders and other stuff. The database structure is sort of nested, and so far I have been keeping my GraphQL queries in correspondence with the database.
That means: I could fetch a ship using some query ship(projectId, shipId), but instead I am using a nested query:
query {
project(id:1) {
id
title
ship(id:1) {
id
name
rudders {
id
position
}
}
}
}
Such a structure of course leads to a lot of nested arrays. For example, if I have just added a new rudder, I would have to retrieve using cache.readQuery, which gives me the project object rather than the rudder list. To add the rudder to the cache, I'd get a long line with nested, destructured objects, making the code hard to read.
So I thought of using GraphQL fragments. On the internet, I see them being used a lot to prevent having to re-type several fields on extensive objects (which I personally find very useful as well!). However, there are not so many examples where a fragment is used for an array.
Fragments for arrays could save all the object destructuring when appending some data to an array that is nested in some cached query. Using Apollo's readFragment and writeFragment, I managed to get something working.
The fragment:
export const FRAGMENT_RUDDER_ARRAY = gql`
fragment rudderArray on ShipObject {
rudders {
id
position
}
}
`
Used in the main ship query:
query {
project(id: ...) {
id
title
ship(id: ...) {
id
name
...rudderArray
}
}
}
${RUDDER_FRAGMENT_ARRAY}
Using this, I can write a much clearer update() function to update Apollo's cache after a mutation. See below:
const [ createRudder ] = useMutation(CREATE_RUDDER_MUTATION, {
onError: (error) => { console.log(JSON.stringify(error))},
update(cache, {data: {createRudder}}) {
const {rudders} = cache.readFragment({
id: `ShipObject:${shipId}`,
fragment: FRAGMENT_RUDDER_ARRAY,
fragmentName: 'rudderArray'
});
cache.writeFragment({
id: `ShipObject:${shipId}`,
fragment: FRAGMENT_RUDDER_ARRAY,
fragmentName: 'rudderArray',
data: {rudders: rudders.concat(createRudder.rudder)}
});
}
});
Now what is my question? Well, since I almost never see fragments being used for this end, I find this working well, but I am wondering if there's any drawbacks to this.
On the other hand, I also decided to share this because I could not find any examples. So if this is a good idea, feel free to use the pattern!

GraphQL Unions and Sequelize

I'm having trouble understanding how to retrieve information from a GraphQL Union. I have something in place like this:
const Profile = StudentProfile | TeacherProfile
Then in my resolver I have:
Profile: {
__resolveType(obj, context, info) {
if (obj.studentId) {
return 'StudentProfile'
} else if (obj.salaryGrade) {
return 'TeacherProfile'
}
},
},
This doesn't throw any errors, but when I run a query like this:
query {
listUsers {
id
firstName
lastName
email
password
profile {
__typename
... on StudentProfile {
studentId
}
... on TeacherProfile {
salaryGrade
}
}
}
}
This returns everything except for profile which just returns null. I'm using Sequelize to handle my database work, but my understanding of Unions was that it would simply look up the relevant type for the ID being queried and return the appropriate details in the query.
If I'm mistaken, how can I get this query to work?
edit:
My list user resolver:
const listUsers = async (root, { filter }, { models }) => {
const Op = Sequelize.Op
return models.User.findAll(
filter
? {
where: {
[Op.or]: [
{
email: filter,
},
{
firstName: filter,
},
{
lastName: filter,
},
],
},
}
: {},
)
}
User model relations (very simple and has no relation to profiles):
User.associate = function(models) {
User.belongsTo(models.UserType)
User.belongsTo(models.UserRole)
}
and my generic user resolvers:
User: {
async type(type) {
return type.getUserType()
},
async role(role) {
return role.getUserRole()
},
},
The easiest way to go about this is to utilize a single table (i.e. single table inheritance).
Create a table that includes columns for all the types. For example, it would include both student_id and salary_grade columns, even though these will be exposed as fields on separate types in your schema.
Add a "type" column that identifies each row's actual type. In practice, it's helpful to name this column __typename (more on that later).
Create a Sequelize model for your table. Again, this model will include all attributes, even if they don't apply to a specific type.
Define your GraphQL types and your interface/union type. You can provide a __resolveType method that returns the appropriate type name based on the "type" field you added. However, if you named this field __typename and populated it with the names of the GraphQL types you are exposing, you can actually skip this step!
You can use your model like normal, utilizing find methods to query your table or creating associations with it. For example, you might add a relationship like User.belongsTo(Profile) and then lazy load it: User.findAll({ include: [Profile] }).
The biggest drawback to this approach is you lose database- and model-level validation. Maybe salary_grade should never be null for a TeacherProfile but you cannot enforce this with a constraint or set the allowNull property for the attribute to false. At best, you can only rely on GraphQL's type system to enforce validation but this is not ideal.
You can take this a step further and create additional Sequelize models for each individual "type". These models would still point to the same table, but would only include attributes specific to the fields you're exposing for each type. This way, you could at least enforce "required" attributes at the model level. Then, for example, you use your Profile model for querying all profiles, but use the TeacherProfile when inserting or updating a teacher profile. This works pretty well, just be mindful that you cannot use the sync method when structuring your models like this -- you'll need to handle migrations manually. You shouldn't use sync in production anyway, so it's not a huge deal, but definitely something to be mindful of.

Add graphQL fragment to schema, and have available for all queries

The following executes correctly in graphiQL
fragment BookGridFields on Book {
_id
title
}
{
allBooks {
...BookGridFields
}
}
My question is, it possible to specify the fragment right in my schema, right below where my Book type is defined, like so
type Book {
_id: String
title: String
pages: Int
weight: Float
authors: [Author]
}
fragment BookGridFields on Book {
_id
title
}
So that I could just run queries like this
{
allBooks {
...BookGridFields
}
}
without needing to define the fragment as part of my query.
Currently the above errors with
Unknown fragment \"BookGridFields\"
Per the graphql docs, I see that fragments are a part of the query api and not valid syntax for setting up a schema. This leads me to conclude that it is not currently possible to specify a fragment in a schema.
https://graphql.org/learn/queries/#fragments

Foreign key conditions in sailsjs Waterline ORM

I have defined two models like below, what i need to do is to retrieve the unique neighborhoods that belong to active partners (partner.status=1), i can retrieve the grouped neighborhoods like this
Locations.find({groupBy: [ 'neighborhood' ],sum['id']}, function(err,locations){});
and then match against a retrieved list of active Partners or backwards (ie first getting the active partners with the locations for each partner and pushing them to an array after verifying they are not already in there) or just by a custom sql query (which i am trying to stay away from)
but... i want to know if there is some kind of way to do it with the ORM as i have seen model.find().populate('model') doesn't accept parameters beyond the desired model not the where in the find method accept foreign keys conditions.
Partner:{
attributes:{
businessName:{
type:'string'
},
status:{
type:'INT'
}
locations:{
model:'Locations'
via:'partner_id'
}
}
}
Locations:{
attributes:{
partner_id:{
model:'Partners',
type:'INT'
},
neighborhood:{
type:'STRING'
}
}
}
Thanks in advance!
Is this what you looking for?
Locations.find({where: {value1: key1}})
.populate('partners', {where: {value2: key2}})
.exec(function(err, locations){
// Now you've got what you need
});
P/S: Please checkout SailsJS v0.10.x documentation on GitHub. Personally I use this: https://github.com/balderdashy/sails-docs/tree/0.10
The documents are not up-to-date though :-)

Categories