Hostare repo Git privati con Gitea

Negli anni ho accumulato un sacco di sorgenti tra codice Java, Bash e altri linguaggi (addirittura il Moonscript di cui non ricordavo nemmeno l’esistenza), prima di perderli accidentalmente ho deciso…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Creating a TypeScript API that consumes gRPC and GraphQL via generated types

In this article we aim to demonstrate how we managed to get fully end-to-end Typed API, written in TypeScript, by generating types for either end of the application. We will also discuss how we can use these types on any front-end clients, the difficulties we encountered, any quirks we found, and how our experience has been so far. Lastly, we’ll look into the pros and cons of having chosen TypeScript.

Before reading any further, make sure you understand the basics of gRPC, TypeScript, and GraphQL. The following doc links and articles may be helpful:

The first step was deciding upon the structure of our application. Two major factors that influenced our architecture were the ability to easily:

Node server architecture written in TypeScript, communicating via GraphQL, REST, gRPC, and HTTP.

Each of the layers displayed above consists of their own models, transformers, and errors, where two-way model transformation is performed in the parent layer — allowing each layer to be consumer-agnostic. The decision to have a model representation on each layer stemmed from the need to correctly separate concerns, making the code easier to maintain, more testable, and reusable. The cost of doing is verbosity, a small price to pay for what we consider a big win, as if we wanted our client layer to live in a separate repo, to be consumed by another project — this would be extremely simple to do.

Considering the diagram above, the directory structure was laid out in a similarly comprehensive structure:

Our service layer holds the core logic used within our application. This layer (consumed by the resolvers defined in the API layer) communicates with different clients, where it gathers and sends relevant data between the clients and resolvers.

This layer is vital, as it communicates and orchestrates multiple clients, while isolating the details of the clients APIs from the resolvers. This allows the resolvers to be as simple as possible, the clients to focus on their downstream micro-services, and the service layer to stitch it all together — meaning that replacing a gRPC client for an HTTP one does not have an effect on the service layer, and replacing the logic of a service method leaves the resolver untouched.

Once we had fully fledged out the architecture of the system, we needed to guarantee that both ends of the app were typed. This would mean that any changes to our .proto files or any files containing gql tags would result in compile time errors as opposed to runtime errors.

The downside of the tools above is that they rely on a running instance of the server. It’s annoying to have to remember to start the server everytime we want to regenerate the types; so we created a (slightly primitive)bash script to “automate” the process.

Using the simple script above, TypeScript types are generated for us. This means that if we change the following schema.ts:

After defining our query, running tools/generate-gql.sh will generate the following schema.d.ts file for us:

The above types mean that we can even type our resolvers:

We found two great tools that allowed for typed, generated code:

We decided to use GraphQL Code Generator as it generates resolver types, as well as Query and Mutation structures — meaning there’s no way that you can change an existing query/mutation without having to modify its accompanying resolver as can be seen by QueryResolvers.FetchChickenResolver, above.

When you have protobuf API definition files that are shared across different services, it’s hard to share these files and keep your API consumption and implementation up to date. This problem is compounded when your consumers and producers are in separated across multiple git repos. To fix this, we have a proto repo that’s installed as a git submodule (proto directory in directory overview) in the consumers and producers, allowing for the API definition files to be shared across more easily. Here Statically generated JavaScript and TypeScript GRfilesis an example of the structure:

Models are separated from service and request definitions to allow for re-usability, and each service has its own directory so we can target code generation on a per-service basis.

Using this, we created a small bash script that generates JavaScript files and corresponding d.ts files:

We run this script whenever changes are made to our proto files — doing so will generate the following files:

Which, in turn, allows us to communicate to clients as follows:

We found two tools that allowed for typed, generated code:

We evaluated grpc-web first, as it’s built by a well know company and has multiple contributors, even though it is meant for the Web. There was one issue with this:

Due to the large amount of work needed for this, as well as forcing a dependency on each of our producers, we weren’t willing to implement this compatibility layer on each of our producers.

At the time this article was written, if both code and details were provided, when using the Node.js library, one was overwritten by the other — meaning we were unable to get explicit error types without a few hacks.

Another quirk is that the TypeScript typing generated sets error type to any, meaning we need to explicitly typecast the error.

Choosing to use TypeScript for this project over vanilla JavaScript provided us with all the advantages that a structurally typed system can give, and so much more. By using TypeScript we were able to ensure that if any of our providers changed our app would break at compile time. Similarly, if our API endpoints break, our front end clients will break at compile time. Therefore we would heavily suggest enduring the difficulties that can arise when using TypeScript — as the payout is far greater.

Using TypeScript over a more suitable backend language, such as Golang, Java, or Rust came with its share of disadvantages (predominantly in the gRPC communication layer), however it also had a couple of major benefits.

In conclusion, we would suggest using TypeScript over JavaScript when writing similar applications, using Node.js. Weigh up the pros and cons if considering writing a similar app using TypeScript or a more backend familiar language such as Golang — considering the GraphQL benefits and gRPC cons.

Add a comment

Related posts:

Smart Wireless Current Monitoring Sensors

The AC A.K.A Alternating Current which is distributed through a long transmission line of towers available all over the world and then selective voltage, transmission frequency, and authorized power…

O guerreiro.

Diante das dificuldades da vida, o guerreiro nunca desiste. Seja por sua bravura inabalável ou por sua perseverança. Não importa o tamanho do inimigo ou de seu exército. A honra sempre fala mais…

The Tech Incubator

Deep in the hidden mists by the Student Union lies one of QC’s most well-hidden secrets: The QC Tech Incubator. But what is this tech incubator? Who resides there and what is it for? Well, simply…