Different field types depending on args - graphql

My database is structured as following:
There is a Product table:
id (integer)
manufacture_id (integer)
state (boolean)
And there is a Product_translations table
product_id (integer)
language_id (integer)
name (string)
description (string)
When querying for a product I would like to be able to receive a name and description directly if I provide a language id as an argument, or receive a list of translations with all the language id's and name/description instead if I don't provide a language id.
Is there a way to achieve this without creating two different Types and two different Queries?

Yes and no.
When you specify the return type for your query (let's call it getProduct), you can only specify one type (or a union or interface... more on that later). That type (Product) will have an immutable list of fields. When you make a request to your server, you will have to identify a subset of those fields to have the server return. With this in mind, it's not possible (at least natively) to send a query have the server return a different subset of fields depending on those arguments.
That said, what you can do is define a type that includes all the possible fields, like this:
type Product {
id: ID!
name: String
description: String
translations: [Translation!]!
}
Then within your resolver for getProduct, you can fetch the product from the table and then check whether language was provided as an argument. If it wasn't, fetch the list of translations and set your product's translations property to it. If language was provided, fetch just that translation, use it to populate the name and description properties of the product, and set translations to an empty array.
In this way, depending on whether language is passed in as an argument, your returned Product will contain either A) null for name and description and a populated list of translations; or B) a name and description and an empty array for translations.
There is, IMHO, also a more elegant alternative: unions and interfaces.
As before, you'd need to construct your returned object appropriately based on whether the language argument is present. But instead of a type, you return a Union or Interface and then utilize the __resolveType field to return a specific type (each with different fields).
There's two advantages to this approach: One, you avoid returning unnecessary null fields. And two, if you use Apollo as a client, it automatically tacks on a __typename field that you can use on the client-side to easily determine the type that was actually returned by a query.
Here's an example you can plug right into Launchpad to play around with:
import { makeExecutableSchema } from 'graphql-tools';
const typeDefs = `
type Query {
getProduct (id: ID, language: ID): ProductInterface
},
type Product implements ProductInterface {
id: ID
translations: [Translation!]!
},
type TranslatedProduct implements ProductInterface {
id: ID
name: String
description: String
},
type Translation {
language: ID
name: String
description: String
},
interface ProductInterface {
id: ID
}
`;
const products = [
{
id: '1',
translations: [
{
language: '100',
name: 'Foo',
description: 'Foo!'
},
{
language: '200',
name: 'Qux',
description: 'Qux!'
}
]
}
]
const resolvers = {
Query: {
getProduct: (root, {id, language}, context) => {
const product = products.find(p => p.id === id)
if (language) {
product.translation = product.translations.find(t => t.language === language)
}
return product
},
},
ProductInterface: {
__resolveType: (root) => {
if (root.translation) return 'TranslatedProduct'
return 'Product'
}
},
TranslatedProduct: {
name: (root) => root.translation.name,
description: (root) => root.translation.description
}
};
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
You can then request a query like this:
{
getProduct (id: "1", language: "200") {
__typename
... on Product {
translations {
language
name
description
}
}
... on TranslatedProduct {
name
description
}
}
}

Related

How should I query and match data from the same response in GraphQL with Apollo Client and Link Rest?

