Using a GraphQL schema to supercharge team collaboration

Examples of using a GraphQL schema to automate data pieces across back-end and front-end.

GraphQL

A single source of truth for app data

There are plenty of goodies GraphQL has to offer, but in this post, I want to go over one of my favorites: the schema.

A GraphQL schema describes your data. Using GraphQL's fantastic type system, you can clearly define the public API of your entire back-end. It looks like this:

schema {
  query: RootQueryType
  mutation: RootMutationType
}

type RootQueryType {
  currentUser: CurrentUser
  billing: Billing
}

type RootMutationType {
  registerUser(
    email: String!
    invitationToken: String
    password: String!
  ): CurrentUser
  registerCompany(name: String!): Company
  resetPassword(password: String!, resetPasswordToken: String!): CurrentUser
  login(email: String!, password: String!): CurrentUser
  logout: CurrentUser
  updateBilling(input: UpdateBillingInput!): Billing
}

type CurrentUser {
  id: ID
  authToken: String
  firstName: String
  lastName: String
  email: String
  mobile: String
  isOwner: Boolean
  company: Company
}

type Company {
  id: ID
  name: String
}

type Billing {
  trialDaysLeft: Int
  plan: BillingPlan
  card: Card
  nextPaymentDue: String
}

enum BillingPlan {
  FREE
  SOLO
  TEAM
}

type Card {
  lastFourDigits: String
  type: String
}

This means that with a schema you have a single contract between the back-end and front-end.

With just this simple schema, we know exactly what we can do with our app's API. For example, we can:

  • run registerUser on new user signup
  • run logout to log the user out
  • run login to log the user in
  • run updateBilling to let the user subscribe using a credit card
  • query billing to see what plan the user is on

This type of contract on top of a solid type system opens up some sweet possibilities...

Abstracting implementation

With a schema, we know what our public contract is for our API. It doesn't matter if updateBilling is using Stripe or Paypal under the hood for payment processing, or if the back-end language is Elixir or Java, or if user data is being stored in PostgreSQL or MongoDB. We have a clean public API and can change the implementation details on back-end or front-end without breaking the rest of the system.

Working in tandem

A schema also lets you work quickly across the stack. For example, let's say we now want to add a feature where we can show an entire team (every user in a company). The front-end or back-end could propose some schema changes like:

extend type RootQueryType {
  team: [User]
}

type User {
  id: ID
  firstName: String
  lastName: String
  email: String
  mobile: String
  isOwner: Boolean
  company: Company
}

And now both the back-end and front-end can proceed with their implementations without holding each other back. Then, when implementations are ready, it should "just work" as long as the schema is respected.

Generating the schema from the API

Instead of maintaining the schema file manually, you can have it automatically generated by your API. There are a few ways you can do this, but I recommend using graphql-config and graphql-cli. Then you can use a .graphqlconfig like:

{
  "schemaPath": "schema.graphql",
  "extensions": {
    "endpoints": {
      "dev": "http://localhost:4000/api"
    }
  }
}

Next, just run graphql get-schema to update your schema.graphql file with the latest changes in the API.

Generating types for other type systems from the schema

Having a schema lets you automate other things from the type system as well. For example, you can generate types for other type systems from it (for TypeScript, Flow, Swift, Scala etc.) using something like apollo-codegen. For example, with Flow you can run:

apollo-codegen generate **/*.graphql --target flow --output schema.flow.js

to generate Flow types automatically from schema types. It can use a .graphqlconfig to locate your schema just like we did in the previous section.

Generating example data from the schema

With a schema, you can automatically generate example data that can be used for building out the front-end before the real back-end is ready or for testing. The usage depends on what tools and languages you are using. For example, if you are using Apollo with React on the front-end, you can use apollo-link-schema for this. You pass it mock functions for certain schema types that look like this (in JavaScript, with faker for example):

import faker from 'faker'import { MockList } from 'graphql-tools'

const mocks = {
  String: () => faker.lorem.text(),

  ID: () => faker.random.uuid(),

  CurrentUser: () => ({
    authToken: () => faker.random.uuid(),
    firstName: () => faker.name.firstName(),
    lastName: () => faker.name.lastName(),
    email: () => faker.internet.email(),
    mobile: () => faker.phone.phoneNumber(),
    isOwner: () => faker.random.boolean(),
  }),

  User: () => ({
    firstName: () => faker.name.firstName(),
    lastName: () => faker.name.lastName(),
    email: () => faker.internet.email(),
    mobile: () => faker.phone.phoneNumber(),
  }),

  Company: () => ({
    name: () => faker.company.companyName(),
  }),

  RootQueryType: () => ({
    team: () => new MockList([0, 30]),
  }),
}

export default mocks

Then the API is wrapped to use these mocked functions depending on the environment. So, for example, in a test or dev environment, we could get back example data to use in building out new features (before the real back-end is ready) or in tests!

Conclusion

GraphQL has a lot going for it. This is cool stuff. But especially having a schema as a public contract is very helpful. I bet we have just scratched the surface of what is possible. I'm excited about the future of GraphQL!