There has been a phenomenal growth in the adoption of GraphQL and accompanying tooling. As with any new technology however, we are still learning about security, best practice approaches & how to do GraphQL right. There is still debate around when it is appropriate to use GraphQL as opposed to REST or gRPC for example.
This post tells my story about why I created WunderGraph, how I believe that WunderGraph takes the best of GraphQL and REST and puts them together, in a particularly unique way, to help developers to become more productive.
Since the beginning of my software engineering career, I have been building apps for native devices and the web. To power these apps I've both written and consumed various API types. Among those include SOAP, REST, gRPC, OData as well as GraphQL.
I've been following discussions on the pros and cons of the different API styles for several years now, and have become particularly interested in comparisons between GraphQL and REST as they seem to be the leading API technologies to power a modern web.
Depending on the use case, GraphQL can give you a lot of benefits when you want to query data from multiple services. You write a query and get data from 5 different services in one go - that's powerful.
Once you're sold on GraphQL, you might realize (as did I), that using it in production also comes with a lot of significant challenges. As I became more experience I realized how naive I initially was as a GraphQL user. My services might have leaked more data than I expected without me knowing about the problem.
To name just a few of my thought processes:
- How should I implement Authentication and Authorization?
- How do I cache Queries?
- How do I implement Rate Limiting to protect my upstream?
- How do I make sure my users cannot traverse my schema in unexpected ways?
- How do I validate user inputs?
- How do I implement persisted queries?
- the list goes on...
Early adopters like Marc-Andre Giroux (through his experiences at GitHub) are doing some excellent work to educate developers on how to ready GraphQL APIs for production.
Despite the growing amount of advice in the community and evolving best practices, I felt that we are still missing tooling to provide concrete answers for some of these problems. I felt that I could give more to the community than I already have, which would make using GraphQL easier, significantly more secure, and reduce the amount of decision making necessary - ultimately making developers more productive.
If you're interested in some of my prior work on GraphQL in open source have a look at my graphql-go-tools. The library aims to offer low-level building blocks to build powerful GraphQL applications using the Go programming language.
Developing graphql-go-tools helped me a lot to get a better understanding of GraphQL and what problems can be solved with it. The library is used in production by several companies. Conversations with users helped me better understand what's missing in the community.
Most, if not all GraphQL implementations, allow the client to request exactly the data they want. This is one of the main selling points of GraphQL as it's also stated on the landing page of graphql.org.
This paradigm makes a lot of sense for a company like GitHub where you have no control over your clients. But what about the 99% of use-cases who run a GraphQL server for themselves and build apps on top of that?
The vast majority of developers already know all the operations a client will need to run at the moment they want to compile/transpile and ship the application.
Developers started, for performance and security reasons, to add the concept of persisted queries to their applications. For those of you unfamiliar with persisted queries: You write all queries in the application/frontend code, extract them in a compile-time step and register them with a server. Persisted query implementations usually have a hashmap containing all queries that were registered during the compile-time step. Then during runtime, the client doesn't send the full query but rather just an ID (e.g. the hash of the operation) alongside the variables. This saves bandwidth and makes the application more secure as the registered queries are like an operation whitelist.
At first, I wanted to build upon this concept and improve it. But after some thought and research, I realized that it would make sense if I don't have to "interpret" the GraphQL query for every request, but rather "compile" it. I'll prepare a separate blog post with a bit more technical detail, but to summarise: much like a database prepared statement, it becomes possible to register a query with the GraphQL server - it gets lexed, parsed, analyzed, validated, etc. all at compile-time.
This was the initial idea for WunderGraph: Compile GraphQL queries on the server
The obvious advantage is performance and reduced bandwidth alongside with a significantly simpler client. The lesser obvious advantage is security. To the outside world, the usage of GraphQL is invisible because it never gets exposed to anybody. Clients can call into pre-registered endpoints that call a precompiled execution tree. The result is the flexibility of GraphQL combined with the performance and security of RPC.
Companies like Facebook and Medium are using similar concepts already. They can build the required tooling to solve these problems. With WunderGraph I'd like to make this available to the masses.
But that's not all there is to the story.
If you write your query on the server, disabling the client from the ability to send arbitrary requests, the developer responsible for the server is pretty safe to write whatever logic they want in the query. This might sound insignificant at first.
What logic can we write in the Query that makes this so powerful?
- we can define Authentication & Authorization rules
- we can define caching behavior for both the server & the client
By pre-registering our queries on the server-side, we can then expose them to clients with a typesafe interface. Much like gRPC, but without any of the complexity.
If you can define these rules in the Query you don't have to do it in the Schema. This is important because it means you can make your backends extremely dumb and they become a lot simpler - with less boilerplate. Besides, your schema and GraphQL/REST implementation doesn't have to take care of Authentication, Authorization or Caching.
Previously, we needed to have JWT & Caching middlewares baked into our REST & GraphQL servers to implement these features. Now with WunderGraph, it becomes possible to keep the underlying services considerably simpler. WunderGraph takes already care of these capabilities for us.
If you previously implemented auth using @directives in your schema you might have recognized that you are forced to redeploy the service whenever you want to change the configuration. With WunderGraph, this is no longer necessary, as it becomes as simple as a configuration change.
Let's take a look at the following example query:
We can see that there must be some magic happening in the GraphQL server. The "viewer" resolver takes some information from the request, e.g. an Authorization header, parses the jwt and extracts the user ID to identify the viewer.
For the field "personalSecret" there needs to be a filter that enforces that a user can only access it if they have the same id in their jwt as the user object.
Let's consider you'd like to implement an admin account that should be allowed to read the "personalSecret" of other users for some reason. To make this feature available you'd have to add a new root field, e.g. "adminViewer" or modify the "personalSecret" resolver to be allowed for admins.
Next let's take look at how you would achieve the same thing with WunderGraph:
@fromClaim will override any value from the variables with the "sub" claim from the user's access token. We removed the field "personalSecret" from friends because we're in control of the server and the client is disallowed to from requesting this field for themselves.
Now let's look at how the admin query might look:
@auth allows the argument
requiredScopes which means you can only invoke this Operation if you have the claim
admin in your access token. Additionally, because we are in charge of defining the operations, we added the
personalSecret field for friends again. We're safe to do so because the operation can only be used by admins.
You can see from this fictitious example, that using GraphQL as a server-side only framework decouples Auth from the backend implementation. The backend code is a lot simpler and thus significantly easier to maintain. We're able to achieve both use cases, the user query and the admin query with the same backend implementation. Less code equals fewer bugs and reduced maintenance.
Clearly not! This paradigm only works in situations where you have full control of your clients. In an organization where you have many services stitched together to form a GraphQL API that is used by teams across the company, you can have shared access to WunderGraph and get all the benefits outlined above.
Compare that to an org like GitHub that exposes a GraphQL API to the public and has no control over the clients. In this scenario, you would have to build a lot of security measures into your GraphQL server and make it rock solid before exposing it to the public to avoid leaking data unintentionally.
Feel free to register with WunderGraph and start experimenting. We would love to see what apps you are building with the WunderGraph service. If you see other use cases or want to share your opinion about this concept meet us on discord, GitHub and use the comments on Medium.