Add variable to Subscription query

I am trying to query tasks in a subscription by a certain user… pretty basic. Here is my code:

GET_TASKS = gql`
subscription {
  query ($username: String!) {
    queryTask(user: $username) {
      id
      title
      completed
      user {
        username
      }
    }
  }
}
`;

I am getting some errors. I am sure I am formatting this wrongly. And of course I have:

query: this.GET_TASKS,
variables: {
  username: email,
},

How should I input the query?

Thanks,
J

Are you using apollo client? If not, how are you sending your query?

Yes. The problem is in the way I am trying to pass the username.

Are you using the useSubscription hook?

No. The problem is in the query itself. I do not know how to structure a subscription query to receive data, in this case the username. Generally speaking, I pass something using “variables” and it works fine. In this case, I am passing a “username” within a "user, which I am confused on, and I don’t know how to pass data when it is a subscription. I am generally confused on this.

Oh, sorry I see the problem now. It is not in adding a variable to a subscription but is in how to filter parent nodes with filters on the edges.

This might be a case for cascade, but it would perform better if there is an edge from the user to the tasks. What is the full schema for users and tasks?

type Task @withSubscription {
    id: ID!
    title: String! @search(by: [fulltext])
    completed: Boolean! @search
    user: User!
}
type User @withSubscription {
    username: String! @id @search(by: [hash])
    name: String
    tasks: [Task] @hasInverse(field: user)
}

Actually, I understand how to pass variables on apollo end, just not on the query end… I am not sure if I need a filter, or just pass in the value, or how to do either in this case.

Okay, so if you want to get tasks by a user, flip the query to get a users tasks instead of tasks by a user.

Use the query:

GET_TASKS = gql`
subscription ($username: String!) {
  getUser(username: $username) {
    username
    tasks {
      id
      title
      completed
    }
  }
}
`

That works! Thanks. Now how would I do that coming from the Tasks field instead?

Sure,

GET_TASKS = gql`
subscription ($username: String!) {
  queryTask {
    id
    title
    completed
    user(filter: { username: { eq: $username } }) {
      username
    }
  }
}
`

But there is a caveat here. If you run this, you will probably end up with errors that the field user is required to have a value but it returns null. That happens because the query returns all of the task nodes whether they have the specified user or not. The filter on the user edge, does not filter the tasks, but rather the users that are returned. So how to filter the tasks to only the specific users? Well, the best and most efficient way to do it is to run the query I sent you that roots with the getUser and then traverses to the tasks. But if you have to root at queryTask for whatever reason, there is a way to do that: use the @cascade directive.

subscription ($username: String!) {
  queryTask @cascade {
    id
    title
    completed
    user(filter: { username: { eq: $username } }) {
      username
    }
  }
}

There will work with another caveat. (Not in your specific case though) If any field your queried results in null, then that task will be removed from the final list of tasks returned. In your case, all of the fields are required, so you shouldn’t run into a case where a title does not have a title or completed field. However, your User could have a nullable name field. So if you query this field using cascade and even though the username matches, the task would still be removed from the returned list due to a null user.name field. The way around this is to use cascade with parameters to list the fields that should be required. You can refer to the docs for that use case.

I would personally limit uses of cascade as much as possible and instead rearrange queries to put the filtered edge on the root as much as possible for best performance.

1 Like

Very well answered and thought out. This helps me tremendously! Thank you sir.

1 Like

@amaster507

Quick question, why does a rule like this:

type Task @withSubscription @auth(
    query: { rule: """
        query($USER: String!) {
            queryTask {
                user(filter: { username: { eq: $USER } }) {
                    username
                }
            }
        }"""}), {
    id: ID!
    title: String! @search(by: [fulltext])
    completed: Boolean! @search
    user: User!
}

not need cascade?

Short answer just assume that auth query rules have cascade applied at the root. Any root node that does not have all of the fields will not pass the authorize rule.

1 Like