Query an interface that implements another interface in GraphQL - graphql

I'm currently working on an Activity Feed built using GraphQL Nexus and Apollo Server (3.9.0). It will receive a flurry of information that will have shared fields that are handled by a shared IActivity interface.
In addition, there will be types that will also share similar fields through other interfaces that are not included in IActivity, say for example IMedia and INews.
We are looking for a way to bring all the fields from IActivity and be able to query IMedia and INews within IActivity. We know that there is a possibility to query the concrete types, but we'd like to avoid it, as we want to extend the backend by adding new types of feeds (That share the same fields of those interfaces) without updating the client (it's a react-native application). We would use concrete types only when rendering custom UI components.
Here's an example:
interface IActivity {
id: ID!
name: String!
description: String
}
interface IMedia implements IActivity {
id: ID!
name: String!
description: String
url: String
type: String
}
interface INews implements IActivity {
id: ID!
name: String!
description: String
date: DateTime
author: String
content: String
}
type Video implements IActivity & IMedia {
id: ID!
name: String!
description: String
url: String
type: String
format: String
codec: String
}
type Image implements IActivity & IMedia {
id: ID!
name: String!
description: String
url: String
type: String
compressed: Boolean
codec: String
extension: String
}
type Audio implements IActivity & IMedia {
id: ID!
name: String!
description: String
url: String
type: String
bitrate: Boolean
}
type Post implements INews & IActivity {
id: ID!
name: String!
description: String
date: DateTime
author: String
content: String
comments: [Comment]
}
type Comment {
author: String
message: String
}
type FlashNews implements INews & IActivity {
id: ID!
name: String!
description: String
date: DateTime
author: String
content: String
rating: Int
}
type Query {
activityFeed(): [IActivity]
}
This is the query
query getFeed() {
activityFeed {
id
name
description
... on IMedia {
url
type
}
... on INews {
date
author
content
}
}
}
This queries all the fields from IActivity but none of IMedia nor INews. There are no GraphQL errors (and we lint them through graphql-codegen and the Nexus builder.)
Our belief was that having IActivity also share the same as other interfaces, we could query IActivity and then specify the other interface (e.g: IMedia) as we do with concrete types.
In hindsight, what we're trying to do is somehow a union type of interfaces (which I know it's not possible)...
Is there a workaround or a solution for what we're trying to accomplish?
Edit
We found out that this exact example IS valid, and the problem is within how GraphQL Nexus is configured.
Here's a code sandbox and its Git Repo using plain Apollo Server.
Edit 2:
Try the following query:
query getFeed {
activityFeed {
id
name
description
... on IMedia {
type
format
}
... on INews {
date
author
content
}
... on Video {
length
}
}
}

We have found the problem. It's been Apollo Client (front-end) which was not properly parsing the heuristic fragment matcher.
More information on how we solved the issue can be found here:
https://stackoverflow.com/a/64886593/1057052
We generated something like this:
schema: "./appsync/appSync.gql"
# documents: "./appsync/**/*.gql"
generates:
src/generated/graphql.schema.json:
plugins:
- 'fragment-matcher'
apolloClientVersion: 3
Then the interfaces were working properly!

Related

Spring GraphQLmultiple schemas with Query per file

with Spring-GraphQl if I have following two schemas in the resources/graphql folder:
schema1:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
schema2:
type Query {
personByName(name: String): Person
}
type Person {
id: ID
firstName: String
lastName: String
}
Spring-GraphQL seems to be merging them into one GraphQL schema file and starting of Spring-Boot Graphql app ends with following error:
Caused by: graphql.schema.idl.errors.SchemaProblem: errors=['Query' type [#1:1] tried to redefine existing 'Query' type [#1:1]]
When I change it to:
schema1:
type Query {
bookById(id: ID): Book
personByName(name: String): Person
}
schema2:
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
type Person {
id: ID
firstName: String
lastName: String
}
it works perfectly good and I am able to call both queries with graphiql. How graphql spring works with multiple schemas? It seems spring-graphql merges files into one schema so multiple Query types per file breaks the app.
Thanks for answer.
Spring GraphQL is loading all schema resources under the configured location and is using TypeDefinitionRegistry::merge to create a single schema out of them.
I think that redifining any type (even the Query one) should raise an error, otherwise this could hide important issues and conflicting schema definitions. That's what GraphQL Java's TypeDefinitionRegistry is doing.
You can organize your schema files like this:
graphql/schema.graphqls
type Query {
}
// add common directives, scalars, etc
graphql/books.graphqls
extend type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
graphql/person.graphqls
extend type Query {
personByName(name: String): Person
}
type Person {
id: ID
firstName: String
lastName: String
}

How to merge results from two gql queries in to one array of results?

We have two services exposing two sets of schemas, merged in a gateway using Graphql Tools Schema Stitching
Is it possible to merge queries from two services in such a way that it returns combined results?
Example case:
Book service contains data for books
interface Searchable {
id: ID!
}
type Book implements Searchable {
id: ID!
name: String
# other fields
}
type Query {
_search( term: String ): [Searchable]
}
User Service has the data for authors
interface Searchable {
id: ID!
}
type Author implements Searchable {
id: ID!
name: String
# other fields
}
type Query {
_search( term: String ): [Searchable]
}
Gateway
interface Searchable {
id: ID!
}
type Book implements Searchable {
id: ID!
name: String
# other fields
}
type Author implements Searchable {
id: ID!
name: String
# other fields
}
type Query {
search( term: String ): [Searchable]
}
I can recommend using GraphQL-Mesh - it uses tools under the hood, and enables you to easily merge multiple sources (GraphQL and many others), manipulate it and get one GraphQL endpoint / schema

How to federate two apollo services that provide the same type

I am new to apollo and I have two apollo service that I want to federate by using apollo federation:
Productservice:
extend type Query {
job(id: String!): Job
}
type Seo {
title: String!
description: String!
keywords: String!
}
type Product #key(fields: "id") {
id: ID!
title: String!
seo: Seo!
}
StaffService:
extend type Query {
staffMember(id: String!): StaffMember
}
type Seo {
title: String!
description: String!
keywords: String!
}
type StaffMember #key(fields: "id") {
id: ID!
title: String!
seo: Seo!
}
How can I use the type Seo in response objects of both objects? Is the correct procedure to create an interface Seo and implement StaffMemberSeo and ProductSeo or is there an annotation that allows me to define the exactly same type within two services?
One service must own the type. In that service use the #key directive. In the referencing services use #extend, and include a stub of the type with fields used by that service.
Think of this as like a foreign key in a SQL database.

