Putting the U in GraphQL
GraphQL has been on my list of technologies to learn for a few months now, and last week I came across Majid Jabrayilov's post, feeling pretty excited to tackle the subject. The post was very good, but it didn't answer the one question I've had as I've gone through numerous exercises to understand GraphQL, how do I make GraphQL requests without a library?
I've read about how to create a GraphQL query and how to integrate GraphQL on your server a dozen times, but one topic that's highly under-covered is how to make a GraphQL request from the client. In the world of GraphQL it's very common to reach for Apollo, a library that handles turning GraphQL queries into functions, leveraging tooling to turn those functions into type-safe API requests the client can make.
While this is a perfectly reasonable approach, and actually a pretty good developer experience, it still didn't answer the questions I had as an ever-curious engineer, how would I do this on my own?
I broke the problem I saw down into two smaller problems, request-generation and request-making. Generating a request, especially in Swift, it turns out is pretty easy. I really like the approach that SociableWeaver takes, leveraging Swift's function builders to let you build a type-safe directly in Swift. The second problem was a bit fiddlier. I knew that I had to make a POST
request, and I knew the endpoint that was being hit, and through some trial and error (and a friend's help1), I was able to start making GraphQL requests without any external libraries needed.
extension URLSession {
func graphQLRequest(url: URL, query: String) -> URLSession.DataTaskPublisher {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let body =
"""
{ "query": "\(query)" }
"""
let queryData = body.data(using: .utf8)
request.httpBody = queryData
return self.dataTaskPublisher(for: request)
}
// If using SociableWeaver or a similar GraphQL query generator, you can do it in a type-safe manner.
func graphQLRequest(url: URL, query: Weave) -> URLSession.DataTaskPublisher {
return self.executeGraphQLQuery(url: url, query: query.description)
}
}
After looking over the above code a few times I realized that the majority of it was handling the creation of a URLRequest
. That served as a hint to me that we could refactor the code into a custom URLRequest
initializer. This would be less prescriptive about how the URLRequest
is used, since my first code snippet assumes you always want to return a URLSession.DataTaskPublisher
.
extension URLRequest {
init(url: URL, graphQLQuery query: String) {
self.init(url: url)
self.httpMethod = "POST"
self.addValue("application/json", forHTTPHeaderField: "Content-Type")
let body =
"""
{ "query": "\(query)" }
"""
let queryData = body.data(using: .utf8)
self.httpBody = queryData
}
// If we're going all in on SociableWeaver we can make a similar initializer that takes a `Weave` parameter instead of a `String`.
}
Now if you'd like to use URLSession.DataTaskPublisher
you're free to by creating a URLRequest
from our new initializer and using it, but you can also return a URLSession.DataTask
or any other reason mechanism that involves a URLRequest
.
extension URLSession {
func graphQLRequest(url: URL, query: String) -> URLSession.DataTaskPublisher {
let request = URLRequest(url: url, graphQLQuery: query)
return self.dataTaskPublisher(for: request)
}
func graphQLRequest(url: URL, query: Weave) -> URLSession.DataTaskPublisher {
return self.graphQLRequest(url: url, query: query.description)
}
}
That looks a lot cleaner, and our responsibilities seem a lot more well-divided.
Is there room for tools like Apollo? Absolutely! I'm not going to pretend that my dozen lines of code replaces the value that a multimillion dollar company provides. (I'll only make sick jokes about it.) But before importing a library like Apollo, any library really, it's worth asking yourself whether you need a big solution for a small problem. Or maybe question the better question to ask before that is, have you really understood the problem you're trying to solve?
But we still haven't really answered where exactly we should put the U in GraphQL. (I say after the Q since Q is almost always followed by U, but I'm open to feedback on that or the rest of this post.)
- Special thanks to Dave DeLong for his debugging prowess.↩
Joe Fabisevich is an indie developer creating software at Red Panda Club Inc. while writing about design, development, and building a company. Formerly an iOS developer working on societal issues @Twitter. These days I don't tweet, but I do post on Threads.
Like my writing? You can keep up with it in your favorite RSS reader, or get posts emailed in newsletter form. I promise to never spam you or send you anything other than my posts, it's just a way for you to read my writing wherever's most comfortable for you.
If you'd like to know more, wanna talk, or need some advice, feel free to sign up for office hours, I'm very friendly. 🙂