I have the following query:
const getPage = gql`
query Page($path: String!) {
page(path: $path) #rest(type: "Page", path: "{args.path}") {
blocks #type(name: Block) {
name
posts #type(name: Post) {
body
author
}
}
authors #type(name: Author) {
name
}
}
}
In blocks.posts.author there's only an AuthorId. The authors object is containing all the available authors.
I'd like to replace/match the AuthorId with it's corresponding object. Is it possible to do this within one query?
I also wouldn't mind to have a separate query for Author only (fetch will be cached, no new request would be made), but I still don't know how would I match it through 2 queries.
Example API response
{
blocks: [
{
posts: [
{
id: 1,
title: 'My post',
author: 12,
}
]
}
],
authors: [
{
id: 12,
name: 'John Doe'
}
]
}
What I want with 1 query that author inside a post becomes the full author object.
Great question. With GraphQL, you have the power to expand any field and select the exact subfields you want from it, so if you were using GraphQL on your backend as well this would be a non-issue. There are some workarounds you can do here:
If all of the Author objects are in your Apollo cache and you have access to each Author's id, you could use ApolloClient.readFragment to access other properties, like this:
const authorId = ...; // the id of the author
const authorInfo = client.readFragment({
id: authorId,
fragment: gql`
fragment AuthorInfo on Author {
id
name
# anything else you want here
}
`,
});
Although it's worth noting that with your original query in the question, if you have all of the Author objects as a property of the query, you could just use Javascript operations to go from Author id to object.
const authorId = ...; // the id of the author
data.page.authors.find(author => author.id === authorId);
The following should work.
First, capture the author id as a variable using the #export directive. Then add a new field with some name other than author and decorate it with the #rest, using the exported variable inside the path.
So the query would look something like this:
query Page($path: String!) {
page(path: $path) #rest(type: "Page", path: "{args.path}") {
blocks #type(name: Block) {
name
posts #type(name: Post) {
body
author #export(as: "authorId")
authorFull #rest(
path: '/authors/{exportVariables.authorId}'
type: 'Author'
) {
name
}
}
}
authors #type(name: Author) {
name
}
}
}
You can use the fieldNameNormalizer option to rename the author property in the response to a field with a different name (for example, authorId). Ideally, that should still work with the above so you can avoid having a weird field name like authorFull but apollo-link-rest is a bit wonky so no promises.

Use GraphQL to retrieve an object that contains an array of objects with different schemas

I am trying to write a query to retrieve an object with the property linkedCards that contains an array of objects with different schemas.
I have 3 different schemas (built in Contentful):
CardA example:
{
id: 42,
productName: 'Laptop',
price: 999
}
CardB example:
{
id: 999,
title: 'Buy our refurbished Laptops today!'
}
CardC example:
{
id: 100,
linkedCards: [
{
id: 42,
productName: 'Laptop',
price: 999
},
{
id: 999,
title: 'Buy our refurbished Laptops today!'
}
]
}
Query:
allCardC() {
nodes {
linkedCards {
id
title
}
}
}
When I try to run the following GraphQL query I get
"Cannot query field "title" on type "CardACardBUnion". Did you mean to use an inline fragment on "CardA" or "CardB"?"
Is there a built-in way to do this or can I use the ids of CardA & CardB somehow? Perhaps have one query to get the ids of the cards in linkedCards and another query to get said cards?
As the error indicates, you need to use an inline fragment when querying a field that resolves to a union:
allCardC {
nodes {
linkedCards {
... on CardA {
id
productName
price
}
... on CardB {
id
title
}
}
}
}
Fragments can be defined inline within a selection set. This is done to conditionally include fields based on their runtime type.
Unlike interfaces or regular object types, unions do not specify any particular fields, only the types that make up the union. That means a selection set for a field that returns a union must always use fragments to conditionally specify the fields depending on the actual type that the field resolves to.
It's like saying, "if this is the actual type of the returned object, request these fields".
You may find it useful to use a GraphQL interface to specify the fields that every card type has in common.
interface Card {
id: ID!
}
# type CardA implements Card { ... }
type CardB implements Card {
id: ID!
title: String!
}
type CardC implements Card {
id: ID!
linkedCards: [Card!]!
}
As #DanielRearden's answer suggests you still need to use (inline) fragments to select fields that are specific to one of the card types, but now that you know every card has an id field, you can select that directly.
allCardC {
nodes {
linkedCards {
id
... on CardB { title }
}
}
}

Skipping over a resolver for a query [duplicate]

I think I'm missing something obvious in the way GraphQL resolvers work. This is a simplified example of my schema (a Place that can have AdditionalInformation):
import { ApolloServer, gql } from 'apollo-server';
const typeDefs = gql`
type Place {
name: String!
additionalInformation: AdditionalInformation
}
type AdditionalInformation {
foo: String
}
type Query {
places: [Place]
}
`;
And the associated resolvers:
const resolvers = {
Query: {
places: () => {
return [{name: 'Barcelona'}];
}
},
AdditionalInformation: {
foo: () => 'bar'
}
};
const server = new ApolloServer({typeDefs, resolvers});
server.listen().then(({ url }) => {
console.log(`API server ready at ${url}`);
});
When I execute a basic query:
{
places {
name,
additionalInformation {
foo
}
}
}
I always get null as the additionalInformation:
{
"data": {
"places": [
{
"name": "Barcelona",
"additionalInformation": null
}
]
}
}
It's my first GraphQL app, and I still don't get why the AdditionalInformation resolver is not automatically executed. Is there some way to let GraphQL know it has to fire it?
I've found this workaround but I find it a bit tricky:
Place: {
additionalInformation: () => { return {}; }
}}
Let's assume for a moment that additionalInformation was a Scalar, and not an Object type:
type Place {
name: String!
additionalInformation: String
}
The value returned by the places resolver is:
[{name: 'Barcelona'}]
If you were to make a similar query...
query {
places {
name
additionalInformation
}
}
What would you expect additionalInformation to be? It's value will be null because there is no additionalInformation property on the Place object returned by the places resolver.
Even if we make additionalInformation an Object type (like AdditionalInformation), the result is the same -- the additionalInformation field will resolve to null. That's because the default resolver (the one used when you don't specify a resolver function for a field) simply looks for a property with the same name as the field on the parent object. If it fails to find that property, it returns null.
You may have specified a resolver for a field on AdditionalInformation (foo), but this resolver is never fired because there's no need -- the whole additionalInformation field is null so all of the resolvers for any fields of the associated type are skipped.
To understand why this is a desirable behavior, imagine a different schema:
type Article {
title: String!
content: String!
image: Image
}
type Image {
url: String!
copyright: String!
}
type Query {
articles: [Article!]!
}
We have a database with an articles table and an images table as our data layer. An article may or may not have an image associated with it. My resolvers might look like this:
const resolvers = {
Query: {
articles: () => db.getArticlesWithImages()
}
Image: {
copyright: (image) => `©${image.year} ${image.author}`
}
}
Let's say our call getArticlesWithImages resolves to a single article with no image:
[{ title: 'Foo', content: 'All about foos' }]
As a consumer of the API, I request:
query {
articles {
title
content
image
}
}
The image field is optional. If I get back an article object with a null image field, I understand there was no associated image in the db. As a front end client, I know not to render any image.
What would happen if GraphQL returned a value for the image regardless? Obviously, our resolver would break, since it would not be passed any kind of parent value. Moreover, however, as a consumer of the API, I would have to now parse the contents of image and somehow determine whether an image was in fact associated with the article and I should do something with it.
TLDR;
As you already suggested, the solution here is to specify a resolver for additionalInfo. You can also simply return that value in your places resolver, i.e.:
return [{name: 'Barcelona', additionalInfo: {}}]
In reality, if the shape of your schema aligns with the shape of your underlying data layer, it's unlikely you'll encounter this sort of issue when working with real data.

graphql using nested query arguments on parent or parent arguments on nested query

I have a product and items
Product:
{
id: Int
style_id: Int
items: [items]
}
Items:
{
id: Int
product_id: Int
size: String
}
I want to query products but only get back products that have an item with a size.
So a query could look like this:
products(size: ["S","M"]) {
id
style_id
items(size: ["S","M"]) {
id
size
}
}
But it seems like there should be a way where I can just do
products {
id
style_id
items(size: ["S","M"]) {
id
size
}
}
And in the resolver for the products I can grab arguments from the nested query and use them. In this case add the check to only return products that have those sizes. This way I have the top level returned with pagination correct instead of a lot of empty products.
Is this possible or atleast doing it the other way around:
products(size: ["S","M"]) {
id
style_id
items {
id
size
}
}
And sending the size argument down to the items resolver? Only way I know would be through context but the one place I found this they said that it is not a great idea because context spans the full query in all depths.
I agree with #DenisCappelini's answer. If possible, you can create a new type which represents only Products that have an Item.
However, if you don't want to do that, or if you're just interested in general about how a top-level selector can know about arguments on child selectors, here is a way to do that:
There are 2 ways to do it.
To do this:
products {
id
style_id
items(size: ["S","M"]) {
id
size
}
}
In graphql, resolvers have this signature:
(obj, args, context, info) => {}
The 4th argument, info, contains information about the entire request. Namely, it knows about arguments on the child selectors.
Use this package, or a similar one because there are others, to parse info for you: https://www.npmjs.com/package/graphql-parse-resolve-info
The above is quite a lot of work, so if you want to do this instead:
products(size: ["S","M"]) {
id
style_id
items {
id
size
}
}
Then in your resolver for products, you need to also return size.
Suppose this is your resolver for products:
(parent, args) => {
...
return {
id: '',
style_id: ''
}
}
Modify your resolver to also return size like this:
(parent, args) => {
...
return {
id: '',
style_id: '',
size: ["S", "M"]
}
}
Now in your resolve for products.items, you will have access to the size, like this:
(product, args) => {
const size = product.size
}
I found this useful #reference
//the typedef:
type Post {
_id: String
title: String
private: Boolean
author(username: String): Author
}
//the resolver:
Post: {
author(post, {username}){
//response
},
}
// usage
{
posts(private: true){
_id,
title,
author(username: "theara"){
_id,
username
}
}
}
IMO you should have a ProductFilterInputType which is represented by a GraphQLList(GraphQLString), and this resolver filters the products based on this list.
import { GraphQLList, GraphQLString } from 'graphql';
const ProductFilterInputType = new GraphQLInputObjectType({
name: 'ProductFilter',
fields: () => ({
size: {
type: GraphQLList(GraphQLString),
description: 'list of sizes',
}
}),
});
Hope it helps :)
these are few tweaks you can add and make your design better and also filter items properly.
1- change your product schema:
{
id: Int! # i would rather to use uuid which its type is String in gql.
styleId: Int
items: [items!] # list can be optional but if is not, better have item. but better design is below:
items(after: String, before: String, first: Int, last: Int, filter: ItemsFilterInput, orderBy: [ItemsOrderInput]): ItemsConnection
}
2- have a enum type for sizes:
enum Size {
SMALL
MEDIUM
}
3- change item schema
{
id: Int!
size: Size
productId: Int
product: Product # you need to resolve this if you want to get product from item.productId
}
4- have a filter type
input ItemFilterInput {
and: [ItemFilterInput!]
or: [ItemFilterInput!]
id: Int # you can use same for parent id like productId
idIn: [Int!]
idNot: Int
idNotIn: [Int!]
size: Size
sizeIn: [Size!]
sizeNotIn: [Size!]
sizeGt: Size # since sizes are not in alphabetic order and not sortable this wont be meaningful, but i keep it here to be used for other attributes. or you can also trick to add a number before size enums line 1SMALL, 2MEDIUM.
sizeGte: Size
sizeLt: Size
sizeLte: Size
sizeBetween: [Size!, Size!]
}
5- then create your resolvers to resolve the below query:
{
product {
items(filter: {sizeIn:[SMALL, MEDIUM]}) {
id
}
}
}
# if returning `ItemsConnection` resolve it this way:
{
product {
id
items {
edges {
node { # node will be an item.
id
size
}
}
}
}
}
Relay has a very good guideline to design a better schema.
https://relay.dev/
I also recommend you to add edges and node and connection to your resolvers to be able to add cursors as well. having product {items:[item]} will limit your flexibility.

How to return nested objects in GraphQL schema language

I was going through the documentation for GraphQl and realized that the new Schema Langugage supports only default resolvers. Is there a way I can add custom resolvers while using the new Schema Language?
let userObj = {
id: 1,
name: "A",
homeAddress: {
line1: "Line1",
line2: "Line2",
city: "City"
}
};
let schema = buildSchema(`
type Query {
user(id: ID): User
}
type User {
id: ID
name: String
address: String
}
`);
//I would like User.address to be resolved from the fields in the json response eg. address = Line1, Line2, City
This is the schema that I have defined. I would like to add some behavior here that would allow me to parse the address object and return a concatenated string value.
As mentioned by HagaiCo and in this github issue, the right way would be to go with graphql-tools.
It has a function called makeExecutableSchema, which takes a schema and resolve functions, and then returns an executable schema
It seems like you have a confusion in here, since you defined that address is String but you send a dictionary to resolve it.
what you can do, is to define a scalar address type:
scalar AddressType if you use buildSchema and then attach parse functions to it. (or use graphql-tools to do it easily)
or build the type from scratch like shown in the official documentations:
var OddType = new GraphQLScalarType({
name: 'Odd',
serialize: oddValue,
parseValue: oddValue,
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return oddValue(parseInt(ast.value, 10));
}
return null;
}
});
function oddValue(value) {
return value % 2 === 1 ? value : null;
}
and then you can parse the dictionary into a string (parseValue) and otherwise

Resources