New features in TypeScript that can improve our GraphQL workflow.
The Next Generation of GraphQL and TypeScript
AI Generated Video Summary
The CTO of the Guild discusses the importance of keeping the GraphQL schema and TypeScript code in sync. They introduce GraphQL Cogen, a tool that generates TypeScript types and code to improve developer experience and type safety. The tool also generates ready-to-use React hooks based on Apollo Client and supports multiple languages and integration with various tools. They also discuss the introduction of Type Document Node, which generates a document node from GraphQL operations. They mention the new string manipulation features in TypeScript 4.1 and the ability to parse GraphQL type definitions with a lexer. They express the need for community help to overcome limitations in TypeScript with parsing complex and multiline strings.
1. Introduction to GraphQL and TypeScript Integration
I'm Lhothan, the CTO of the Guild, a group of open source developers focused on GraphQL and TypeScript integration. Keeping the GraphQL schema and TypeScript code in sync is crucial to reduce runtime errors. GraphQL Cogen is a flexible tool that generates TypeScript types and code to improve developer experience and type safety. Let's dive into the most popular plugins, starting with TypeScript, which generates types based on the GraphQL schema and operations. We also generate code, like the TypeScript React Apollo plugin.
Hi, everyone. My name is Lhothan. I am the CTO of a group called the Guild. We're a group of open source developers mainly focused on GraphQL and its ecosystem. I'm here today to talk with you about GraphQL, TypeScript, the integration of the two, and tooling around it. And also a bit about the next generation of TypeScript and GraphQL integration.
So, let's start with the basics. Why do we even need this kind of integration? Each language has its own type system. GraphQL has its own type system where you define your GraphQL schema. It's called SDL and there is TypeScript which has its own type system. So, while writing your GraphQL schema, you need to keep in sync your GraphQL SDL where you define the actual schema and your TypeScript code where you write your actual implementation in resolvers. You can either do it manually and keep it in sync manually or you can use tooling for that. This is what I'm going to discuss in this case.
Keeping the two type systems in sync is very important because, consider a field that you have removed or added to your GraphQL schema, it needs to be reflected in the TypeScript code. So keeping this two in sync is important and will probably reduce most of your runtime errors related to GraphQL and TypeScript. So a few years ago I had issues with GraphQL and TypeScript, so I started with a small open source called GraphQL Cogen. This was four years ago and today we have a huge community and we're not generating only TypeScript it also flows C Sharp, Java much more, and we're generating more than just types, we're generating like an actual code that can help you to improve your developer experience and your type safety. Cogen is a very flexible tool you can write your own plugins and configure the output hook into the generation process and add your own code so it's pretty cool. I'm going to talk a bit about the most popular plugins that we have today that are related to TypeScript.
So the first one is TypeScript, the one that I mentioned before. This plugin just generates TypeScript types out of your GraphQL schema and this seems very similar because the type systems are kind of similar and we aim to generate code in TypeScript that is similar as possible to the GraphQL schema. But GraphQL schema isn't all. We also have queries mutations and everything that involves a selection set which is one of the most powerful features of GraphQL, the ability to choose fields and select exactly what you need and not more than that. So given the fact that you can choose the fields it means that your types are kind of dynamic and based on your selection of fields. So we are also generating types based on your GraphQL operations. So when you use it in your code you'll have fully typed safety. But this is only types. We're also generating more. As I said we're generating code. So here is a small example for another plugin called TypeScript React Apollo.
2. GraphQL Cogen Plugin Features
The plugin generates ready-to-use React hooks based on Apollo Client and your operations. It pre-compiles the GraphQL operations and creates hooks based on the operation name. No manual type specification is needed, as types are burned into the generated hooks. The tool also generates a complete Resolvers signature, utilities for various frameworks, and supports multiple languages and integration with various tools. Give it a try with the live demo on our website.
And this plugin generates ready-to-use React hooks based on Apollo Client and on your operations. So it basically takes your GraphQL operations, pre-compiles those and creates ready-to-use hooks based on your operation name. So when you use it in your code, all you need to do is just to import the hook, provide your variables and then use the data.
And the idea is that we are burning the types into the generated hooks. So you don't need to specify manually types or anything like that. You just get autocomplete and type validation for the variables and the result type. So this is pretty cool. As I said, we're not generating just types and that, we're generating more.
3. Introduction to Type Document Node
Recently, we introduced a new concept and plugin in GraphQL Cogent called Type Document Node. It generates a document node from your GraphQL operation, including the types of the result and variables. If your client library supports typed document node, you can precompile your operations and get type safety and auto-completion. By merging the trees into a single object, TypeScript inference automatically detects types based on the document. Type document node is supported by GraphQL.js, Apollo Client 3, and Oracle. Additional libraries are supported through patches.
So, let's move to the next topic. Recently we introduced a new concept and a plugin in GraphQL Cogent called Type Document Node. So, basically what we're doing here, we're taking your GraphQL operation and we're generating a document node out of it, which is just an object representation of the GraphQL operation, nothing more than that, but what we're adding is the types of the result and the types of the variables burned into that object.
So, if you are using a client library that supports a typed document node, you can just precompile your GraphQL operations into a document node and just use it with your library and you don't need to specify anything else. Like you don't need to add types, you don't need to explicitly set the generics or something like that. You can just use the types as is and you'll get type safety on your variables and on your result, but also you'll get auto-completion based on the selection set that you chose and the fields that you wanted to use. So, this is pretty awesome.
So, if you wanted to know how it works. So, if you are not using typed document node, what we're generating for you today is a separate type for the result type of the operation, a separate type for the variables of that operation and a document node. As I said, it's just a compiled version of your GraphQL query. But these three are separate. Each one of them is defined separately. And when you use it, you need to specify two types and the object of the document itself. This might be an issue because using it like that means that if you'll get the type wrong, you won't catch it during development and you might get it only on runtime where it's too late.
So, we're thinking of how we can avoid this kind of explicitly set types. So, what we did is we took that document node. We took that type that we generated for the result type and the type for the variables. And we just merged all of the trees together into a single object. And the idea is that we're using TypeScript inference to automatically detect the types based on the document that you are passing. So, if you're using a library that supports type document node, all you need to do is just provide your GraphQL operation. You'll just get automatic types and auto-completion based on that operation. So, this is pretty awesome. And here, there are no, there is no place for mistakes, because you don't need to specify types manually or anything else. Just use it and it will be typed.
So, at the moment, type document node is supported directly from GraphQL.js. The type itself is part of the GraphQL.js implementation, so you can just use it from there. And this is gradually, becomes the standard for this kind of integration. Apollo Client 3 and Oracle adopted it and they are now using it and if you're using type document node, all you need to do is just specify it and the types will be inferred automatically. If you're not using those libraries, you can just go to the type document node repository, where you can see a list of additional plugins, additional libraries that we support. You can just use a patch that we provide and it will add support for those libraries until they will support it.
4. TypeScript 4.1 String Manipulation
In TypeScript 4.1, you can manipulate string types at the compiler level. By defining specific structures and using TypeScript inference, you can extract and manipulate substrings, validate structures, and more. This powerful feature allows you to convert GraphQL SDL strings into types during compiler transpilation.
So far with type document node, my next topic for today is the next version of TypeScript, which is TypeScript 4.1 and the new awesome feature that it has. In this version of TypeScript, you are going to be able to use strings and do manipulation for those string at the level of this TypeScript compiler. This sounds a bit vague, I know, but think of taking string types that you define on TypeScript, like you see here, and just do manipulations for it at the level of your TypeScript compiler.
Here, for example, we define a type with the string world, and then we have a new type with greeting, and we just concat the string, and we have a new type with that output. So this is simple, but we can do much more with it. Let's take this one, for example. At this example, I defined a new type called statement, and it just has a string. I also defined a new helper type called extract name, and this type is kind of sophisticated. What it does, it takes the string, any string, and it tries to extract a specific structure out of it. So in this case, what we're doing is looking for a structure that starts with hello and then some name and then the rest of the string. We're using here TypeScript inference to extract the name. So basically, we're saying if this string matches this structure exactly, give me that name. Otherwise, just return never, which is our way to say it didn't work.
So let's say that I'm using this helper with my string here. The extracted type as you can see is my name, which is pretty cool. And this is all done at the level of the compiler. So we can do strings manipulations and extract some kind of like substrings and even detect structure, do validations. So this is pretty cool, but this is a simple string. We can do even more. Let's take this example. In this example, as you can see, I took the same string, but I added some more logic to that helper. So now extract name instead of just looking for a specific structure, it's looking for a more dynamic flow. So we're using inference to extract the greeting and then we expect a space and then the name and then a comma and then a space and then the question. Instead of just returning what we found, we can return a type, a new type script type that contains this. For example in this specific string we're going to get a splitted version of my string. So here we can see I have a name which is my name and greeting which is the actual greeting and the question, and this is like a type script type that we can use. As I said all of that is happening at the compiler level and we can take any string and apply any set of rules. So any string that follows a standard or a specific structure that is well defined can be put here and we can write the rule for extracting whatever inside. This is mind-blowing because as of today what we're doing with TypeScript and GraphQL, we're taking the GraphQL, converting it to AST, and then we take this AST and generate code and this code is later being loaded. But if we can take the GraphQL SDL as a string and just convert it as is into a type in real time during the compiler transpilation of the code.
5. Parsing GraphQL Type Definitions with Lexer
You can take any GraphQL string and create the representing types of it on the fly. I wrote a basic GraphQL lexer to break the string into small pieces and parse it into the document node. By understanding the links between the pieces, we can create types on the fly at the compiler level. I re-implemented the GraphQL JS lexer in TypeScript helpers to parse type definitions and validate the language.
So this is mind-blowing because you can take any GraphQL string and create the representing types of it on the fly. So I took some time and I wrote a very basic GraphQL lexer. So as I said, GraphQL is structured. It's a structured language. We have this type system and we have the exact structure that defines the language and the components that are in it. So it's built as pieces as a small pieces that link to each other. This kind of representation is called lexer.
So what basically happens when you call GraphQL dot parse on a string, GraphQL takes the string, breaks it into a lot of small pieces and then takes those pieces and parses it into the actual document node and do validations on it. So if we take for example this simple query, simple type definition in GraphQL, let's try to break it into pieces. If we just try to understand what are the actual pieces here. So we have a type, the keyword type, and then another word, and then we have a brace here, and then we have another all of those are well defined in the GraphQL, let's call it dictionary because this defines how each sentence in the GraphQL language is structured. So in our case, this is how it will look like we have this how GraphQL will break it to pieces. So thinking of this new feature in TypeScript and GraphQL, we can basically take this kind of string and on the fly, convert it to the representation of the type. So if we can break it to pieces, like each piece on its own and understand the links between them, we can eventually be at a place where we can take a string that contains a GraphQL definition, it could be either a type or an operation, and create types on the fly on the compiler level.
So, I did a small example for how it's really working. What I did is, I took the implementation of the original GraphQL JS lexer and tried to re-implement it in TypeScript helpers. The idea behind it is to be able to define a GraphQL lexer that knows how to parse type definition on runtime and do this validation of the actual language. So what happens here is I took this string that defines a GraphQL type definition and I created a TypeScript helper called parse-graphql-lexer. And basically, what it does, it just looks for specific characters in the string level and it tries to break it to pieces. Each piece just points to the next one. This is how the original GraphQL lexer works. I was just trying to re-implement it with the type system. So as you can see here, I'm just looking for specific tokens that are known for us and are valid in the GraphQL language. And what I'm doing here, I'm trying to extract each one and then I'm moving to the next one. So in this case, we start with the beginning of the string and then here, as you can see in the result, we have a name. This type word is just a name for the lexer part because it's not parsed yet. So what we're doing, we just name it. And then I move into the next one, and as you can see here, we start with the name, name and then brace and this is fairly like a very long type definition because our goal is to be able to parse that into a structure that later can be used. So this is just a proof of concept. And the idea here was to take the very basic string and parse it into pieces that are understand.
6. Parsing GraphQL Strings and Seeking Community Help
We can convert graphical strings into types on the fly, parse graphical schemas and operations dynamically, and create resolver signatures. However, there are limitations in TypeScript with parsing complex and multiline strings. We're actively seeking community help to overcome these limitations and improve the TypeScript and GraphQL ecosystem.
This is just a basic example of how we can take the graphical string and convert it into a type on the fly. But actually, there is more. If we're able to understand the structure from a graphical string, we can parse graphical schemas on the fly. We can understand graphical operations on the fly. And basically, take graphical strings and make it available for use within the TypeScript language.
So if we'll be able to understand the complete structure of a string, we can even have like a resolver signature that is typed on the fly. And actually, much more. So at the moment, this is just a proof of concept as I said. Our goal is to have more than that. But we need the help of the community.
At the moment, there are a few limitations at the level of TypeScript. We can't really parse very complicated strings and we can't really parse and deal with multiline strings that has white spaces. So we're trying to find ways to solve all that and make the TypeScript and GraphQL ecosystem much better. So if you're having ideas and this is something that interests you, feel free to ping us and maybe we can work together. So that's it for me. I hope that this talk was interesting for you. If you're having any questions, feel free to ping me or anyone from the guild on the discord channel.