We talk about elm-graphql, how to organize your SelectionSets, and other best practices.
November 2, 2020

How can elm graphql decoding fail?

Backend Frameworks for Full-Stack GraphQL Type Safety


Hello, Jeroen.
Hello, Dillon.
How are you doing today?
Pretty good.
How about you?
I'm good.
And well, we've made it 15 episodes without talking about Elm GraphQL.
That's surprising.
Yeah, we've had a lot on our minds.
So we've gotten to talk about a lot of good stuff.
But there's a lot to say about Elm GraphQL as well.
And we will do that then.
As some of the people in the audience may not know, Dillon is the author of Elm GraphQL.
That's pretty much how I learned about you, watching your talks about it.
Maybe let's get into it and can you tell us what Elm GraphQL is?
So what GraphQL is, is it's a schema for your API.
So it's just describing all of the endpoints that your server can return.
Now, I like to break it down into two pieces.
The name GraphQL has two parts.
It's a graph and it's a query language.
So the query language is the part that it's a schema that describes what you can query for
and the types of those values that it will return.
The graph part is a whole nother piece.
Essentially, the graph part is that rather than making several follow up requests,
when you get one bit of data and you say, okay, now that I know the ID of all of the users that I'm connected to,
I want to find their first names and get their avatar,
so I need to make a follow up request to get that information.
Or you sort of throw in a bunch of additional information into that rest endpoint
because you know that you very frequently get that information together.
It's a way of sort of traversing a graph to get all of the data that you need in a single request.
So that's one of the goals of GraphQL is to solve the underfetching and the overfetching problem.
You can fetch exactly the data you need and you can traverse these relationships.
And it's quite a nice way to sort of build up data and evolve an API where you don't need to know exactly
how you're going to get the data at first.
You can sort of start exploring data and the types are all known.
So one of the really interesting things about GraphQL is that it has a complete specification of all of the type information built into it.
So any GraphQL server, you can ask it what are all of the data types and all the things I can query for,
and it will respond with that information.
Is that this schema that it gives you in one giant response or?
Yes, it's actually like these magical little queries that you can do in GraphQL syntax.
So like you can in any context, you can ask for the type of something and it will tell you, you know, this is a person object.
This is an ID, you know, you can query for those types.
So those little introspection, those introspection requests are built into the GraphQL query language and you can,
a server will respond with that information.
So that's what Elm GraphQL does.
That's where Elm GraphQL comes in is it uses that introspection information to ask the server what data can I get?
And then it generates an entire API for your server.
So like the way I think of.
And in this case, we talk about the library API or module API.
Yes, good distinction.
So it takes your server's API and it builds an Elm API at DSL domain specific language for interacting with your API.
So like I think of it kind of like, do you ever see these libraries that will be, you know,
like a Ruby library for interacting with the GitHub API or a node library for consuming this API?
And it's kind of custom tailored to, you know, have these functions for interacting with it in a more high level way.
That's essentially what Elm GraphQL does is you point it at your GraphQL API and it gives you one of those libraries.
That's basically what it does for you.
And so it does that by just generating a bunch of functions that define something called a selection set.
So that's sort of at the core of Elm GraphQL is this concept of a selection set.
Yeah, just the general look and feel of the API that is generated kind of looks like Elm HTTP with decoding, kind of.
We will get into details, but that is just what the people or the audience should have in mind, I guess.
Right. And then it looks a lot like building up a decoder, which you can do in a pipeline style or like the map and functions, map three, map five functions.
So building up an Elm GraphQL selection set, which is this GraphQL concept of the data you're querying for.
But in Elm GraphQL, it allows you to query for that data and then it knows how to decode that into Elm data.
So you can think of it like a JSON decoder, except it knows how to query the server for that data.
And it knows it's going to get the data because it knows that the underlying types are a string and a float.
So it looks a lot like building up a JSON decoder and then sending it with HTTP.
All right. So with the JSON decoder, you always say, I want this field and I assume that it's a string.
But what you're saying here is you say, I want this field and in GraphQL will well, Elm GraphQL will tell you this is a type that is expected.
You don't have to specify it yourself.
Exactly, exactly.
So maybe to compare, if we were getting an object from a REST endpoint that had a person with a first name and a last name,
then you would maybe do decode.mapTo and then decode that in.
You'd give a constructor for a person type alias record, which has a string first name, string last name.
Then you would do you'd have decode.fieldFirst, decode.string, and then you'd have decode.fieldLast, decode.string.
So you're actually when you're doing that, you're stating several assumptions.
You're stating the assumption that it's going to exist at this level under something called field, under a field called first and a field called last.
And the type of those values are going to be string and they're not going to be null.
And so you're stating several assumptions there.
And if any of those assumptions are incorrect, then you get a decoder error, which is awesome.
We've discussed that on our JSON decoders episode that that's an awesome feature of Elm that it can fail fast
instead of getting way into the far reaches of your application and giving you a mysterious error.
Well, it's better than to have a decoding error than to have a runtime error.
Exactly. Where you sort of core some type and you add it to something and you're like, how did this turn into null or why is this empty string?
That's great. But if we have a schema that describes our entire API, we can do better.
And that's that's the goal of Elm GraphQL is what if we could know that those assumptions were correct?
Not when we run the decoder, but when we compile, it will actually tell us.
And so so what would that query to get a person's first and last name look like in Elm GraphQL?
Well, in a nutshell, the way you build up that decoder, the selection set for that person would look like instead of decode dot map to you could start with selection set dot map to.
And then you give the person constructor and then you have person dot first person dot last.
Now, what is person? Person is generated code from Elm GraphQL and it's going to have something called API dot object dot person.
That module is going to contain a top level value called first, which is a selection set of type string.
Which will query the first field in a person.
Exactly. It contains all of those things that we were making assumptions about when we were writing our JSON decoder.
It contains that information. It knows it knows how to it knows where it's going to find that data that comes back from the server when it comes back.
And it knows how to it knows what the underlying data type is going to be.
And we also use it to send off the query. But those assumptions are no longer assumptions.
Those are that knowledge that is contained in the generated code.
Yeah, I think that as Elm developers, we really want to move things to to be compiler errors rather than runtime errors or decoder errors or just false assumptions that you have to handle it some way.
And I feel like Elm GraphQL was one to me, at least one of the few that pioneered type safe APIs that are generated to be that way.
Yeah, it's been cool to see like there definitely been some innovations that use code generation.
And I've seen some like call outs to Elm GraphQL.
And I think that the domain of GraphQL was ripe for some sort of nice integration with Elm because it's just you've got this type information.
It's there. It's like, you know, if you if you pull up the interactive like GraphQL query window,
you get this nice auto completion where it knows what values are there and you can look at the documentation in line and that information is there.
And yet it's sort of separated in this different world that we can't access.
And that's sort of the premise of the talk I gave types without borders is, you know,
you've got this line dividing this beautiful type of information that you'd like to have and why not bring that into the Elm world so we can have access to that information.
But yeah, as you say, that's GraphQL is one application of that idea.
But that idea itself can be used in so many ways.
Yeah, we mentioned that first like person dot first was a selection set function, a function that returns a selection set, which goes to which fetches a first field in a person and is of type string.
Yeah. And it just to clarify, it's a value in that particular case.
I mean, it depends on whether it has arguments or anything like that.
But assuming it has no arguments in the simplest case, it would be a top level value just to use the precise terminology there.
But you cannot use that person the first somewhere else.
You can only use it when you create your selecting a person.
That's right. Exactly. So that's that's the concept of a scope, which I actually just just in time for this episode, I made a documentation change.
So in the docs, there used to be a type variable, which some people may have seen called type block.
And I actually wrote that I wrote that name the very day that I discovered phantom types because I didn't know what they were.
And I was just amazed that like, oh, I can lock the type like this.
So I called it type block and I never really thought of a better name until recently.
And so finally I changed. But so a selection set has two type variables.
The first type variable is what it decodes to, just like a JSON decoder.
It's a decoder of some type variable. So a decoder of string, a decoder of person in Elm GraphQL.
It's the exact same thing. A selection set has a first type variable of the type it decodes to.
The second type variable is the scope of the selection set.
And as you say, so this is this is where sort of the graph part of GraphQL comes in.
If you have at the top level, let's say you can make some requests to get the current version of the app.
So you you say current version and it gives you a hash or a version number or something.
So that is a top level query. So in raw GraphQL syntax, what that would look like is usually people will explicitly write out the word query and then curly braces.
It looks a lot like JSON, and that's the intentional design of GraphQL is it looks like JSON, except it's not the key value pairs.
It's just the key. It just looks like the keys. And that's like the data you're requesting from the server.
So you would say query curly braces and then build version or whatever that field is called.
And that would be saying I'm requesting this top level value from the server.
And that would have a type of string, for example. And so that's like the simplest top level query.
But now if you're saying if you're getting a person, now you need to specify, well, what information do you want to know about that person?
And that is where you get this sort of graph part of GraphQL, where you're traversing these relationships and you're fetching data within other bits of data.
So you're saying I want, you know, let's say you want like the current user, you want their first and last name to show in the top right corner.
Then you would say so instead of query curly braces build version, you'd say query curly braces current user.
Now that's getting the current user, but the current user is not a simple string.
Yeah, it's not a primitive.
Yes, right. It is an object. And so within that object, now you need a nested selection set to tell it what fields you want from that object.
And that's where you you open up some new curly braces and then you say, well, I want the first and last.
So that would look like query open curlies, current user, open curlies, first new line, last and then close all the curlies.
No commas between first and last?
Commas are optional.
OK, good to know. Well, not good to know because I use Elm GraphQL at work.
So right. Exactly. And it abstracts away those details.
And Elm GraphQL actually abstracts away a few other details about the sort of low level GraphQL syntax.
For example, in in GraphQL, if you're so you can pass arguments to an object like if you if you want
a person with some ID.
Exactly. If you want to do find person, you can pass it an argument.
Now, well, what if you're saying find person with ID one and find person with ID two and you want both of those.
Right. Well, the way that GraphQL works under the hood that I say under the hood, because as an Elm GraphQL user,
this is an implementation detail that you don't deal with.
But that JSON data, you're going to get a JSON response. And when you say find user.
So I was saying that it's intentionally designed to look like JSON, but just the keys, not the values.
Well, the responses come back in the same shape.
So if you request first name, that JSON object is going to come back in the key first name.
Sorry, if you say find person, that JSON object is going to come back under the key find person.
But you're doing find person twice. And so there's going to be a collision and you get a GraphQL error.
Is that a problem that people write GraphQL manually have?
That will certainly be a runtime error that the server will fail.
You may have like a linter that helps you with that or that sort of tool.
But with Elm GraphQL, you don't even have to think about it. So it's one thing if we can make those errors impossible,
like Elm GraphQL generates an API that you're going to use things the right way.
That's one way to do it. Another way to prevent errors is by abstracting away those details and taking care of them for the user.
In this case, that's what Elm GraphQL does.
So the way that you solve this problem of a collision where you're requesting two fields with the same name is you need to give it a field alias.
And so you would just use the syntax where you say, you know, person one colon find person ID one and then person two colon find person ID two.
Now it's going to come back under the JSON key person one, which was the alias you gave it or end person two.
But Elm GraphQL takes care of that for you so you don't have to think about it.
And that's why in the FAQ in the Elm GraphQL repo, I talk about these mysterious ID numbers that show up in all of the fields.
So you may notice those. That's what that's doing.
And I wrote an article talking about like the design of that and some of the I talked about how Elm sort of pushes you towards simpler design
because it's harder to have sort of imperative state in your code.
So we'll share a link to those and people who are interested to learn more about that can take a look.
So one problem I often encounter when I play with Elm GraphQL is that I'm looking for data on person, for instance.
So what I do is I do current user.
And we totally got sidetracked, didn't we?
We forgot to mention that scope type variable prevents you from trying to request something at the top level.
That was the original point.
So maybe let's wrap that up real quick and then get to your point there.
Which is related anyway.
Okay, good.
So the scope type variable, there are two type variables.
There's what it decodes to.
That's the first type variable and the second type variable, which used to be called typelock.
And it's now called scope, which I think better reflects its purpose.
So the scope, as you were talking about, if you request this top level thing of the build version, which is just a simple built in string type at the top level.
It's not a nested object query.
Now, the type of that is going to be it's a root query.
But then once you so that would be for the build version, the scope type variable would be the root query.
But for a person selection set, the scope is going to be person.
And so the way to think about that is if you try to get first name at the top level scope, it doesn't make any sense because you're getting it from that person object.
So you can get current user, open curlies, first name.
You can do find user ID one, open curlies, first name.
So in the scope of a user object within the curlies in the find user or the current user, you are in the scope of a person selection set.
Of a person up.
And so you have access to fields in that scope.
So that's what that second type variable in a selection set represents is that scope.
Yeah. And what it helps you prevent is you asking for fields that do not exist on something else.
Exactly. Without that, Elm GraphQL would not really be able to achieve its promise of preventing invalid GraphQL queries or at least giving you a compiler error if you try to do that.
Yeah. It would give you a decoder error.
Exactly. Yeah. That's a key ingredient.
OK, so did that cover your point or was there something else you were going to bring up?
I was going to bring up something else, but that might actually just be me.
So you have that you want to have the first name of a user of a person.
So, for instance, you do current user.
Current user is a selection set on person where the scope is person.
And then you will pass in person dot first and person dot last person dot age, whatever.
But the scope is a object that the API that the Elm GraphQL CLI tool will generate for you.
But it is not related to the module that defines it.
I always find it a bit tricky to connect both in my mind.
So I have a scope and I have the selection sets on that scope.
Yeah, they're in very different places.
Right. This is a good question.
And this is sort of this is one of those like little workflow things that I like to share with people when I, you know,
when I teach my Elm GraphQL workshop, these little workflow tips can make a big difference, I think.
And so this is like one of those little details that I like to share in my workshops.
This is worth money, people.
This is a for money workshop.
Is there any other kind? I guess so.
And so, OK, you had you have these two different values.
So you've got a module, as you were saying, there is this API object that person module.
And this is where you get the first name field and the last name field.
These are defining these selection sets that you can get in that scope.
Now, the technical reason why that magical type variable scope is using a value that's not in the API dot object dot person module is because it would lead to circular dependencies.
So it's not possible to do that.
However, I'm happy with the way that you work with it, but I think there's a little trick that helps to make it nice to work with.
So the trick is if you have a so when you say find find person, you've got, you know, so this is at the top level.
So it would be query dot find person, you know, API dot query dot find person.
And then you have find person takes a selection set of the nested selection set.
So what fields would you like to select on that person?
Right. So first, you give it the required arguments.
So you'd give it the required arguments of the ID for the user you're selecting, ID one.
And then the second argument you would give that function for find person would be the nested selection set.
And so the type of that is going to be selection set decodes to anything you can decode to whatever you want it to.
And then the scope must be API dot object dot person.
API dot object dot person. And the scope is API dot person.
The scope is API dot object dot person.
To also. Right. Yes. OK. So here's here's how that works.
It's not API dot object dot person dot person because it doesn't exist in the API dot object dot person module.
It exists in a module called API dot object, which contains all of those magical scope type variables.
Right. So if you want to find what fields you can use, yes, what functions you can use, you just go to that module.
Exactly. Same name as the scope. Exactly.
The name of the scope is going to look exact. It's going to be the exact name of the module, even though it's in a different module.
It's in the API dot object module, but it's called API dot object dot scope, API dot object dot person.
Now, if you do API dot object dot person, that module contains all of the fields you can use in that context.
So the scope API dot object dot person means that you can do API dot object dot person dot any field name.
So it's a bit difficult to explain that without showing code. But but hopefully that that wasn't too confusing.
It makes sense to me at least. But I think that in our case, my workplace, we're confusing ourselves by doing a lot of aliasing for the module names,
which might not be always a good idea, especially if you rename it like it's not always person, it's user, for instance.
API dot object dot person alias as user. Yeah. And then you're you're confusing yourself.
Interesting. I mean, at that point, I would be wondering, I mean, maybe it's hard to make a breaking change to the schema,
but I would be considering changing my GraphQL schema at that point.
Well, yeah. Or maybe it's just some discipline we need to get around aliasing our modules.
Right. One of the techniques that I think is really important is these selection sets.
In one sense, it's it's pretty high level. You know, it's it's not as low level as just making a GraphQL query.
But in another sense, it's very low level, much in the same way that JSON decoding is low level.
And as such, it belongs in a module most of the time.
You would move your selection sets in a different module?
Most of the time, yes. Like if it's a person now, if it's like a toy example and you're just fetching a person and showing their first and last name.
OK, but in a real non toy code base, what's going to happen is there's going to be a lot of intricate logic on that.
And you're going to have you know, you're going to have user settings and you're going to have all these details that you need to manage.
And I think it's a really good practice to start finding nice ways to organize these into modules.
So modules next to where you would use them or modules that you would share around the rest of your code base?
Modules that you would share. And I think it's really nice to encapsulate.
So like let's take a person so you can encapsulate that into, you know, let's just call it a person top level module called person.
Well, that's going to be confusing for aliasing.
Well, but you've encapsulated all of the selection sets that deal with API dot object dot person to within that module.
Unless you also need the person, the API dot object dot person module for other things.
If you move everything related to selection sets into that module, then I guess that problem is solved.
I think so. And I mean, the seams might not always be perfectly clear, but I think this is the general direction that's very nice.
You have it. So, you know, just like you would have I don't know, maybe you've got like an article and then you have, you know, the body of the article and the title and the author.
So if the author of the article is a person, then you need to make a selected and a nested selection set for that article to say, OK, I want to get the author of the article.
Well, what fields do you want? And then you need a nested selection set to say, I want the first name, last name.
Well, you can delegate that to the person module that you defined to encapsulate that Elm GraphQL selection set.
So you can say, you know, you have module person. It exposes selection. You know, you have a person dot selection, which is of type selection set.
And now that's going to fit in. So when you when you have, you know, an article module that encapsulates your selection sets for fetching an article, then you're going to say, well, get the author.
And then you're going to pass in the nested selection set, which is going to be person dot selection. That's something you defined in your own module that encapsulates the selection set.
OK, so you would have a personal selection that takes a lot of arguments.
A person dot selection wouldn't take any arguments because you're passing it to it's a it's a selection set that's describing here's what I want from a person. And then when you say article, I want the author.
That's described. So, see, that's the cool thing is you've got this module that sort of encapsulates how do I get a person?
And then you've got this this article part is describing where to get the person from. Right. So that's like the graph part of GraphQL.
And so you can just sort of say, like, OK, well, here's the query for a person that's in the person module you defined person that selection.
And then, well, what person do you want? Where do you want the person from? Well, that's the relationship. It's the person who wrote this article.
Now, there is that does make it sound a little simpler than than it turns out to be in reality, because oftentimes you have if you're getting the current user, then you want to get you know, you want to get their avatar and maybe some additional details that for for the author of a post.
You know, maybe for for the author of an article, you want to fetch the number of articles that they've posted or other articles that they've posted.
So you do have to get creative with the way you define those those things.
Because you don't want to fetch or underfetch most of the time. Right. Yes.
You want to avoid over and under fetching and you want to find the right seams for for sort of having the responsibility of the person where, you know, that that sort of describes the core details like, you know, maybe the logic for how you display an avatar.
For example, you could encapsulate that in the person module.
So that's view logic. And you could use an opaque type to have a person and you know how to render a person batch.
And you can render that at the bottom of an article. You can render that, you know, in the nav bar for the current user or maybe maybe like an author and a user are two different types of things.
You know, so you need to get creative with these things and you may need to, you know, you can you can expose different types of selection sets to you can expose within a person module.
You could expose the bare bones selection set and the selection set that includes some additional information for the for the nav bar display.
And so it's not like a one size fits all solution. This is just data modeling techniques and module design.
But my point is that I think that too often people think of, you know, an Elm GraphQL selection set as just fetching data and as like a high level unit.
But it's really not. It's something that should be encapsulated and you should build your own types that are appropriate types for your domain.
You should use all the same good data modeling techniques and everything that you would use with a JSON decoder and encapsulate those details.
Yeah, you don't have to keep the data that you get from the GraphQL endpoint as it is.
You can transform it using selection set dot map and stuff like that into the main model that you want and that you will use for reviews and for your update functions.
Exactly. That's the beauty of, you know, of this sort of approach in Elm of like this decoder style.
And, you know, yeah, we talked about this in our like JSON decoding episode that you can you can make changes at a local level.
You can, you know, within a certain JSON decoder subsection, you can map.
Whereas with JavaScript, what often happens is you just say like, all right, give me all this data.
And then you get back a giant, deeply nested JSON object.
And then you pass that into some function that transform that does a bunch of transformations on that whole object and becomes really unwieldy.
It's so much easier when you can just do a precision surgery in that one spot that you want to change with a map.
And that works the exact same way in Elm GraphQL selection sets as it does in a JSON decoder.
You just call map and you can you can turn a string into all uppercase or you can turn an int into its string version or, you know, whatever.
You can turn some some Elm data type into another data type or some error type into another error type or whatever, whatever logic you have.
Do you have other tips in your mind? Otherwise, I can ask some hard to answer questions.
Hard to answer questions are good. Other tips? I do have some other tips.
Let's go through them then. OK, cool. So, well, so I mentioned opaque types and just general module design and encapsulation.
You know, I think that's something I really would love to get out there as a more common practice.
Something that pairs really nicely with that is using this custom scalar codecs feature in Elm GraphQL.
I'm this is like probably my favorite feature of Elm GraphQL and I feel like it's not used as much as it could be.
Are custom scalar codecs specific to Elm GraphQL or to GraphQL?
Because I've seen them, but I don't know whether they're originally from.
Good question. Custom scalars are a GraphQL concept. Custom scalar codecs are an Elm GraphQL concept.
The GraphQL concept of a custom scalar is you have these built in scalars.
You have strings and ints and there's an ID built in custom type.
So a scalar is pretty much a primitive or a object, but a primitive object like?
Yeah, yes. A known declared object. Right.
So here's how I think of it. I think of a custom scalar as a contract.
Here's why that's really cool, because it's a contract that you can guarantee on the server side.
And it's a contract that you can leverage that guarantee on the client side.
So like, what does that mean? Well, if you have an ISO 8601, your favorite number.
ISO 8601. Years of practice.
I wake up and look at myself in the mirror every morning and say ISO 8601. I recommend it.
If you ever mess that one up, I will laugh at you so much.
But yeah, that's a perfect example because it's a contract. It is a specification, right?
It is a primitive, but it has some form or some contracts that it tries to uphold.
Exactly. Exactly. So that's right. There is an implicit contract in that concept of an ISO 8601 custom scalar type.
And so, well, how do you trust that contract?
Well, you trust it because on the server side, any time you're returning a value of type ISO 8601,
you define the logic for fetching those types of values and you take care of the serialization in that format.
And so you can then deserialize it with that assumption. And so it's a contract that you're making.
Now, this brings up an interesting part of working with Elm GraphQL, which is that what guarantees does Elm GraphQL make?
Does Elm GraphQL guarantee that your selection set will never have a decoder error?
And the answer is no. It essentially guarantees that you will not have a decoder error under specific conditions.
And serializing something as ISO 8601. Sorry, but Elm GraphQL really can't help you there.
That's just a contract that you're... But what it can help you with is it can say, OK, well,
any time there's a value that's of this type, I'm going to use this logic to deserialize it.
And the server can say, OK, well, any time I'm sending a value of this type, I'm going to use this logic to serialize it.
So it's... So Elm GraphQL helps you by providing this mechanism for leveraging that contract at the right point.
But you're on your own for making sure that you've wired up the contract correctly between the back end and the front end.
Yeah, if the server messes up, then you have a decoder error or some kind of error. What kind of error would you have then?
You would have... That is a good question. Let me look at the docs.
I believe I remember being very explicit that I... As much as possible in the Elm GraphQL error messages,
I tried to proactively defend against bug reports by saying, you know, the person who wrote this Elm GraphQL...
Well, I don't say it that way, but I say like, you know, somebody defined a custom codec that failed.
So it's going to give you an error like that.
Yeah, because you need to write the codec well also, right? As an implementer of the front end for Elm GraphQL.
Exactly right. You need to handle that correctly. But then it gives you a pinch point where in a single place, that codec doesn't even...
It will apply that decoder to any value of that type. So now when you go to select a date, if you parse that string,
which is ISO 8601 format into a time.posix, then when you have a selection set, which is a created at timestamp,
which has that type, when you get it, the type for you as an Elm GraphQL user building up a selection set is going to be time.posix.
There are a number of places that you can make use of that. You can make use of that for, you know, I think it's a great idea for ID types.
I recommend that people use not just the built in, you know, GraphQL custom scaler.
GraphQL has a built in scaler type called ID, which, you know, that's nice.
I mean, if it's like a globally unique ID, then maybe that's fine. But really, we can do better than that.
And I recommend having unique ID types for each type of ID, a user ID type, a product ID type, because, you know, if you're trying to pass in an ID type,
you can describe in your Elm code base, this needs a user ID. This function requires a user ID.
And now it's guaranteed to be in sync. You don't need to remember to turn something into a user ID. The codec is going to do that for you.
Yeah. So in this case, the codec would just wrap it in an ID. You wouldn't have to do anything more than it wouldn't fail ever.
It wouldn't fail as long as you get the underlying type right.
From the server.
So this is a corner of the GraphQL specification that is not perfect. Overall, the GraphQL specification is very nice.
But there's currently no type information about custom scalers. So if you send a custom scaler for a user ID, there's nothing telling you, oh, and by the way,
the underlying type is a string or the underlying type is an int.
Okay, I thought that would be the case.
You would think so. And there are some active specification amendments that are in progress to help with that, although it seems like they're going to be optional, not required.
And by the way, the thing that makes it useful to have the schema describing the types in a GraphQL API is that for one thing, you can describe that data and introspect it, which is great.
But how does that guarantee anything?
Right. And the answer is, well, it guarantees that you're going to. So the GraphQL specification doesn't guarantee that the server will never send an incorrect type.
It guarantees that if it does try to send an incorrect type, it will give an error at the GraphQL level.
So the GraphQL server framework itself is responsible for not letting anything escape outside of it.
Okay, so you would get a 500 or something. I guess that's not the equivalent of a 500.
Equivalent of a 500, it might even be a 200 and then contain an errors key in the payload that it returns, which Elm GraphQL would treat as an error.
I can't even remember. It's been too long since I've been in there. But whatever the case, it's going to be treated as an error.
So now you can use a more like type safe end to end solution if you're using something like Juniper with REST, which is a type safe GraphQL framework.
Then you can you can make type guarantees from the database all the way through to returning something from the server or using something like Hasura or PostGraph file.
It's actually looking at your Postgres database schema and then inferring the type information from that.
So there are ways to keep them in lockstep, but all the GraphQL specification says is if they're mismatched, then I will not let it get through as incorrect data.
Instead, I will mark it as an error and send an error response.
So you do need to drop in in the custom scalar codecs. You do need to drop in and decode it into the low level type.
And then you can map it into whatever types you want. So you actually write a raw JSON decoder, as scary as that may sound.
Well, it's not that scary once you've once you've listened to the JSON episode that we've made.
Right. Yes, exactly.
The one thought that I had when I first heard of Elm GraphQL is like you generate all this API from the GraphQL schema definition file or API.
And that works as long as you as the API does not change.
So if my server and my front end are separated, how do I keep them in sync?
Right. OK, yes. So the way that I recommend people do this is so if you have a mono repo, which is our case, which is practical for this purpose.
OK, good. Which I'm a fan of mono repos. Anytime there's a server change that changes your GraphQL schema,
you can take that schema, generate your Elm GraphQL code, and you can actually verify that you have not broken any of the code you're calling.
Because if you so here's the step here. Here are the steps you take.
You are doing a build on your CI. You have a server change.
You generate the GraphQL schema from that. There are different ways to do that with different tools.
But now you have the latest schema that you're that you're saying I would like to deploy.
Right. So then you take that schema, you feed that into the Elm GraphQL CLI tool, which is going to generate the latest code.
So now you've updated the generated code or generated the latest Elm GraphQL code.
And now you run Elm make on your code. If there was a breaking change and you depended on it,
if you removed person dot first name and last name in favor of full name and your code was calling it,
that code will Elm make will give an error. So your new server schema change does not go live.
That is very nice when you got a server team and a front end team and the third team does not really know how you use it.
How you use the GraphQL API in the front end. They kind of have a safety net that says, hey,
I'm not going to break the front end because there there is this build step.
Yes, exactly. And on the flip side, it actually gives you more confidence about removing things that you want to deprecate.
Now, a lot of people in the GraphQL community talk about not versioning their APIs and not doing breaking changes.
I personally don't see a problem with doing a breaking change with you know, you can deprecate fields in GraphQL.
You can have like a sort of safety window and you can do it using these techniques in your build system so that you are guaranteeing that things are in lockstep.
And so you can do it in a safe way. I think it's it's a reasonable thing to do.
And it gives you more confidence because you know that you've stopped depending on things when when that build goes live.
That said, that doesn't guarantee that somebody is not on a version from last week.
Yeah, like the front that has not been deployed yet.
So there are different ways to manage that. But I actually I do know for a fact that that some people, you know, will check.
They'll check the deployed version against the client side version.
And, you know, if there's a mismatch, then you can, you know, reload or warn the user or there are different ways to approach it.
You could also keep track. You could increment some version number on your CI anytime you have a breaking GraphQL change.
Right. And you could say, OK, we don't need to restart anytime the client and server are out of sync only if there has been a breaking change.
And so you could check for that. So there are a number of ways to deal with that. But I think I think it's quite doable.
There are a lot of tools at your disposal there.
So what about when the server and the front end are not in the same repo?
Do you then try to avoid breaking changes and have the schema published somewhere where the front end can use it for at build time?
You could. I mean, at that point, you could certainly try to just avoid breaking changes.
There are there are tools out there that will give you a warning if you make a breaking change.
So you could at least put in a stopgap that says if there is GraphQL schema breaking change, then maybe it needs to be built with a certain flag or exactly something like that.
So that's that's one technique at your disposal, too. You could also you could also just you know, even if it's a mono repo, even if it's not a mono repo, you could on the back end before you make any changes to the schema,
you could fetch the latest client side code, clone that repo and run Elm make on it and make sure everything's OK.
So you can still achieve like some of those sort of characteristics of a mono repo, even if it's not technically a mono repo.
Yeah. And then, yeah, it's mostly about discipline. Like just when you do HTTP calls, right?
The thing is, it doesn't feel nice because you have Elm GraphQL that promises a lot of type safety and then you have to kind of trust whatever the server is giving you because you might be out of date.
And then you get those errors that they mentioned before.
Yeah, I think it's the kind of thing that really just requires some like infrastructure investment and some time and thought to come up with the right solution for your environment.
But all of these techniques, I think, are tools you can leverage to try to try to make that more robust.
Yeah. But even even without that, it will be much safer than if you did the GraphQL or HTTP requests on your own.
Right. Exactly. So should we talk about while we're on the topic of ways that an Elm GraphQL request can fail?
Should we talk about other ways it can fail?
Oh, well, you're getting me curious now. So, yeah. All right.
Well, well, the number one source of failure that comes up when you send an Elm GraphQL request, you do get an HTTP error potentially.
So that that's definitely, yes, that's a failure that Elm GraphQL can't help you out with too much.
There's there's another type of error, which is which is maybes or nullable fields.
So GraphQL has this concept of a nullable field. And what happens quite often, I sometimes get bug reports of why is my Elm GraphQL code generating a maybe here?
And I have a it's it's my most commonly linked to section of the FAQ document, which is well, oftentimes when people are like going to something like Elm GraphQL,
they were consuming that GraphQL schema from an untyped language and so weren't information that they made use of.
And so they just had nullable fields all over the place because it's the default in GraphQL.
When you define these GraphQL types, you have to go out of your way to put an exclamation point to say this is not nullable.
And so you'll even get like a you know, instead of getting a list of IDs, you'll get a list which could be null of IDs, any of which could be null,
which is something that happens often even in like public GraphQL APIs that people can't control.
But it is often not what what they were trying to express with those types.
But it just was like, no, it's it's not supposed to be null. It should never be null. But we've got to add those types.
So that's a server side issue, right? That is a server side issue. And that's not going to be an error.
Elm GraphQL is just going to, you know, faithfully put those maybes in everywhere and make you annoyingly deal with them.
So Elm GraphQL provides an escape patch, which is let me look up the name so I don't get it incorrect.
I made these names unappealing. So there's non null or fail. Yeah. I remember someone using that at work and and it failed.
And I definitely went out of my way to say somebody called selection set that non null or fail in the error message and say,
please find, you know, invocations of non null or fail in your code base. So I don't get bug reports there.
But yeah, so non null or fail. It's really a last resort. You want to avoid using it.
I have some similar helpers for for a list of noble elements.
And then I have like a more general map or fail. And that allows you to, you know, handle possible errors.
So if something if you want to validate some data, you can allow your decoders to fail making those guarantees.
Now, that said, I think it's I think it's a better practice to deal with those validations in custom scalar codecs.
So I would recommend reaching for that before you reach for map or fail. But in some cases, that can be useful, too.
All right. Have we covered the subject now or are there other?
I think that's pretty good. If you're if you're done with your tough, tough questions, they weren't they weren't too tough.
No, I know. I was just trying to scare you. Yeah. Yeah. That was intimidating.
How do people get started with the graph?
The number one place I recommend as a starting point is to just go to the Elm GraphQL package documentation and click the GraphQL dot selection set module.
I sort of step through the basics of a selection set. And if you understand the concept of a selection set really well, then you're going to be in good shape, I think.
So like a selection set can have zero items. You can have one item.
You can map together selection sets. You can treat it, you know, if you're familiar with a fragment in regular GraphQL,
you can build these composable pieces and mix them together just like a fragment in GraphQL, except they're just selection sets and you can mush them together.
Yeah, I feel like we should specify and this might be very late, but we're talking about Dillon Kern's slash Elm GraphQL.
That's fair. There are three others. They work differently and have different purposes, which we will probably not go into considering it's probably the we should be wrapping up now.
Yeah, I would say I think I lay that out in the readme.
Yeah. So at the very top of the readme, I point to a discourse thread that talks about the differences between the different approaches.
But in a nutshell, it's, you know, Dillon Kern's Elm GraphQL is the only one that is type safe, except there's actually one other library that's that's type safe, I believe.
But it's built around the concept of turning a single GraphQL query into an Elm function to call that.
I'm going to need to look into that, but I'll link to that, too. But Elm GraphQL takes this philosophy of being like a query builder that you can sort of compose together the pieces in Elm code rather than manipulating strings of GraphQL queries.
Yeah. Do you still do workshops around Elm GraphQL?
I might do another workshop. I have been thinking about maybe making my workshop into a video course and adding some additional content around that.
So if there's interest in that, let me know. I'd love to love to hear about it.
Yeah. And then other than that, I think types without borders gives a pretty good intro to sort of the philosophy behind it.
Which is a comfort stalk.
That's a comfort stalk I gave about Elm GraphQL and those ideas. And, you know, look at the Read Me. There are some other good resources there. And as always, there's a very helpful Slack community that you should reach out to if you want to ask questions or talk about best practices.
Yeah. All right. Any other parting words of wisdom or have we given people enough to think about here?
You should use selection set dot succeed.
Oh, I like it. That's a good note to end on.
Yeah, that's following the footsteps of the Jason episode and the tiny steps episode. Use selection set dot succeed.
Nice callback. Succeed is the key to success. Beautiful. All right, Jeroen. Well, thanks a lot. And I'll talk to you next time.
See you next time.