It is only natural to expect the GraphQL endpoint (in public beta) to return content from the Pages app, since the Pages app is so popular.

However, as we at Magnolia looked at how this would work concretely, we have come to doubt if this would actually be useful. Whereas the existing REST Delivery endpoint works well for this use case.

Now we want your, the Magnolia community, input on the topic. Maybe we are missing something! Please share your opinion and thoughts, whether you are in favor or against, and why. You can share as comments on this wiki page, or via this web form.


In summary, the most common use case with the Pages app is that a developer wants to retrieve the entire contents of a page, including all of its areas and all of its components. But central to the GraphQL approach is that you must specify exactly which content you want returned. Specifying everything that you want returned from a page is very verbose when you consider all of the different areas and components which could be placed on a page. You would almost want some additional code to generate the GraphQL query for you based on the component availability on the page! 

See the examples below.


What GraphQL queries of "page-like" content looks like

Consider the following three examples.

Would this be useful for you in your projects?

GraphQL

In GraphQL, to handle queryig across a set of types - as we need to do to get the different components which could be in an area - you need to use "Unions" or "Interfaces".

From the GraphQL documentation here is an example of a query on unions

https://graphql.org/learn/schema/#union-types

  search(text: "an") {
    __typename
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}


Example from another CMS

We can see that GraphCMS, for example takes this approach:

https://dev.to/graphcms/make-no-compromises-on-content-design-with-graphql-union-types-1ig2


query PageQuery($slug: String!) {
  page(where: { slug: $slug }) {
    blocks {
      __typename
      ... on Cta {
        content
        title
      }
      ... on Grid {
        columns {
          __typename
          ... on Feature {
            content
            title
          }
        }
      }
      ... on Hero {
        subtitle
        title
      }
    }
  }
}


How this could look in Magnolia

(Not implemented, just an example of how a query could look.)

