How SvelteKit & GraphQL plays well together
- Jean-Yves Couet Dynamic Process - CEO
Let's show you the magic of GraphQL with Houdini.
"Hocus Pocus keep your focus"
Transcript
Welcome ladies and gentlemen to the greatest show on earth.
Uh, on the web, uh, on Svelte.
At least in my bedroom.
I'm Jean-Yves, your French tech magician.
And today at Svelte Summit, I want to show you how SvelteKit and GraphQL plays well together.
But before we start, let me assure you that there is no smoke, no mirror code.
Here, we don't summon rabbits from hat, but data from server.
And I will tell you that this is magical.
Hocus pocus, keep your focus.
But before we start, is there any alternative to GraphQL?
Maybe REST?
Maybe GraphQL?
Maybe something else?
(silence) Dig into it.
First with slide and with the architecture.
So most of us have some endpoints with some data or something else, whatever.
And with Svelte, we were doing a lot of CSR, going to a REST endpoint.
And this REST endpoint is doing hopefully some business logic to retrieve some data and displaying it on the UI.
With REST, the good thing is we are able to have a UI representing the data or being able to play with the data without any browser interaction or Svelte interaction.
It's just about the data.
And then when SvelteKit arrived, we were about to have this SSR, meaning that we can get the first page with the HTML already with the data.
All right, what's the deal now with GraphQL?
GraphQL is about the same thing.
So in the end, it's replacing REST.
We have this GraphQL endpoint, and then we have a GraphQL to explore the data.
All the rest is very similar.
We have the SvelteKit story where we can do SSR and the SvelteToy where we can do CSR.
So it's really about the same thing.
First thing, let's have a look at this layer of browsing data.
For this, I will go to the SvelteSubmit endpoint and I prepared the two.
So the first thing is the REST swagger.
If I go to the REST swagger, we can see that we have a REST with task and categories.
And maybe if we look at task and we try out this task execute, and then we get a big JSON of a lot of things.
So hello endpoint, check the tire pressure, and we can see maybe a category with an ID, a strange ID here.
But at the beginning, I saw that there was a category.
So if I go get categories, try out, execute, and in here I have two answer, tech and bicycle.
Ah, now it makes sense to check the tire pressure on the bicycle.
Okay, so now that we can see data, let's see if we can post data.
So to add a category, for example.
If I go here, I'm able to see that it requires a name and let me try it out.
So if I put the name on REST, for example, execute, I see that it's executed successfully.
And then we have the name REST with a new ID.
And if we come back now to the get part and execute, we should see three element, tech, bicycle, and REST.
Okay, so we are able to see data and to update data or mutate data, very good.
Let's have a look now at the other part.
And GraphQL come with another UI, let me make it bigger maybe, where we can see show GraphQL Explorer.
And with the Explorer, we're able to see what is all available on my endpoint.
And we can see again, categories and tasks and other things.
So let's have a look at first the task, for example.
And when I click here, you see that it's building my query in the middle.
Task, items, title, for example, and I can execute my query.
And in here, I can see only the title, which is what I requested, nothing more.
This is already the big difference that we can see between REST and GraphQL.
So we were able to see before as well the categories.
So let's have a look at categories, item, name.
And if we hit, so let's remove the task.
If we hit enter here, we're able to see tech, bicycle, and REST.
Good, we are able also to mix and match things.
So if I come back to my query, in the editor itself, I don't need to click on the left part.
In the editor itself, I can do a control space and I will have auto-completion everywhere.
So if I do again, task, open bracket, item, open bracket, here I can see the title, like we did before.
But now we can get maybe the category of it.
So if I control space, I can see category, and in the category, I can display the name.
And now I can see that hello endpoint is from the tech category, and all the rest is coming from the bicycle.
Okay, I have a lot to do in bicycle point.
Now let's have a look at how to add data like we did before.
So if I go back to categories, categories, items, name, we added the REST here.
Let's add now the GraphQL.
So I do a new query here, and it's not called a query, actually it's called a mutation.
And if I create an operation that look like a mutation, I'm able to see here create category, and I will give some parameters.
It needs an input.
In the input, it needs a name, and I can put GraphQL.
Every mutation need to return something.
So it will return the category normally with the name, for example.
Now if I execute this query, I can see GraphQL, and if I go back to my previous one, and if I run this, I can see the four options.
Okay, now we can see that we can grow the data in both worlds, so in REST and GraphQL.
So we are able to grow the data.
Now we have the toolkit, which will deal with component, route, and everything.
And we have GraphQL, which will speak the semantic of the data.
And to close the gap between the two, we have a library called Houdini.
Let's first initialize our project and initialize Houdini inside of it.
So I open my command line, and I create, so npm create skeleton, add latest, hit GraphQL, and done.
We want a skeleton project, we want it in TypeScript, and we check everything.
CD, kit.
graphql, code, boom.
And now we have our code, A-Tor.
Let's close everything, and let's do an init first, directly after creating the project, init.
Save.
We will install everything, npm i, npm dev.
Okay, let's check now if it's working.
Okay, good.
It looked like it's working, we have a welcome to self kit, perfect.
Let's continue a bit to tweak this first example.
So if I go to route, here I can create a plus layout.
shelves, that will be a way for us to navigate through things.
We can just remove here, and we can put the H2 here to say it's home, save.
In here we can put a slot, save.
We have home displayed, and in the H1 we can say, welcome to kit.
graphql, save.
Okay.
And let's do, weld.
head, we can put the title.
So it's kit.
graphql.
Okay, we don't see the title, but it's fine.
And most important part, we can add a default CSS, which will tweak all our element in a nice way in the background.
Okay.
So now that we have most of it ready, so tweak.
Now that we have everything ready, let's add Houdini.
So to do that, I will do a mpx Houdini, Houdini init.
And it will ask me, hopefully, and it will ask me if I have a remote GraphQL endpoint.
Yes, in this example, I have a remote GraphQL endpoint, and you remember before it was something like this, so localhost, api.
graphql.
Enter.
Perfect.
Let's do nothing here.
We have a few files created.
Let me check this file.
So something in the gitignore, because we generate some part, something to help the F code, the configuration of Houdini itself, some documentation about the package, the schema, so the contract between the client and the server, and we'll come back to this later.
We'll update the self-config to have Houdini working with it, a few ts-config tweaks, so that we have better typings, and then finally, the client.
So we do init, Houdini.
So we do pnpm i, to install the latest of everything.
Okay, very good.
And now we do pnpm dev.
We should see our site coming again here, live.
Yes.
And we can see that there is no operation found.
Everything is normal.
Now that we initialized everything with Houdini, let's grab our first data.
And to do this, we have this special page.
gql to speak only about the data.
So let's go here.
We know that we have in route the plus page.
self, dealing with the self component, the route component, and we can create another file called plus page.
gql.
And in this file, I can do query and open the bracket.
And here I have exactly the same experience as the GraphQL we saw before.
Auto-completion through the server, okay?
Through the endpoint.
So if I do query, categories, and in categories I have some items.
In the items, I have a name.
And in here, I just need to add the name of the query.
So it will be category, for example.
And if I save this, now Houdini is telling me, oh, there is something new coming called categories.
Good, so we seem to have data.
How to bring it now to the plus page.
self?
Exactly like what we do with the data in a usual self component.
So I open the script tag here with the line yes, because I'm in TypeScript.
And in here, usually I do something like this.
So let's do a bit of space here.
So I import page data from the type and export the data from page data.
Since we work with Houdini, we don't import from $type, but $Houdini.
And by doing so, in here, I can see that we have data category.
And the category is a store directory.
So if I control space here and I get the category, at this level, I'm able to say already, so let me have a look here.
I want to get items and the items will be the store.
So $categories.
data.
categories.
items or nothing.
And you saw with this already that everything is type.
So in the home, I can do a for each.
And on each items, I want to say it's Cathy.
And in there, I will have like a P tag, for example, Cathy.
And in here also, everything is type.
So I have only the name available.
And now I save it and boom, I have my tech, bicycle, racing, golf gear.
Good.
Now it's good because we are able to get the data.
Let's have a look with parameter how it look like.
So you remember that in each category, we have a few tasks.
So maybe we can create another route called task of category.
Category.
And the category here will be the ID of the category.
So we can do task of category and maybe add a dash here.
In here, I'm able to create a file called firstpage.
relt.
Since here I have my list of tasks, let's see if we can already navigate to this one.
So in here, instead of just displaying the name of the ID of the element here, I will put the href, sorry, A like this.
And I will come here, bring this guy in the middle here.
So now it's a link.
And in the href, I will do /tasks/of- and we do one, two, three here.
Save.
Now everything is a link.
If I click here, I have a list of tasks.
I can come back.
Let's just, for convenient reason, bring a link here.
So I do link to home.
So it's with the A, home.
And the href is like coming back here.
Okay, so I can go home.
I go tech, home, tech, home.
Very good.
Let's close this one.
And now we need to bring here the ID of our category, right?
But in the category, we get only the name.
So not a big deal.
Let's go back to the firstpage.
jql.
And in here, I want to get the ID of it.
So if I save this now, so the category is updated, as we can see here.
And now I can come back here.
And instead of the ID here, I see that I have ID and name.
And instead of the hard-coded ID, I can simply add here, item.
id.
It's not item, of course, it's category.
id.
Save.
And now if we go here, we don't see because the URL is too big, but we see that we have an ID here.
Okay.
So it looked like it's working.
Now we go to each page, and each page have a different thing.
But now how to bring the data?
Then we do exactly the same thing that we created this pluspage.
jql on one side.
We'll create this pluspage.
jql on this other side.
So pluspage.
jql.
And in here, we do also a query.
And the query, I have my category.
In my category, I want to see the task of the category, for example.
And in the task of the category, I have a few items.
In the items, I have the title.
So here, I have to name my query, as usual.
So task of category, for example.
And it's telling me that there is something wrong here.
Yes, because a field category need an argument called ID.
So let's add the ID.
And the ID will be something.
And this something is coming from the URL.
And fortunately, if we have this parameter here, it's already available in our pluspage.
jql.
So if I do $category, this will be a variable.
And it's a variable that will be sent from the top of the query here, ID.
So like this, this variable is coming here and setting it to this category.
And then if I save this, I have now this new task of category coming from Houdini.
And if I go to my pluspage.
jql, where I have my list of tasks, same thing as before, I will do my script tag because I want to get the data.
I have my little snippet, page data.
Instead of coming from type, I do Houdini.
And in there, in the data, I have already my task of category.
In my task of category, I will do the same trick.
Items equals to task of categories.
No, not this one for sure, the $ one.
And we have already a lot of things here, the data, the category, the task of category, the items, or nothing.
Same thing as before, here I do each items.
Oops, items.
So this will be a task.
And in here, I should be able to display the task.
title.
Save.
Okay, so now if I go to tech, if I go to tech, I have my hello endpoint.
And if I go back home to bicycle, I have a lot more things to do.
Okay, and we have our two other things, the rest in GraphQL.
Good, let's have a look at the network a bit.
So if I do F12, network, and in the network type, I check only for the GraphQL API.
So I'm on home now.
If I do F5, there is nothing about API GraphQL.
Why?
Because everything was SSR.
If I go now to tech, I see that we have one GraphQL request coming here.
And this one GraphQL request, we have the data coming back with our task title, hello endpoint.
Cool.
If I remove this now, and if I go back to home, nothing is happening because everything is cached.
If I go to the bicycle, I have a second GraphQL query coming.
If I come back home, everything is cached.
So no queries anymore.
And now if I navigate to tech or bicycle, there is no query happening because everything is cached.
Cool.
A few days ago, someone online said that it's good to define data requirement in a component and fetch it at the route.
So now it's what we are doing, we fetch at the route.
But we don't have the data requirement in a component itself.
Everything is in the route, which is a problem.
And I totally agree with this statement that the data requirement should be in a component directly.
And it's also what Alec commented, this should be Houdini's new tagline.
I fully agree as well.
Let me introduce you at the same time, Alec, the creator of Houdini.
And without him, all of this would not be possible.
So thanks a lot, man.
Let's speak about components and about fragments.
- Let me show you the code.
- In our library, there is nothing today.
So let's create something called task.
spell.
The task.
spell will be empty for now.
So if I put just a P here, hello, just to make sure that everything is wired, I'm doing it like this usually.
So instead of the task title, I will just do here task.
Okay, do the right import, save.
And if I go to bicycle, I see my five hello.
Now let's go back to something that, with data, right?
So now we have to improve our task component to be able to have a fragment inside the component and make it a query from the page.
For this, I will go to the Houdini website.
And in the Houdini website, I want to show you a few things.
First thing, if I go to the API doc, I have something called fragment.
And in here, I have directly the example of how to use a fragment.
So a small cool feature is that we're able to see the code here in gs doc.
And over there, we can switch it to TypeScript.
So let's bring all this into my component.
So let's make a bit of space here.
And instead of having hello, I will just maybe put this one at the bottom, hello, and not display anything, not an image.
So here, I just copied this example.
But of course, we don't have this kind of code in our case.
So we did not with user.
So let's have a look inside here.
There is a GraphQL statement.
So of course, when I just copy paste the code, this code is not happy because we don't have avatar user or anything.
But let's focus first at our GraphQL statement here.
We have a fragment with a name.
And here, our name will be more something like task component.
This is the name of my component.
And this will be on the type, and if I can control space here, on the type task.
And on the type task, what do I want to get?
So if I do control space here as well, we have already also auto-completion, and I want to get the title.
All right, already good.
Now with the task component that was just created, it will create a type called task component.
So I can copy this and paste it everywhere here.
And now if I save, everyone is happy.
We can see that we export something here called user because it was from the example.
And in our case, it's not user, of course, it's task.
So now we have a task, and in the task, I will want to look into the title.
So if I go one step before, this component is complaining because we are missing the task.
So let's bring the task, and the task is equal to something where we need to assign the task component.
The task component, so let me say task is coming from the task that's up above.
And here, of course, if I save this, it will shorten the thing.
And it's telling me that the fragment is missing in the route plus page of the query.
So if I go back to the route here, and I do dot dot dot, control space, I can see that I have one fragment available to put here called task component.
And if I save now, I have this coming here, and this will be sent to my component.
And in my task here, I have my hello that I can replace by dollar, dollar, data, whoops, dollar, data, dot title, save.
And now all my data are coming here.
But the magic of this is now this component is fully autonomous.
So if I want to display now here, title and ID and save, everything is refreshed in the background.
And I'm able now to say, whoops, dollar, data, dot ID, save.
And ID is here.
And the cool thing is that it's available only to this component.
And this is called fragment masking.
So if I go one level above here, where I was able to do this task, the title, in here, if I do dot, I don't see ID.
Even if ID is fetched into the component inside here, I'm not able to get title here, the ID here.
So I get the title because it's displayed here.
So we can see it here.
But if I now go back to my plus page of GQL and I comment this title, if I save this, and I go back to my component, it's telling me that the title is not available at this level.
So at this level, I'm allowed only the thing coming from the page of GQL, not anything else.
So every component is responsible for his data, nothing more.
So I can now remove this safely.
And then we have our beautiful component.
Make sense?
So this was about fragment.
And yes, you're totally right.
It should be the new timeline of Houdini.
You fetch data in your route and you have your data requirement in your component.
Like this, everything is safe to avoid.
You can have nested component, nested fragment, everything.
Okay?
Now let's look at a few utilities.
First one is about paginate.
So in here, you see that that will be overloaded because I have a lot of things to do in one to five items.
So if I go to this task here, page of GQL, at this level here, we have a list of something.
So when we have a list, we are able to do an @paginate.
If I save this now, it's telling me that, oh, to have a field paginate, you need a limit argument.
Okay, let's have a look at the limit argument.
And I can do a limit.
Now, if I save this, I have only two items.
Cool.
Just with this one paginate, I have two items.
Cool.
And now if I go to my list of tasks, I can say under something like button, next, for example, and then on click, to be able to go to the next page, right?
And to do this, I will go to path of category, dot load next page.
Save.
And now I have another button called next.
And if I click on it, it's going to the next page.
How nice is this?
It's only one directive away.
So now that we have this paginate directive, let's look at this other directive called loading.
At the leaf level, so the task, I want to say that this can be loaded.
So loading and loading.
Okay, save.
When I have this loading, now I can do a switch statement at this level.
I can do here, if he's pending.
What is pending?
If I have my $.
data.
id is pending, for example, I will display loading.
Of course, you will go with crazy CSS here.
Not with this, but.
.
.
And then else, you want to display something.
Okay, to have this working, we have from the root, say, I want to opt in loading.
So I have to say loading on top here and save it.
And if I now go to my network, because my network is fast in my environment, and if I go slow 3G, if I click bicycle here, I navigate to my page to download the JavaScript and everything that is needed.
And then I'm able to see loading, loading, loading, and then my two things are common.
Good, good, but we know already that we have a limit of two.
So let's say here loading, and let's specify the count will be two.
And like this, we will be having exactly the same thing.
If I come back to my task, in the meantime, I'm able to also put a P tag here so that the loading will be in the same place as the title and ID here.
So I go back to fast 3G, to fast network, home, F5, and now I go back to slow 3G.
And if I go to bicycle, now it's downloading my JavaScript part, then moving to the next page.
I have my two loading, and now when my data is back, I have my checked tiger pressure.
Everything is good.
Just adding this loading directive.
You see that we have two distinct things.
One thing is about Zeldkit and the component itself.
The other thing is about data and semantical data.
And here we say data can load.
Okay, very cool.
So now that we are able to load things also in a pretty granular manner, let's have a look at how to mutate data and add information.
And for this, we will create a new category.
So for that, I will create a new component here.
So add category, for example, .
Zeld.
And in this add category, I will have an input of type text.
I will bind this value to name.
And then script, yes.
I will have let name is nothing.
And I will have my form.
In my form, I have a button.
Add, yes.
And when I go on, submit.
Submit.
On submit.
We call name.
Okay, so now if I go back to my page and I add at the bottom here this add category, save, I should have this coming here.
So of course, not working.
You see, so that was not working.
So I can say new thing, add.
And it's removing the new thing.
It's not adding here because we didn't do anything.
Okay.
So now let's add here the mutation.
And for that, we will do a let add and we'll do equals to GraphQL and the GraphQL coming from Houdini.
And we'll open our template tag.
And in our template tag, it's exactly like the GraphQL we had before.
So I can start typing mutation.
We do add category, for example.
And in there, I'm able to write down my mutation of create category.
And you remember that it needed an input.
In the input, I need a name.
And the name will be of $name, for example, which will be the variable.
And then I need to return something.
So if I return category, name, for example, is good.
So since it needs the $name here, I need to pass it as an input and it will be a type string.
If I say that, you can see that Houdini creates me the add category.
And the add category now is a mutation available.
So if I come here and say add.
I have this store where I can mutate things.
And in the mutate, I can do control space here.
And you can see that you have the variable name.
So in here, if I put the name, then it will mutate with the name.
Let me save this and put an await.
So I need an async.
And all this should be good.
So from self kit, add.
And if now we refresh, we have something coming from self kit.
Okay, everything good, but we needed to refresh, which is not the best experience ever.
Moreover, that we have one list here coming with the name and we have another list coming with maybe other fields.
So let's have a look at how to deal with that.
If I go back to my plus page, the jquery, here I have my list of items with the ID and the name.
And on the items here, I can add list and say that this list, the name is called L_category, for the list of categories, for example.
And if I say that, it updated this, of course, but it will update something else as well.
But it will update something else as well.
So if I go back to my add category in here, I'm able to say, dot, dot, dot, L_category, which was the list that I created just before, insert.
And if I say that, it will refresh everything, of course.
And now if I do, let me open this in big, for example, so I have no request here.
From self_kit number two, add.
And now, with only one GraphQL request, we can see here, it's only one GraphQL request, which was the mutation, create category.
We also filled the list here.
So we don't need to do a F5 anymore.
It's already appending into the list.
With just this little fragment here called, with the name of the list, underscore insert.
The really cool thing as well, is that if I go back here to this plus page of JQL, I can improve the requirement of this thing here.
And maybe say, in here, I want to see also the total count of task category.
Oops.
And it increased the number of things I want from the fragment.
And in the page here, instead of just displaying the name, I want to display also the category, dot, task category, dot, total count.
If I say that, now I have number of tasks in each category, okay?
And now, I didn't change anything in my mutation.
So I can do form_kit number three, add.
It will directly do the mutation for sure, and do the query with this total count included.
So you don't need to think about, oh, this list have this number of data, now I have to return this or that.
No, everything is self-contained.
Everything works with component.
Everything works together with fragment.
Fragment is one of the biggest thing on GraphQL.
Of course, in here, you can add a count, default, default, this, and do a log result.
And if I log this result, if I do a, for example, add, you can see that data category, the category is new.
Why is it new?
Probably because we got an error.
So in here, we can say, not only category, but I want to see error.
And error, depending on the type name, so on error, I want to see the message.
And if I now trigger again the a, enter, I'm able to see that data, we had an error, name is too short.
Okay, so my server doesn't accept one character.
Okay, so now we can extract this for sure in a separate logic, and do something around the name, and the name is too short.
All right, so one good thing is also about this one schema, which is somehow the contract between the client and the server.
So let's go back here, and let's pretend that in our backend, someone is changing something, a field, which sometimes happens.
And I don't do anything on my client code, and Houdini, which is checking the schema from time to time, tell me, oh, cannot query title from the type task.
And fortunately, if I go to my diff here, I can see directly that, oh, yeah, in create task input, it doesn't require now a title, but a title name.
And you can see that, yes, it seems that the whole title was renamed to title name.
So either you go to your backend team and say that, please do something, this is a breaking, it's a breaking something, but you are ensured about consistency.
And this is the key point.
You want to have this consistency, and you want to be aware as soon as there is something inconsistent.
It's not just preying on the runtime part that the return of a patch doesn't return title anymore, but title name, right?
You heard probably about another topic called persisted queries.
What is it about?
I think there is two angles to see that, or three or four.
The first one is about security.
One thing is that you don't want to enable anything from your endpoint, right?
Because a customer can do like very nasty queries, very, very deep and everything.
So you want to avoid this thing, and doing so, you have to enable persisted queries.
Like this, the server will be able to answer just with an ID, with a hash.
And fortunately, if you go to this dollar Houdini, you have this persisted queries.
json.
And in there, we write down the list of all the queries possible in your application, and you can see that add category, categories, and everything is now an ID or a hash.
Now you just need to give this to your server, and you need to allow this to your server to only be able to execute these three things.
So it's two advantages.
First one is security, because you need to know the hash to be able to execute something.
You cannot execute random things.
Second thing is about the weight of what you transfer from your client to your server.
Now you don't need to transfer the full query, which is quite light, but now you just need to transfer a hash, which is also a nice benefit, right?
And then of course, I cannot leave without speaking about the very famous GraphQL n+1 issue that everyone is talking about on Twix.
But let me tell you something.
It's not a GraphQL thing.
Let's have a look at our example.
So we take our architecture, and over there we have task and categories, right?
With GraphQL, when we want task and categories, we will do something like this.
So we want all the tasks, and then the items, we want the title, and then for each task, we want the category and the name.
This will be sent to the server, and over there we'll have a few queries.
The first one we select on the task, and then we'll have as many select from categories where the ID of the category ID is here.
And this is what is called the n+1 problem, because yes, you can get a lot of SQL queries, which is not that good.
So let's have a look now at the REST side of it.
I put in place some code here.
So first we get the task, and then on each task we will get the category to get the name.
So first request will happen here, then we go back to the browser, and then we do multiple other requests to get the category name.
So here we have even a bigger problem, I would say, is that the n+1 issue is a network issue and a SQL issue, while on GraphQL, it's only a SQL issue.
Of course, it's not what we want.
But everything about this relates me to this very famous thing.
I think that we will arrive to n+1 issue very fast.
So people from the REST side will tell me, "It's easy, just create another endpoint called task and category.
" And yes, it's true.
On the front end, now you have only one thing, and then we will fetch this task and category, and this will result in one very nice SQL statement with a from and a join, and this is the most optimized thing.
Very good.
Now on the right side, on GraphQL, are we stuck with our SQL queries?
Actually, no.
There is already the traditional data loaders, where it will not go down to one SQL query, but to two, which is probably good enough in 99% of your cases.
If you want to know more, there is a data loader 3.
0 talk that was done by Jens from Wondergrap.
Actually, it's rethinking about how to use data loaders.
Very interesting talk.
And then there's another initiative called Graphas from Benji, where at this level, he's able to go to this one SQL statement with a join.
So pretty good research.
And if you see, it's at the bottom right, so meaning that all this happened on the server, meaning that on the front end, there is zero change.
And this is the best thing out of it.
When you have a struggle with performance and data and something, you look into your server and optimize things over there.
But on the front end part, no change.
So you require all the tasks and the categories the same way.
And this, I think, is a huge benefit.
Thank you very much for listening to all what I had to say.
If you want to come and join the conversation, go simply on Houdini on GitHub, and we will be happy to welcome you there and teach you all the stuff about GraphQL.
Thank you very much again, and have a nice end of Zeld Summit.
Bye-bye.
- Okay.
Can we rest a bit?
Yeah, it's awesome.