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

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

Related

Graphql query using a variable for one of the arguments

I'm using Gatsby and Graphql to build a website and I'm using the 'createPage' API from Gatsby to create all my pages, sourcing the data from the graphql CMS https://hygraph.com/.
I can pass over dynamic values to my template just fine using the 'gatsby-node.js' file and createPage's 'context' like so:
createPage({
path: `/path-to-my-page',
component: require.resolve(`./src/my-template.js`),
context: {
product_attribute: "metal",
product_attributeValue: "silver"
},
})
When I come to create the page query in my template, I have passed over one dynamic value to my template, the "product_attributeValue" and the following code works just fine:
export const pageQuery = graphql`
query ProductListingByAttributeQuery(
$product_attribute: String
$product_attributeValue: String
) {
gcms {
products(
orderBy: updatedAt_DESC
where: {
metal: $product_attributeValue
}
) {
slug
title
image
content
}
}
}
`
But what I really want to do is use the "product_attribute" variable in the "where" instead of the string 'metal' like so:
where: {
$product_attribute: $product_attributeValue
}
But I get syntax errors saying it doesn't like that I've used $product_attribute in the where: {...} object. The error says "Syntax Error: Expected Name, found $".
Is it possible to do what I want to do here? In that I want to use the variable 'product_attribute' instead of a string?
I don't think you can use "standard" GraphQL syntax in Gatsby's umbrella, since Gatsby ships its own GraphQL implementation where where filter is not a valid operator. If you test the query in the GraphiQL playground, it will break (output the same error message) since it's not a valid option.
You are passing the context values properly but using them in an invalid way, you may want to use filter, along with in, ni, nin operators. More references: https://www.gatsbyjs.com/docs/graphql-reference/
For example:
export const pageQuery = graphql`
query ProductListingByAttributeQuery(
$product_attribute: String
$product_attributeValue: String
) {
gcms(productType: {eq: $product_type}) {
products {
slug
title
image
content
}
}
}
`
Note: this is not intended to be a copy/paste. It needs to be tweaked and refined
Something like this should work but as I said, test it and built the query using the GraphiQL playground (at localhost:8000/___graphql), it's really helpful

Referencing GraphQL variable used in query in Gatsby template?

I am aggregating all posts that have a certain tag in it's tag list:
export const Search = graphql`
query SearchResults($tag: String = "") {
allMarkdownRemark(filter: { frontmatter: { tags: { in: [$tag] } } }) {
nodes {
frontmatter {
title
date
}
}
}
}
`
The query works great, but I want to also be able to dynamically show the tag that is being queried for. How can I pass this information through?
For example: Searching results for tag: Java which would be the value inside $tag in the graphql query.
I tried to pull from the URL, but it's rendered in node, so I don't have access to the window object and it felt a bit hacky anyway.
I got it. Passing in props.pageContext gives you access to the contextual information passed in via gatsby-node.

GraphQL - What are best practices for nested queries?

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
}
}
}
}

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.

Remove read-only fields before mutation in GraphQL

I've got a type called Article in my schema:
type Article {
id: ID!
updated: DateTime
headline: String
subline: String
}
For updates to it, there's a corresponding input type that is used by a updateArticle(id: ID!, article: ArticleInput!) mutation:
input ArticleInput {
headline: String
subline: String
}
The mutation itself looks like this:
mutation updateArticle($id: ID!, $article: ArticleInput!) {
updateArticle(id: $id, article: $article) {
id
updated
headline
subline
}
}
The article is always saved as a whole (not individual fields one by one) and so when I pass an article to that mutation that I've previously fetched, it throws errors like Unknown field. In field "updated", Unknown field. In field "__typename" and Unknown field. In field "id". These have the root cause, that those fields aren't defined on the input type.
This is correct behaviour according to the spec:
(…) This unordered map should not contain any entries with names not
defined by a field of this input object type, otherwise an error
should be thrown.
Now my question is what a good way to deal these kinds of scenarios is. Should I list all properties that are allowed on the input type in my app code?
If possible I'd like to avoid this and maybe have a utility function slice them off for me which knows about the input type. However, since the client doesn't know about the schema, this would have to happen on the server side. Thus, the unnecessary properties would be transferred there, which I suppose is the reason why they shouldn't be transferred in the first place.
Is there a better way than maintaining a list of properties?
I'm using apollo-client, react-apollo and graphql-server-express.
You can use a fragment for the query, which includes all mutable fields of the data. That fragment can be used by a filter utility to remove all unwanted data before the mutation happens.
The gist is:
const ArticleMutableFragment = gql`
fragment ArticleMutable on Article {
headline
subline
publishing {
published
time
}
}
`
const ArticleFragment = gql`
fragment Article on Article {
...ArticleMutable
id
created
updated
}
${ArticleMutableFragment}
`;
const query = gql`
query Article($id: ID!) {
article(id: $id) {
...Article
}
}
${ArticleFragment}
`;
const articleUpdateMutation = gql`
mutation updateArticle($id: ID!, $article: ArticleInput!) {
updateArticle(id: $id, article: $article) {
...Article
}
}
${ArticleFragment}
`;
...
import filterGraphQlFragment from 'graphql-filter-fragment';
...
graphql(articleUpdateMutation, {
props: ({mutate}) => ({
onArticleUpdate: (id, article) =>
// Filter for properties the input type knows about
mutate({variables: {id, article: filterGraphQlFragment(ArticleMutableFragment, article)}})
})
})
...
The ArticleMutable fragment can now also be reused for creating new articles.
I've personally had same idea and took #amann 's approach earlier, but after some time the conceptual flaw of using query fragments on input types became evident. You would'n have an option to pick input type field that isn't present in (corresponding) object type - is there even any?
Currently I'm describing my input data by typesafe-joi schemas and using it's stripUnknown option to filter out my form data.
Invalid data never leaves form so valid data can be statically typed.
In a sense, creating joi schema is same activity as defining "input fragment" so no code duplication takes place and your code can be type-safe.

Categories