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.
Related
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
How to join column, that when i launch my function, he return me only columns name instead of entity_columnName.
I'm using TypeORM, and i try this;
const data = this.conn.getRepository(User).createQueryBuilder('user');
data.leftJoinAndSelect('user.orders', 'orders');
data.getRawMany();
but return me:
firstName: ...
lastName: ...
age: ...
order_name: ...
order_price: ...
instead of:
firstName: ...
lastName: ...
age: ...
name: ...
price: ...
can someone tell me how do this? thanks for any help
How are your User and Order entities defined?
If you define a relation (e.g. OneToMany) with the eager: true option, then TypeORM will automatically include the related entities when you query using the repository *find methods. It won't do this when you use the QueryBuilder, where you have to add them such as with leftJoinAndSelect() (as you have done).
An example from an Invoice entity that has OneToMany line items:
#OneToMany(
() => InvoiceLineItem,
(item: InvoiceLineItem) => item.invoice,
{ eager: true }
)
items: InvoiceLineItem[]
Per the example, if I were then to find() or findMany() invoices, then the related objects, items will be included as well because eager: true.
This behaviour might translate well to your situation with users and orders.
Also be aware of the differences between getMany() and getRawMany() when using the query builder.
If you use getMany() then TypeORM will automagically give you entities back (e.g. you'd get an instance of User with property orders that is an array of Order instances). The property names will be correct.
Since you added the NestJS tag to your question, also understand serialization:
https://docs.nestjs.com/techniques/serialization
There is a built-in ClassSerializerInterceptor that comes with NestJS that you might find useful.
In your controller you can decorate the class or any of its methods e.g.
#UseInterceptors(ClassSerializerInterceptor)
This will transform the response to JSON, and will apply the rules specified with class-transformer decorators on the entity/DTO class.
If you were to use this interceptor, the response sent to the client will have your desired property names.
If you really want to modify the response that your client gets back, you can also look into writing your own Interceptor.
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!
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.
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