I'm trying to run a GraphQL query in the AWS AppSync console:
query MyQuery {
getUserInfoById(id: "1234566789") {
account {
id // need this value for getAvailableCourses
}
}
getAvailableCourses(accountId: "", pageNumber: 0) {
data {
id
name
type
}
}
}
Basically I need the value account.id in getUserInfoById for getAvailableCourses. I'm obviously new to GraphQL. How would I go about this?
To the best of my knowledge, there can be two ways you can do this.
You can handle this in your frontend by getting user's id
from the session info and pass it to the other query.
You can also merge these two queries and make it one. You will also have to change the respective fields. Then attach a resolver with AvailableCourses and use $ctx.source.id in the resolver to get further details. Schema would look something like this
type Account {
id : ID!
availableCourses: AvailableCourses
..
}
type AvailableCourses {
name: String!
type: String!
..
}
type Query {
getUserInfoById(id: ID!): Account
}
Using the returned fields as inputs for a second query into your datasource is precisely what field resolvers are for. I can't say for sure since I don't know your schema or access patterns but it looks like you need to make available courses a sub field of the user.
Related
For example, if there are two types User and Item
type User {
items: [Item!]!
}
type Item {
id: ID!
name: String!
price: Int!
}
If one user has PARTNER role.
I want to prevent it from being called only in the form of the query below.
query Query1 {
user {
items {
name
}
}
}
If user call another query, I want to indicate that user doesn't have permission.
query Query2 {
user {
items {
id
name
}
}
}
In short. if (Query1 != Query2) throw new Error;
Your question is a bit hard to follow but a couple things:
A GraphQL server is stateless - you cannot (and really should not) have a query behave differently based on a previous query. (If there's a mutation in between sure but not two queries back to back)
access management is normally implemented in your resolvers. You can have the resolver for the item id check to see if the user making the query has the right to see that or not and return an error if they don't have access.
Note that it can be bad practice to hide the id of objects from queries as these are used as keys for caching on the client.
I'm experimenting with AppSync + DynamoDB. I want to have the following types in my GraphQL Schema:
type User {
user_id: String!
}
type Box {
name: String!
user: User!
}
How can I create, in DynamoDB, a table storing items pointing to another table (In my case, I want the field user of the table BoxTable to be a reference to a user in the table UserTable?
How can I, in AppSync, define the above schema? When I set user: User!, I get the error Expected User! to be a GraphQL input type.?
As per my understanding of your question, these are my answers.
How can I create, in DynamoDB, a table storing items pointing to another table
DynamoDB is not a relational database and does not offer foreign keys or table joins. Therefore, to achieve what you have mentioned in your post, you would still require two calls to DynamoDB to get all the information for the Box i.e. first get the Box item from BoxTable and then get user from UserTable based on user_id. If your use case is such that you get user first, then you can get the Box using filter by user_id.
Now to the second part of your post,
How can I, in AppSync, define the above schema?
With DynamoDB unit resolvers, you can query against a single table (outside of DynamoDB Batch Operations but those are reserved for bulk use cases).
One way of doing this is by defining your schema that should look something like this;
type User {
user_id: String!
}
type Box {
name: String!
user: User!
}
input BoxInput {
name: String!
user: UserInput!
}
input UserInput {
user_id: String!
}
type Mutation {
createBox(input: BoxInput): Box
}
type Query {
getBox(input: BoxInput): Box
}
And this is how you can run query and mutation;
mutation createBox {
createBox(input: {
name: "abc"
user: { user_id: "1234-abcd-5678-efgh"}
}){
name
user { user_id }
}
}
query getBox {
getBox(input: {
name: "abc"
user: { user_id: "1234-abcd-5678-efgh"}
}){
name
user { user_id }
}
}
So, beware of the above query and mutation. These will show user as null unless you attach a separate resolver with you user type within your Box type. For example:
Query that returns Box --> Resolver
type Box {
name
user --> Attach resolver to get user_id from your UserTable
}
Other way is to utilize the pipeline resolvers in which you can create multiple functions, each of which can use the results of the previous function and query a database. These functions run in an order you specify. For example:
Function to get Box from BoxTable.
Function to get user from UserTable by using user_id from ctx.prev.result.
And finally consolidating above two result into one JSON object depending upon Box type in your schema.
Imagine the following query:
query {
user {
id
}
SomeOtherStuff(id: <--- I want to pass the id obtained from user) {
id
}
}
How do you pass a parameter obtained from one query to another ?
In GraphQL, fields at each "level" of the request are executed and resolved in parallel. In your example, user and SomeOtherStuff are both fields of the same type (the root Query type) -- so they will be resolved at the same time. That means each query essentially is not aware of the other or what the other resolved to.
You would have to handle this kind of scenario client side. In other words, request the user first, parse the response for the id and then make the second request.
Edit: In Apollo, you would utilize compose for this purpose:
const userQuery = gql`query User { user { id } }`;
const stuffQuery = gql`query SomeOtherStuff($id: ID) { someOtherStuff(id: $id){ stuff } }`;
export default compose(
graphql(userQuery, { name: 'userData' })
graphql(stuffQuery, { name: 'stuffData', options: ({userData:{id}={}}) => ({variables: {id}}) }),
)(YourComponent)
I agree with #DanielRearden. You should make type-resolvers so you can go infinitely deep into the graph. I made a simple server example here that shows deep relationships. Because all the noun-fields are references, it goes infinitely deep for any query.
With that server, you can run a query like this, for example:
{
hero {
name
friends {
name
friends {
name
friends {
name
friends: {
name
}
}
}
}
}
}
So, in your example, structure it like this:
query {
user {
id
otherStuff {
id
}
}
}
I was looking for same scenario and landed on this question. You can get it work other way around. It all depends how you have written your graphql resolver and you need to make sure that your database relations are intact. I have got it working like this.
I have users who can create lists. Lists can be starred ("featured" if you prefer). I want to build a Graphql query in Lighthouse/Laravel that would retrieve all the starred/featured lists for a given user.
I tried this query and it works:
clistsByStarred(user_id: Int! #eq, starred: Boolean! #eq): [Clist] #all
and then I can use it like that:
query {
clistsByStarred(user_id: 1, starred: true) {
id
name
starred
}
}
But it feels like a dirty hack to me.
What I would like to achieve is this:
query {
user(id: 1) {
id
name
clistsByStarred {
id
name
starred
}
}
}
Any idea on how to implement it ?
type User {
id: ID!
clists(starred: Boolean #eq): Clist #hasMany
This example gives you a user type which has a list of Clist types.
You then just add a field in your query to get a single user by id.
When you query this, you can then specify the parameter starred to only get the starred.
For this to work the relationship has to exist on your eloquent model.
https://lighthouse-php.com/master/eloquent/relationships.html
How would I go about having an Apollo query with different parameters. Let's say my app has the concept of users and I want my API to be able to find a user either by ID or username.
It doesn't seem I can do this
type Query {
user(id: ID!): User
user(username: String!): User
}
do I have to resort to something like this
type Query {
userById(id: ID!): User
userByUsername(username: String!): User
}
Create a new input type with ID and username as fields and specify it as parameter.
type Query {
getUser(filters:UserFilter): User
}
input UserFilter{
ID : Int,
Username : String
}
Query should look like this ...
Query{
getUser(filters:{ID:123123})
}
OR for username filter
Query{
getUser(filters:{Username:"Tom"})
}
I believe your second option is the best however you could do something like this.
type Query {
user(id: ID, username: String): User
}
If the parameters are not required then your resolver could use some logic to determine if it should get the user based on the id or the username.
if (args.id) {
return getUserById(args.id)
} else {
return getUserByUsername(args.username)
}
However for reasons gone over in this talk https://youtu.be/pJamhW2xPYw?t=750 (maybe see at about 12:30), I believe your second option is a better design choice. Hope that helps!