query getPageByPath($path: String) {
    page(path: $path) {
        _metadata {
            id
            name
            path
            template
        }
        content {
            ... on travel_demo__pages__pageProperties {
                title
                windowTitle
            }
        }
        main: Area(name: "main") {
            _metadata {
                id
                name
                path
                template
            }
            components {
                _metadata {
                    id
                    name
                    path
                    template
                }
                content {
                    _metadata {
                        id
                        name
                        path
                        template
                    }
                    ... on travel_demo__components__columnLayout {
                        areas {
                            components {
                                _metadata {
                                    id
                                    name
                                    path
                                    template
                                }
                                content {
                                    _metadata {
                                        id
                                        name
                                        path
                                    }
                                    ... on sample_module__components__TextImage {
                                        headline
                                        text
                                        image {
                                            link
                                        }
                                    }
                                    ... on travel_demo__components__teaser {
                                        teaserTitle
                                        teaserAbstract
                                        image {
                                            link
                                        }
                                    }
                                    ... on mtk__components__video {
                                        assetautoplay
                                        assetcontrols
                                        assetloop
                                        assetmuted
                                        assetpreload
                                        assetsource {
                                            link
                                        }
                                        scale
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        footer: Area(name: "footer") {
            _metadata {
                id
                name
                path
            }
            components {
                _metadata {
                    id
                    name
                    path
                }
                content {
                    _metadata {
                        id
                        name
                        path
                    }
                    ... on sample_module__components__TextImage {
                        title
                        asset {
                            link
                        }
                    }
                }
            }
        }
    }
}


Could GraphQL help at the component template level?

Instead of using a GraphQL endpoint to deliver the contents of a page, could it help in a different way?

A benefit of GraphQL is that you can easily retrieve linked content. What if you could specify exactly what content would be returned from at the component level? 

  • You put a simple file with a graphQL query adjacent to your component definition and dialog files
  • It specifies which properties should be returned, including linked content such as from a content app or from assets
  • You then hit a new "graphQLPage" REST endpoint which obeys these fragments when assembling the JSON of the page by "walking" down the component heirarchy of the page. It would be a simple call, like using the delivery endpoints.. ie http://localhost:8080/magnoliaAuthor/.rest/graphQLPage/travel

This would be somewhat similar to freemarker rendering, ONE of the things a freemarker file does is specify which content to return (of course it also generates the HTML). 

To contrast to how that works with the REST delivery endpoints now: With the delivery endpoints you specify in the endpoint definition itself which references should be "resolved" - that is - which properties from linked content should be included in the response. But in the case of getting the content of a page, it would really be nicer as a developer to be able to configure that at the component level, woudn't it? Otherwise the REST endpoint configuration needs to anticipate all of the different components that could be on the page.

GraphQL could help with that. (But it could be specified in other ways too.)


Example - consider the Tour carousel on the homepage of the travel demo. What if you could put a snippet like this next to the dialog definition?

tourCarousel.graphql

{
	tours: Tours{ # Need a hint to know its type. Gets content from the "Tours" content app.
		name
		duration
		image: Asset{
			link
			renditions(renditionNames: "1600") {
                renditionName
                link
            }
		}
		tourTypes: Category{ # Gets content from the "Category" content app.
			displayName
			icon: Asset{
				link
			}
		}
	} 
}

Do you have other ideas?

Log in and drop a comment on this page, or drop us a line on or via this web form.

  • No labels

7 Comments

  1. Spent a lot of time recently thinking about how to integrate magnolia with an existing graphql server. As a consumer of the graphql api, I find myself not usually interested in reading/traversing directly the structure of a page. As the `pages` informations are useful only when consumed by a piece of software that can interpret it meaningfully e.g. The `@magnolia/react-editor` library.
    If there's no use case for actually exposing the whole page structure as a strictly typed entity, a possible alternative approach would be implement a custom JSON graphql scalar type that returns the whole `page` payload.
    It would loosen the type safety on the page resource, but it would also make possible to retrieve page informations no matter the nesting of the page structure.  

    Relevant links:
    1. Example on how to implement a cutom scalar server side: https://www.graphql-java.com/documentation/v16/scalars/
    2. Even if the JSON scalar is not standard, is quite easily supported by browser clients: https://www.graphql-tools.com/docs/scalars/ 

    1. Thanks for your input Luca. What advantages do you see this bringing over just using the delivery endpoint to get a page?

      Also it's fairly common to configure reference resolvers on the delivery endpoint in order to return some of the content "referenced" from a component on a page... for example a page component can link to "tours" in a tours workspace, and a reference resolveer can then return the name and image of a tour from that linked content directly in the single delivery endpoint response. Would this be possible with a custom scalar? Would there be a way to parametertize the request?

  2. The main advantage I see is in highly dynamic pages (mainly user defined). Where there's a set of components that can be mounted in any page. In such scenario, writing a query for every page is unpractical, as it is an operation that can be performed only by a developer.
    Retaining the ability of interacting over graphql with an unstructured query, would play for a quite nice integration with an already existing graphql server, as it would be possible to just stitch the already existing schema with the magnolia's one. Offering right away a unified interface to a third client. Instead of having to craft a custom resolver.

    The custom JSON scalar would completely loosen the type safety. Therefore, everything that is configured on the page resource (also references), would be just serialized and sent back as json. So, yes it would definitely be possible to embed all the the references in a single response, but of course the response would not be type safe.
    In terms of the parameters, it would be possible to only pass them at top level, but this shouldn't be too much of a problem as right now (rest endpoint) all the parameters are passed in the query string. Therefore, all the existing parameters are already top level parameters.

    An example query might look like the following:

    scalar JSON
    
    type RawPage {
      data: JSON
    }
    
    type RawPageQuery {
      path: String
      renditionNames: String[]
    
      ... all the props that should be passed either as
      params or query params ...
    
    }
    
    
    type Query {
      rawPage(input: RawPageQuery): RawPage
    }


    It would also be possible to offer both options, unstructured response as a JSON scalar, and the typed one. As they are not mutually exclusive. But that would of course increase the surface of code that should be maintained. 

    1. Thanks. I don't understand how referenceResolvers should be configured, currently they are configured on the REST endpoint definniton.
      You write "everything that is configured on the page resource (also references)". That makes logical sense that references could be configured on the page template, but that's not how things work today, so that would be a new feature.
      An alternative could be to pass the referenceResolver configuration as a parameter to the graphQL query. Thoughts?

      1. I think passing reference resolvers (or whatever other configuration) as a parameter in the graphql query might be a good middle ground. As it would already avoid the monster query!

  3. I don't understand "... all the props that should be passed either as params or query params ...". Is that basically that you would have to specifiy every single property as in the "getPageByPath" example above (directly after "

    How this could look in Magnolia")

    Or do you mean something else? I just dont think anyone would really want to provide such a complicated monster query.

    1. Agreed that the mentioned one is a monster query, I was not suggesting that. With the schema taking advantage of the JSON scalar (written in my previous comment), a query could look like this:

      {   
          rawPage(input: {
             path: "/",
             renditionNames: "600"
          }) {
             data
          }
      }