How can you tell which protocol you should use for your API?
Both REST and GraphQL have their pros and cons. When it comes to the first one, it is easy to use and everyone is familiar with it. On the other hand, the latter provides an easier parameter validation, a possibility to get only the required parameters from a large response, etc.
For the purpose of our project, we’ve been thinking a lot and decided to use GraphQL. At first, things were going great. But as the team started to grow and a lot of functionalities started to pile up, we hit the first obstacles. Initially, we had everything written in a single schema file. So, we ended up with an extremely large file and lots of merge conflicts.
Divide and conquer
The solution for the large schema file was to modularize our schema and to move everything into smaller files. That went well. We had successfully mitigated one huge problem.
Don’t repeat yourself
After some time we noticed that we are repeating ourselves. We were coding methods with business logic first. After that, we added method calls inside the resolvers, followed by the schema definition. However, we noticed the following recurring patterns:
Our business logic methods:
Then the resolvers:
Finally our schema:
We came to an idea that the resolvers and schema parts can be easily generated. First, we did a research about which tools can do that and landed up with quite nice ones. Nevertheless, none of them was a good fit for our way of coding. At the end, we decided to create our own code generator.
Code Generator v1.0
Since our API is written in Typescript, we decided to proceed with a reflection-based solution. Unfortunately or luckily we weren’t able to get much meaningful metadata about our methods and were a bit afraid about the performance impact of the reflection-based solution. Thus, we agreed upon moving away from this approach.
Code Generator v2.0
The second idea was to listen for file changes, parse the code, then generate the schema and the resolvers as a part of a pre-build event.
Step 1 requires to read your file contents and to create ts.SourceFile object using ts.createSourceFile method.
Step 2 is iterating through your code’s AST. Using ts.SyntaxKind enum, you can evaluate what kind of node you are dealing with – whether the node is for each statement, class, method, etc. You can get all the necessary metadata for each node: a list of arguments for each method, the argument names and types, the entity properties, etc.
Armed with all this data, the only thing left is to generate your source code. First, you need a string representation of the resolver class with API/Service method calls. The second thing is GraphQL schema definition string. Finally, all strings will be saved in their appropriate files and you’re ready to roll.
How to tell if the class method is Query or Mutation?
In order to differentiate the purpose of the methods, we have implemented custom TypeScript decorators. Each of them is named after HTTP verbs. For example, every method for data fetching is marked with ‘@Get’ decorator. Hence, all other methods are marked with: ‘@Post’, ‘@Put’ and ‘@Delete’ decorators respectfully.
Now, having the Generator as a solution for the problem with the large schema file, we can be more focused on the business logic. It’s quite a relief knowing that we don’t have large files to merge or tedious duplicate code to write. Additional ease is the ability to switch from GraphQL to REST in a matter of minutes.
The result was writing API/Service classes in which each method is decorated with a simple decorator, shown in the example below.
I work on a TyphoonX – a cloud-native platform that automates application lifecycle management in cloud environments, focused on AWS.