Custom schema, interface, #fileByRelativePath and gatsby-image

I'm trying to get an interface working with the new #fileByRelativePath resolver extension, to keep compatible with v3.
I'm using Prismic for my content, and gatsby-source-prismic v2. I have two content types in Prismic, and created the interface to be able to more easily query and map over both for a home page index.
Here's the functioning (but with deprecated inferred resolvers) schema:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
interface indexPosts #nodeInterface {
id: ID!
uid: String!
data: Data!
type: String!
}
type Data {
title: Title!
date: Date!
featured: String!
featured_image: Featured_image!
body: Body!
}
type Title {
text: String!
}
type Featured_image {
localFile: File!
}
type Body {
html: String!
}
type PrismicGallery implements Node & indexPosts {
uid: String!
data: Data!
type: String!
}
type PrismicEssay implements Node & indexPosts {
uid: String!
data: Data!
type: String!
}
`
createTypes(typeDefs)
}
The problem comes after adding #fileByRelativePath to the Featured_image type definition. Doing so gives me an error during build:
"The "path" argument must be of type string. Received type undefined"
I'm unsure how to provide the necessary path argument, considering my images are third-party hosted. I'm trying to follow the brief guide at the end of this page and suspect the way to do it might be with a resolver or type builder and using 'source' to access the url field provided by both localFile and its parent, featured_image, but I can't figure it out!
I'm using gatsby-image and the childImageSharp convenience field to present the images, if that makes a difference at all!
I had exactly the same problem when I tried to use #fileByRelativePath. I managed to solve my problem by using #infer on the type that contained the File.
Try this:
type Featured_image #infer {
localFile: File!
}

Graphql with nested mutations?

I am trying to figure out how to mutate a nested object with graphql mutations, if possible. For instance I have the following schema:
type Event {
id: String
name: String
description: String
place: Place
}
type Place {
id: String
name: String
location: Location
}
type Location {
city: String
country: String
zip: String
}
type Query {
events: [Event]
}
type Mutation {
updateEvent(id: String, name: String, description: String): Event
}
schema {
query: Query
mutation: Mutation
}
How can I add the place information inside my updateEvent mutation?
Generally speaking, you should avoid thinking of the arguments to your mutations as a direct mapping to object types in your schema. Whilst it's true that they will often be similar, you're better off approaching things under the assumption that they won't be.
Using your basic types as an example. Let's say I wanted to create a new event, but rather than knowing the location, I just have the longitude/latitude - it's actually the backend that calculates the real location object from this data, and I certainly don't know its ID (it doesn't have one yet!). I'd probably construct my mutation like this:
input Point {
longitude: Float!
latitude: Float!
}
input PlaceInput {
name
coordinates: Point!
}
type mutation {
createEvent(
name: String!
description: String
placeId: ID
newPlace: PlaceInput
): Event
updateEvent(
id: ID!
name: String!
description: String
placeId: ID
newPlace: PlaceInput
): Event
)
A mutation is basically just a function call, and it's best to think of it in those terms. If you wrote a function to create an Event, you likely wouldn't provide it an event and expect it to return an event, you'd provide the information necessary to create an Event.
If you want to add a whole object to the mutation you have to define a graphql element of the type input. Here is a link to a small cheatsheet.
In your case it could look like this:
type Location {
city: String
country: String
zip: String
}
type Place {
id: String
name: String
location: Location
}
type Event {
id: String
name: String
description: String
place: Place
}
input LocationInput {
city: String
country: String
zip: String
}
input PlaceInput {
id: ID!
name: String!
location: LocationInput!
}
type Query {
events: [Event]
}
type Mutation {
updateEvent(id: String, name: String, description: String, place: PlaceInput!): Event
}
schema {
query: Query
mutation: Mutation
}

Resources