Does GQL support spreading / composing objects (eg. input args)? - javascript

I did not find anything about it in the Docs, but for me it seems to be a very common task, so is there a possibility/syntax or a workaround to achieve the following?
My GraphQL query looks something like this:
query customerPersons(
$pagination: PaginationInput!
$filter: FilterPersonInput
) {
persons(pagination: $pagination, filter: $filter) {
totalCount
data {
...PersonsData
}
}
}
With apollo and its codegen I get some helper React Hook function calls, which I can use like this:
const { data } = useCustomerPersonsQuery({
variables: {
pagination: {
skip: 0,
take: 20,
},
filter: {
query: 'peterchen',
userType: 'Customer'
}
});
Is it possible to move the userType: 'Customer' part into the static part of my query? If it would be JavaScript I would say it is assign or merge two objects with the spread operator.
Something like:
query customerPersons(
$pagination: PaginationInput!
$filter: FilterPersonInput
) {
persons(pagination: $pagination, filter: { ...$filter, userType: "Customer" }) {
totalCount
data {
...PersonsData
}
}
}

No, GraphQL does not support such syntax in query documents. The closest you could achieve would be creating separate variables for all the fields in FilterPersonInput other than userType.
The two solutions available are:
create a separate resolver in your backend that includes this filtering, providing a customers field in the schema next to the persons one. (If you're using unions/interface, this might be an opportunity to choose a more concrete result type as well).
create another React hook as a wrapper for the auto-generated one, and add the property to the variable value in there

Related

Conditional where condition in GraphQL Query

i have the following query:
query MyExampleQuery($templateQuestionId: Int!, $assetId: Int) {
report_answer(
where: {
report_template_question: { id: { _eq: $templateQuestionId } },
asset_id: { _eq: $assetId } }
) {
id
}
}
and i want the asset_id condition of the where only apply conditionally. When $assetId is provided it should be a condition, otherwise it should not apply at all.
How can this be achieved with GraphQL?
Yes but not in the way you have described above.
You have:
query MyExampleQuery($templateQuestionId: Int!, $assetId: Int)
Which says that $templateQuestionId is required but $assetId is optional.
When you write your resolver for this query you can take the existence (or absence) of assetId into account when you execute an actual database query.
But the GraphQL language itself does not have the concept of a where clause. Some GraphQL APIs include a where in their input objects but it's not part of the language.

Graphql filter query result

I am new using graphql and I would like to improve some features at my API, one of the is get a better filter.
This API should return some recipe base on the ingredients that the user will inform in the respective APP, This is The resolver I am using:
module.exports = {
Recipe: {
async ingredients(recipe, _, { dataSources }) {
return await dataSources.IngredientService.getRecipeIngredients(recipe.id)
},
},
Query: {
recipe: async () => db('Recipe'),
ingredient: async () => db('Ingredient'),
recipeByIngredient:async () => db('Recipe'),
}}
the service
class ingredientService {
async getRecipeIngredients(recipe_id) {
const filteredRecipe = db('Ingredient')
.where({ recipe_id })
.join('Recipe', 'Recipe.id', '=', recipe_id)
.select('Recipe.*', 'Ingredient.*')
.whereIn('Ingredient.name', ["sal", "açucar"])
return await filteredRecipe
}
the query schema
type Query {
recipe(name:[String]): [Recipe]
ingredient:[Ingredients]
recipeByIngredient(ingredients:String):[Ingredients]
}
type Recipe {
id: Int
title: String!
author: String
link: String
category: String
subcategory:String
ingredients:[Ingredients]
}
type Ingredients{
id:Int
name:String
quantity:Float
measure:String
observation:String
}
The filter is working but I would like ot improve 2 things:
When I see the return no the graphql "playground", when there is no value to the ingredient (that is in on different table from recipes), then the ingredient value is "null" and I would like to not even return the recipe.
I could not make the filter work. I reated the query type "recipe(name:[String]): [Recipe]", for example, but I do not know how to filter it from there. It means, I would like to ingredients filter over my query, filtering the result as expected
quer:
recipe(name :["sal", "açucar", "farinha"]){
id
title
author
link
category
subcategory
ingredients{
name
quantity
measure
observation
}
}
but as you can see, the resolver is hardcode, how could I sent the filter to the resolver?
can anyone help me on it?
Thanks a lot.
In general, to handle filtering, I set create a Condition type, named based on context. Here, maybe you'd like to pass a type RecipeCondition, which defines fields to effectively filter or scope the recipes returned, for example, based on whether it has ingredients in your datastore. This assumes you may want to add additional conditions in future (otherwise, could just hardcode condition in your sql).
type RecipeCondition {
ingredientsRequired: Bool
// Add other condition fields here as needed
...
}
type Query {
recipe(name:[String], condition: RecipeCondition): [Recipe]
...
}
I would handle the filter at the top level where you initially fetch recipes with db service (as opposed to handling in ingredients subresolver). You can simply use this condition, accessible on the recipe resolver arg, and pass it to your db service func that initially fetches a recipes array. If the condition ingredientsRequired is true, filter with sql appropriately (will require join to ingredients table and whereIn condition -- if your passing an array of recipe names, this may need to be completed iteratively). In this way, a recipe with no ingredients will not even hit the ingredients subresolver (assuming that condition is desired).
Thank you all that tried to help, all these comments was very important to guide to the final answer.
I got one posible solution, that I would like to share and get your feedback, if posible.
On first, I improved my Query resolver
Query: {
recipe(obj, {name}, {dataSources}, info) {
if (name) {
return dataSources.IngredientService.getIngredientsByName(name)
} else {
return db('Recipe')
}
}
Second, I changed my Service to receive the filter
async getIngredientsByName(name) {
const filteredRecipe = db('Ingredient')
//.where({ recipe_id })
.join('Recipe', 'Recipe.id', '=', 'Ingredient.recipe_id')
.select('Recipe.*', 'Ingredient.name', 'Ingredient.quantity', 'Ingredient.measure','Ingredient.observation')
.whereIn('Ingredient.name', name)
return await filteredRecipe
now is everything working fine and making the filter as expected.
Once again, thanks a lot, all of you.

Mock Relay-style pagination with Apollo Server

I have a mock server using Apollo Server to return GraphQL responses. One of the queries is to get teams which has an array of metrics (see schema below):
const mocks = {
Query: () => ({
teams: (/*parent, args, context, info*/) => teamsFixture,
}),
};
const graphServer = new ApolloServer({ typeDefs: schema, mocks });
graphServer.applyMiddleware({ app });
And my query used to be (fields redacted):
teams {
bpxId
metrics {
timestamp
}
}
The real (java) server has changed this to allow me to query (Relay style) only the first item in the metrics array as it wasn't performant:
teams {
bpxId
metrics(first: 1) {
edges {
node {
timestamp
}
}
}
}
With the response in this shape:
metrics: {
edges: [
{
node: [Team]
__typename: "TeamMetricsConnectionEdge"
}
]
__typename: "TeamMetricsConnection"
}
I want to add this capability to my Apollo Server mocks but not sure how?
New schema (relevant bits):
type TeamMetrics {
timestamp: Int
# etc
}
type TeamMetricsConnection {
edges: [TeamMetricsConnectionEdge]
pageInfo: PageInfo
}
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
teams(bpxId: Int): [Team]
}
type Team {
bpxId: Int!
metrics(first: Int! = 5, after: String): TeamMetricsConnection
}
How can I adjust my mock response to handle Relay pagination? Thanks
You can use graphql-relay to create mock resolver results. This could be an easy way to create pagination for static mock arrays. Use connectionFromArray to wrap an array of mocks to automatically create an object structure that fits the connection type. It is possible to access all the field arguments in the mock similarly how you would do it in a real resolver and pass them into the function.
I am not sure if this works when you call the function on a MockList though. Pagination is a bit tricky as well if the length of the results changes all the time (the pageInfo.hasNextPage might be mixed up and you might violate a lot of assumptions that Relay has according to the spec). So it might be fine to start with a simple static array if that satisfies your mocking needs.

Add custom GraphQL resolvers and types into Prisma/Nexus schema

Using: TypeScript, Prisma, MySQL, GraphQLServer, ApolloClient, building schema this way:
const schema = makePrismaSchema({
// Provide all the GraphQL types we've implemented
types: [Query, Mutation, User, Post],...
And then:
const server = new GraphQLServer({
schema,
context: { prisma }
});
How to combine that with custom resolvers and types unrelated to the SQL?
(I would like to call some REST endpoint by the GQL as well)
While nexus was created to be used alongside prisma, it's really just a schema builder. You could easily use it to create a schema without even utilizing Prisma. For example:
export const User = prismaObjectType({
name: 'User',
definition(t) {
t.list.field('comments', {
type: 'Comment',
resolve(root, args, ctx) {
return getComments();
},
});
},
})
export const Comment = prismaObjectType({
name: 'Comment',
definition(t) {
t.string('body');
},
})
Here getComments can return an array of comment objects, or a Promise that resolves to one. For example, if you're calling some other API, you'll normally return a Promise with the results of the call. As shown above, the resolver exposes the parent value, the field's arguments and a context object -- you can use any of this information in determining how to resolve a particular field.

What type of Relay mutator configuration is appropriate for inserting a new record?

I'm diving into GraphQL and Relay. So far, everything has been relatively smooth and easy, for me, to comprehend. I've got a GraphQL schema going with Accounts and Teams. There is no relationships between the two, yet. I've got some Relay-specific GraphQL adjustments for connections, for both the accounts and teams. Here's an example query for those two connections ...
{
viewer {
teams {
edges {
node {
id,
name
}
}
}
accounts {
edges {
node {
id,
username
}
}
}
}
}
I've got a GraphQL mutation ready to go that creates a new account. Here's the GraphQL representation of that ...
type Mutation {
newAccount(input: NewAccountInput!): NewAccountPayload
}
input NewAccountInput {
username: String!
password: String!
clientMutationId: String!
}
type NewAccountPayload {
account: Account
clientMutationId: String!
}
type Account implements Node {
id: ID!
username: String!
date_created: String!
}
I'm now trying to create my client-side Relay mutation that uses this GraphQL mutation. I'm thoroughly confused as to how to do this correctly, though. I've followed examples and nothing I come up with even seems to run correctly. I tend to get errors relating to fragment composition.
If I were writing a Relay mutation that uses this GraphQL mutation, what would the appropriate mutator configuration be? Should I be using RANGE_ADD?
For your client side mutation, you can use something like this:
export default class AddAccountMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on Viewer {
id,
}
`,
};
getMutation() {
return Relay.QL`mutation{addAccount}`;
}
getVariables() {
return {
newAccount: this.props.newAccount,
};
}
getFatQuery() {
return Relay.QL`
fragment on AddAccountPayload {
accountEdge,
viewer {
accounts,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'accounts',
edgeName: 'accountEdge',
rangeBehaviors: {
'': 'append',
},
}];
}
getOptimisticResponse() {
return {
accountEdge: {
node: {
userName: this.props.newAccount.userName,
},
},
viewer: {
id: this.props.viewer.id,
},
};
}
}
Then, in your GraphQL schema, you'll need to return the newly created edge, as well as the cursor:
var GraphQLAddAccountMutation = mutationWithClientMutationId({
name: 'AddAccount',
inputFields: {
newAccount: { type: new GraphQLNonNull(NewAccountInput) }
},
outputFields: {
accountEdge: {
type: GraphQLAccountEdge,
resolve: async ({localAccountId}) => {
var account = await getAccountById(localAccountId);
var accounts = await getAccounts();
return {
cursor: cursorForObjectInConnection(accounts, account)
node: account,
};
}
},
viewer: {
type: GraphQLViewer,
resolve: () => getViewer()
},
},
mutateAndGetPayload: async ({ newAccount }) => {
var localAccountId = await createAccount(newAccount);
return {localAccountId};
}
});
var {
connectionType: AccountsConnection,
edgeType: GraphQLAccountEdge,
} = connectionDefinitions({
name: 'Account',
nodeType: Account,
});
You'll need to substitute the getAccounts(), getAccountById() and createAccount method calls to whatever your server/back-end uses.
There may be a better way to calculate the cursor without having to do multiple server trips, but keep in mind the Relay helper cursorForObjectInConnection does not do any kind of deep comparison of objects, so in case you need to find the account by an id in the list, you may need to do a custom comparison:
function getCursor(dataList, item) {
for (const i of dataList) {
if (i._id.toString() === item._id.toString()) {
let cursor = cursorForObjectInConnection(dataList, i);
return cursor;
}
}
}
Finally, add the GraphQL mutation as 'addAccount' to your schema mutation fields, which is referenced by the client side mutation.
Right now, I'm following a roughly 5 step process to define mutations:
Define the input variables based on what portion of the graph you are targeting - in your case, it's a new account, so you just need the new data
Name the mutation based on #1 - for you, that's AddAccountMutation
Define the fat query based on what is affected by the mutation - for you, it's just the accounts connection on viewer, but in the future I'm sure your schema will become more complex
Define the mutation config based on how you can intersect the fat query with your local graph
Define the mutation fragments you need to satisfy the requirements of #1, #3 and #4
Generally speaking, step #4 is the one people find the most confusing. That's because it's confusing. It's hard to summarize in a Stack Overflow answer why I feel this is good advice but... I recommend you use FIELDS_CHANGE for all your mutations*. It's relatively easy to explain and reason about - just tell Relay how to look up the nodes corresponding to the top level fields in your mutation payload. Relay will then use the mutation config to build a "tracked query" representing everything you've requested so far, and intersect that with the "fat query" representing everything that could change. In your case, you want the intersected query to be something like viewer { accounts(first: 10) { edges { nodes { ... } } }, so that means you're going to want to make sure you've requested the existing accounts somewhere already. But you almost certainly have, and if you haven't... maybe you don't actually need to make any changes locally for this mutation!
Make sense?
EDIT: For clarity, here's what I mean for the fat query & configs.
getConfigs() {
return [
{
type: "FIELDS_CHANGE",
fieldIDs: {
viewer: this.props.viewer.id
}
}]
}
getFatQuery() {
return Relay.QL`
fragment on AddAccountMutation {
viewer {
accounts
}
}
`
}
*addendum: I currently believe there are only one or two reasons not to use FIELDS_CHANGE. The first is that you can't reliably say what fields are changing, so you want to just manipulate the graph directly. The second is because you decide you need the query performance optimizations afforded by the more specific variants of FIELDS_CHANGE like NODE_DELETE, RANGE_ADD, etc.

Categories

Resources