Transforming GraphQL data

The data coming back from 8base is not bad, but there are things I’d like to do such as remove .items, add firstName + lastName, and fill null objects before sending to the view layer. I currently do this by hand, mapping for the data and nested data doing the transforms.

Has anyone used transform libraries or have some ideas on the best approach? I can always roll my own, but it it would be nice if one existed that wasn’t overly complicated.

I’d imagine something like this:

const template = {
  assignee: {
    fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`,
    avatar: {
      downloadUrl: valueOr(null)
    },
    tasks: ({ tasks }) => tasks ? tasks : '—',
  }
}

const { assignee } = transform(template, data)

Some possibilities:

https://www.apollographql.com/docs/graphql-tools/schema-transforms

https://selecttransform.github.io/site/transform.html

Hey Mike - there’s some really good ideas that you point out here. I’ve seen some cool GraphQL features around this before that I think would be valuable. Stuff like:

query {
  myTable {
    /* Format a date */
    createdAt(format: "MM/DD/YY - HH:MM")
    /* Specify default value for field */
    someString(default: "not specified")
    /* Template a response from object data */
    fullName: template(format: "{lastName}, {firstName} is great") {
      firstName
      lastName
    }
  }
}

That said, I’m not sure whether removing the items array is something that would be supported on the 8base side. You would have to handle that transformation on your own side or build a custom resolver that can handle it.

I don’t mind doing some transforming on the client. It would be pretty interesting for the above to work, but I feel you’ll always hit some limit, and I feel it will complicate the GraphQL, trying to remember all the functions and syntax. I don’t know how it would interact with Apollo cache… Maybe it would work fine.

I’ve got a prototype transform function working, which allows me to write code like

task.assignee.avatar.downloadUrl

even if assignee or avatar is null and without having to write

task && task.assignee && task.assignee.avatar && ...

https://codepen.io/mikeaustin/pen/KKKQzWo?editors=0012

The template is what you want the data to looks like, and your provide nested objects and functions to provide the data. I tried several variations, ending with the “leaf” nodes using functions that get passed their parent object. This way you can build fullName as an independent function.

1 Like

Nice codepen. So are you planning on writing transform templates for all of your requests?

Basically, yes, so that in the view layer I don’t have to worry about nulls until the final leaf, and fill in and transform any data as needed. Something like:

const TodoContainer = () => {
  const { loading, error, data } = useQuery(GET_TODOS)

  <TodoPresenter todos={transform(template, data)} />
}

const TodoPresenter = ({ todos }) => {
  todos.map(({ assignee }) => (
    <Avatar
      name={assignee.fullName || '—'}
      image={assignee.avatar.downloadUrl}
    />
  )
}