Propel & GraphQXL: the missing language extension

Large GraphQL APIs, like Propel’s, end up with a lot of boilerplate types and inputs. Learn how GraphQXL solves reusability with inheritance, generics, and imports while maintaining a schema-first approach friendly to API designers.

GraphQL Logo

Photo: Propel

GraphQL at Propel

At Propel, we use GraphQL extensively to enable our customers to communicate with us.

  • Customers can connect their data with Propel using our API.
  • They can execute real-time analytic queries using GraphQL.
  • They can manage their account's resources using GraphQL API requests.

The problem

As you may imagine, Propel's GraphQL API is pretty big, and we want to keep it clean, maintainable, and well-documented as possible. Unfortunately, sometimes GraphQL does not make things easy for us:

  • Common fields: While defining <span class="code-exp2">types</span>, we need to copy-paste all the common fields across a lot of resources, fields like <span class="code-exp2">id</span>, <span class="code-exp2">createdAt</span>, <span class="code-exp2">modifiedBy</span>...
  • CRUD inputs: When defining <span class="code-exp2">inputs</span> that are meant to create or modify specific resources, there is a lot of repeated code for handling the <span class="code-exp2">input</span> that creates the resource vs. the one that modifies it.
  • Cursor pagination: We follow the GraphQL Cursor Connections Specification, and for each paginated resource, we need to define its own <span class="code-exp2">Edge</span> and <span class="code-exp2">Connection</span> even though all look the same.
  • Documentation: We document every field, and if there is a common field across different <span class="code-exp2">types</span> or <span class="code-exp2">inputs</span>, we need to maintain the documentation individually for each one.

All these problems can be boiled down to one: the lack of tools the GraphQL SDL specification provides for reusing code.

Code-first approach?

Using a code-first approach and auto-generating the GraphQL schema is a very common way to overcome this problem, but it has some disadvantages:

  • For customer-facing schemas, it can get tricky to control and review exactly what is being served to the customers, as there is no direct control of the GraphQL schema.
  • Ensuring that the documentation is consistent across all the resources is less ergonomic, as the documentation pieces are spread through the code base.
  • Non-technical people find it more difficult to contribute to new API features, as they will need to deal with actual code (TypeScript, Python, GoLang...).

But what if there is another way?

GraphQXL

GraphQXL is a language built on top of the GraphQL SDL syntax that solves the reusability problems of the original language.

It is framework-agnostic, as it is just an independent language that compiles down to plain GraphQL, that way it can be used with any programming language that supports it.

Here are some of GraphQXL’s features:

Field inheritance

One common pattern is to have an <span class="code-exp2">interface</span> that defines some common fields that are shared across many types, for example, imagine that we want to reuse the field’s from this <span class="code-exp2">interface</span> across multiple types:

interface Resource {
  id: ID!
  createdAt: Date!
  modifiedAt: Date
  createdBy: String!
  modifiedBy: String
}

In GraphQXL spread operators can be used to solve this:

GraphQXL

interface Resource {
  id: ID!
  createdAt: String!
  modifiedAt: String
  createdBy: String!
  modifiedBy: String
}

type User implements Resource {
  ...Resource
  username: String!
}

type Product implements Resource {
  ...Resource
  price: Float!
}

GraphQL

interface Resource {
  id: ID!
  createdAt: String!
  modifiedAt: String
  createdBy: String!
  modifiedBy: String
}

type User implements Resource {
  id: ID!
  createdAt: String!
  modifiedAt: String
  createdBy: String!
  modifiedBy: String
  username: String!
}

type Product implements Resource {
  id: ID!
  createdAt: String!
  modifiedAt: String
  createdBy: String!
  modifiedBy: String
  price: Float!
}

Try it in the GraphQXL explorer!

Generics

In GraphQL APIs there are usually some <span class="code-exp2">types</span> or <span class="code-exp2">inputs</span> that are very similar, and one clear example is the cursor-based pagination. Generics are really good at handling structures that look almost the same but have a small difference:

GraphQXL

type Product {
  price: Float!
}

type User {
  username: String!
}

"""The ${{ variables.T }}'s Edge object for pagination"""
type _Edge {
  """The ${{ variables.T }}'s cursor that refers to the current node"""
  cursor: String!
  """The ${{ variables.T }} itself"""
  node: T!
}

"""The Connection object for paginating across the ${{ variables.T }}s"""
type _Connection {
  """Metadata with the current page info"""
  metadata: String!
  """List of ${{ variables.T }}s for the current page"""
  edges: [T!]!
}

type ProductEdge = _Edge
type ProductConnection = _Connection
type UserEdge = _Edge
type UserConnection = _Connection

GraphQL

type Product {
  price: Float!
}

type User {
  username: String!
}

"""The Product's Edge object for pagination"""
type ProductEdge {
  """The Product's cursor that refers to the current node"""
  cursor: String!
  """The Product itself"""
  node: Product!
}

"""The Connection object for paginating across the ProductEdges"""
type ProductConnection {
  """Metadata with the current page info"""
  metadata: String!
  """List of ProductEdges for the current page"""
  edges: [ProductEdge!]!
}

"""The User's Edge object for pagination"""
type UserEdge {
  """The User's cursor that refers to the current node"""
  cursor: String!
  """The User itself"""
  node: User!
}

"""The Connection object for paginating across the UserEdges"""
type UserConnection {
  """Metadata with the current page info"""
  metadata: String!
  """List of UserEdges for the current page"""

Try it in the GraphQXL explorer!

<aside>💡 Note that the documentation for the Edges and the Connections is also autogenerated following the templating rules in the source GraphQXL file.</aside>

Import statements

There are different ways and tools developers can use to split a GraphQL schema into multiple files and merge them afterwards, but GraphQXL provides its own: import statements.

The behavior is pretty straightforward and predictable, given this file <span class="code-exp2">common-stuff.graphqxl</span>

# common-stuff.graphqxl

type Identifier {
    id: ID!
}

It can be imported and reused into other <span class="code-exp2">.graphqxl</span> files:

GraphQXL

import "common-stuff"

type Product {
    id: Identifier
    price: Float!
}

GraphQL

type Identifier {
    id: ID!
}

type Product {
    id: Identifier
    price: Float!
}

A good usage of this feature could end up in a final .graphqxl that looks like this:

import "products"
import "users"
import "subscriptions"
# import other entities

type Query {
		..._ProductsQuery
		..._UsersQuery
    ..._SubscriptionsQuery
    # queries for the other entities
}

type Mutation {
		..._ProductsMutation
		..._UsersMutation
    ..._SubscriptionsMutation
    # mutations for the other entities
}

schema {
    query: Query
    mutation: Mutation
}

Is there more?

There is a lot more! Feel free to

Related posts

Start shipping today

Deliver the analytics your customers have been asking for.