Move fast with GraphQL, Rails, and TypeScript
Tags: graphql, rails, typescript
Published: 2020-05-06 22:55:48

Here at OneHQ, we've had great success using GraphQL with Rails and TypeScript. I wanted to take the time to show off a couple techniques that we use to enable developers to move fast ~~and break things~~:

1. [Dynamically create GraphQL types in Ruby](#dynamically-create-graphql-types-in-ruby)
2. [Commit the GraphQL schema to version control](#commit-the-graphql-schema-to-version-control)
3. [Auto-generate GraphQL types in TypeScript](#autogenerate-graphql-types-in-typescript)

### Dynamically create GraphQL types in Ruby
Our database schemas are continuously evolving, and we didn't want the overhead of having to keep our tables, models, and GraphQL types in sync. ActiveRecord gets around this by using meta-programming to dynamically analyze the database to generate methods. Why couldn't we do the same with GraphQL? 

***

We could and we did! Thus was born [hq-graphql](https://github.com/OneHQ/hq-graphql). hq-graphql is a wrapper around [graphql-ruby](https://graphql-ruby.org/) that does all of the necessary magic to integrate with ActiveRecord. Instead of creating a bajillion types with a bazillion fields...
```ruby
class AdvisorType < ::GraphQL::Object
  field :id, ID, null: false
  field :created_at, ISO8601DateTime, null: false
  field :name, String, null: false
  # <bazillion fields go here (use your imagination)>
  # <bazillion fields go here (use your imagination)>
  # <bazillion fields go here (use your imagination)>
end
```
We can now simply reference the model to dynamically create fields.
```ruby
class AdvisorType < ::HQ::GraphQL::Object
  with_model "Advisor"
end
```

***

### Commit the GraphQL schema to version control
Once we began creating types dynamically, I feared that devs would add/remove columns from our schema without realizing that it affected GraphQL. Too much magic can be bad, especially if there isn't feedback for when something changes.

For this reason, we dump and commit the GraphQL IDL (Interface Definition Language) to a file named schema.graphql whenever the schema changes. Checking in the schema to version control helps both the developer and code reviewer identify breaking changes before they hit production.

To make things _even_ easier, we created a test that compares the current schema with what's committed so that they can't forget to check it in! This ensures that our CI process fails whenever a dev forgets to update the schema file.

### Auto-generate GraphQL types in TypeScript
Another fear of mine was that we would update the schema and forget to update the front-end!<sup>(I'm afraid of a lot)</sup> Of course you could write tests to circumvent this (and we do), but what if we could auto-generate our GraphQL types in TypeScript instead of creating them by hand? This would give us another layer of protection and allow for us to easily correct any disparities when something does change.

***

Good news! This was the easiest of all our problems as there's already a tool out there that does this. [Apollo Codegen](https://github.com/apollographql/apollo-tooling#apollo-clientcodegen-output).

Point codegen to the code that uses GraphQL and run it! It pulls out any GraphQL queries and uses introspection to dynamically generate your types. If the GraphQL queries in your client don't align with your back-end, then it throws an error and lets you know what's wrong
```zsh
$ apollo client:codegen src/graphql/types/index.ts --endpoint=http://[::1]:8080/graphql --target=typescript --includes='src/graphql/**/**/*.{ts,tsx}' --tagName=gql --addTypename --outputFlat
  ⠏ Loading Apollo Project
    Generating query files
.../src/graphql/application/useApplications.ts: Cannot query field "names" on type "Application". Did you mean "name"?
ToolError: Validation of GraphQL query document failed
```

***

## Conclusion
Using meta-programming to generate types has saved us countless hours at OneHQ by allowing us to focus on what really matters. **But beware!!!** Using meta-programming without checks and balances in your code review process could lead to a broken app.