THpubs
THpubs

Reputation: 8162

How to load the graphql queries from the server without defining it in the front end?

Now let's say we are using a REST API. I have one endpoint like this: /homeNewsFeed. This API will give us a response like this:

[
  {
    blockTitle: 'News',
    type: 'list',
    api: 'http://localhost/news'
  },
  {
    blockTitle: 'Photos',
    type: 'gallery',
    api: 'http://localhost/gallery'
  }
]

Now after getting this we go through the array and call the respective endpoints to load the data. My question is, how to do this in GraphQL? Normally we define the query in the front end code. Without doing that, how to let the server decide what to send?

The main reason to do this is. Imagine we have a mobile app. We need to push new blocks to this news feed without sending an app update. But each item can have their own query.

Upvotes: 1

Views: 555

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84657

Normally we define the query in the front end code. Without doing that, how to let the server decide what to send?

Per the spec, a GraphQL execution request must include two things: 1) a schema; and 2) a document containing an operation definition. The operation definition determines what operation (which query or mutation) to execute as well as the format of the response. There are work arounds and exceptions (I'll discuss some below), but, in general, if specifying the shape of the response on the client-side is undesirable or somehow not possible, you should carefully consider whether GraphQL is the right solution for your needs.

That aside, GraphQL lends itself more to a single request, not a series of structured requests like your existing REST API requires. So the response would look more like this:

[
  {
    title: 'News',
    content: [
      ...
    ],
  },
  {
    title: 'Photos',
    content: [
      ...
    ],
  }
]

and the corresponding query might look like this:

query HomePageContent {
  blocks {
    title
    content {
      # additional fields
    }
  }
}

Now the question becomes how do differentiate between different kinds of content. This is normally solved by utilizing an interface or union to aggregate multiple types into a single abstract type. The exact structure of your schema will depend on the data you're sending, but here's an example:

interface BlockContentItem {
  id: ID!
  url: String!
}

type Story implements BlockContentItem {
  id: ID!
  url: String!
  author: String!
  title: String! 
}

type Image implement BlockContentItem {
  id: ID!
  url: String!
  alt: String!
}

type Block {
  title: String!
  content: [BlockContentItem!]!
}

type Query {
  blocks: [Block!]!
}

You can now query blocks like this:

query HomePageContent {
  blocks {
    title
    content {
      # these fields apply to all BlockContentItems
      __typename
      id
      url
      # then we use inline fragments to specify type-specific fields
      ... on Image {
        alt
      }
      ... on Story {
        author
        title
      }
    }
  }
}

Using inline fragments like this ensures type-specific fields are only returned for instances of those types. I included __typename to identify what type a given object is, which may be helpful to the client app (clients like Apollo automatically include this field anyway).

Of course, there is still the issue of what happens when you want to add a new block. If the block's content fits an existing type, no sweat. But what happens when you anticipate you will need a different type in the future, but can't design around that right now?

Typically, that sort of change would require both a schema change on the server and a query change on the client. And in most cases, this will probably be fine because if you're getting data in a different structure, you will have to update your client app anyway. Otherwise, your app won't know how to render the new data structure correctly.

But let's say we want to future-proof our schema anyway. Here's two ways you could go about doing it.

  1. Instead of specifying an interface for content, just utilize a custom JSON scalar. This will effectively throw the response validation out the window, but it will allow you to return whatever you want for the content of a given block.

  2. Abstract out whatever fields might be needed in the future into some kind of value-key type. For example:

.

type MetaItem {
  key: String!
  value: String!
}

type Block {
  title: String!
  meta: [MetaItem!]!
  # other common fields
}

There's any number of other workarounds, some better than others depending on the kind of data you're working with. But hopefully that gives you some idea how to address the scenario you describe in a GraphQL context.

Upvotes: 4

Related Questions