 Hello my DevNation friends, welcome to another Tech Talk. And today, if you like Quarkus, if you like web frameworks, today the talk is for you because we have a very special guest. I'd like to ask my friend, Stefan and Pardo, straight from Nice, I suppose, he's not traveling. Welcome, Stefan. Hi. Welcome. And thanks for pronouncing my name brilliantly. That's exactly it. Oh, yeah. Well, I studied French one year French, 25 years ago. And I hope I did a reasonable job. Absolutely. Perfect. And so, Stefan, today we're going to be talking about Quarkus-Henard. And I know that we have some Quarkus, we already have Panache, which is a French word, we have Henard, which is a French word. And I can see a pattern there. And maybe you're involved in this. So can you tell us why Henard? Yes, there is indeed a pattern. This is both our projects that I started. And I guess I'm really bad at finding names. And then I, at some point, I find a name that sticks in my head and it doesn't leave anymore. And for this one, I found the Renard name, which is a female fox. Because I thought what I was working on was a web framework which was trying to be smart and cute and clever. And when I thought about these qualities, then I thought about a fox. And female foxes, that's my opinion, if I'm not cute and smart and clever and agile. And so that's the thing that's stuck in my head, and that's why it's stuck. Yeah, good qualities for a web framework, right? Yes. All right, so let's see what we have with how amazing it is, how amazing Quarkus Henard can be, and I'll leave the stage to you. So today, live from Nice, France and Cary, North Carolina, welcome to Dev Nation. Thank you. So let me start the slides. I'm here to show you Quarkus Renard, which is a North school web framework, but modernized and running on Quarkus. So what is it exactly? So I do mention Panache. People ask me this question a lot, why are some extensions named after French words? That's because we have a lot of French developers working on Quarkus, and some of them just have this coming in mind. So what is it really? It's a web framework. It's very old school. It's not at all modern in the sense of the current way of doing web frameworks where everything is on the client. This is really something that is more akin to Rails and PlayOne with a, you know what I mean, attitude. We want the framework to let you do things as easily and short and concisely and intuitively as possible. It doesn't get in the way. It tries to help you always to what you want with a minimum of ceremony. And to that end, we use a lot of existing Quarkus features, like the server-side templating that we have with Qt. We use persistence with Hibernate ORM or reactive with Panache, which helps you a lot to cut down on ceremony as well. We use JaxRS and REST easy reactive, but something very enhanced that allows you to write your controllers and endpoints much easier. We make it very easy to deal with URIs, URLs, and redirection, et cetera, which is very important in web frameworks. We also use webjars to help you get client-side dependencies such as Bootstrap, jQuery, whatever. You name it. We use webjars so that you don't have to download them and package them in your application. Then we also have a lot of security integration with JWT, OpenID Connect, and web often fingers in the nose that we try to make it as easy as possible. So Qt. I don't know if you're familiar with Qt, but this is something that is in Quarkus that has been in Quarkus for a while already. It's a way to do server-side templating. But it's not just Qt and something that, because Qt is already more than two years old, there's been a lot of new additions to Qt lately, and this is very interesting to show, such as global variables with type checking, simpler type arguments, type type arguments, a lot of user experience progress, and we'll show in the demo integration with a VS code. When I talk about checkserries++ and REST easy reactive++, this is because we have much simpler endpoints with a lot of convention, such as your get methods don't need to be annotated with get because this is the default annotation if your method is public. Whenever your method is annotated with put, post, delete action, then by default we add the transactional attribute annotation. We also have implicit path annotations, and all this you opt in by extending a base class called controller, which allow you to get all these benefits for free, and a lot of methods as well that come from this base class. Whenever you're in a controller and you call another endpoint method, then instead of actually calling the method, it will trigger a redirect to this method, which is very handy to get down on ceremony because there's a lot of redirects going on when you do a web framework. If you don't want to do a redirect and you just want to obtain a URI, then we have a special method called router.getURI to which you pass a method pointer to obtain the URI to this method. This is something if you've done Jaxrs before, and you will know exactly what I mean because this is much simpler than the existing alternative of using reflection to get URIs. In templates, you also need to get URIs, so you have a special template, which is whenever it starts with URI column, then class.method, then you can point to the method you want to get the URI and pass the arguments directly there. It will build the URI for you. We have delayed validation for richer validation, which is something I will show, and the flash scope, which is also something that is very handy, which allows you to pass values from one request to another, especially after redirect. That's very handy. We have support for security, making it as easy as possible to do security with utility classes that help you secure your application in favoring JWT for user sessions. We also have a lot of utilities helping you deal with OpenIDConnect and WebOften. In all the cases, they work together. There's trivial configuration, great documentation, and lots of bug and UX fixes, and also support testing, where we provide mocks for both the WebOften hardware and OpenIDConnect providers, such as Twitter, Facebook, whatever we mock them, really trying to help you get everything working in your application. So now, next to be done is a to-do app. What we want to do in this demo is show an entire application from start to finish with user management to do handling and really an entire application written in runout. So as I said, maybe I didn't mention it, runout is a Quarkus extension, so the only thing you need to do is when you create your Quarkus application is to import the Quarkus runout extension, which has a few other options such as OpenIDConnect, OpenIDConnectTest, which with the mocking, et cetera. But mostly you just need to import the Quarkus runout extension in your Quarkus application, and then you will get the benefit from it. So when you create your Quarkus application, which we started here, I have a Quarkus application with the runout extension and a bunch of other dependencies, such as Postgres, I'm using Postgres, I'm using WebAuthn, OpenIDConnect, et cetera. I have my application running in Dev mode, and I have it running here, and whenever I go to the root of my application, I get a 404 because I don't have anything there. All right, so the first thing we're going to do is fix this. We're going to go in our sort folder, but there's a lot of convention in runouts. So here I'm going to create a class called application in the REST package, and I want this class to be a controller. So I make it extend controller out, and this allows me to opt in all the gray features and simplify Jack's REST. So I can just write string hello and return hello. And this will define a URL starting with application slash hello, which returns this. And I can go back here and paste it. I hit reload, and you can see that at some point I have this application slash hello appear. So I can try it, application hello, and I have my input. All right, this is cool, but this is not exactly what I want. This is the default path, but you can override it. And I want here actually to make it so that this happens at the root. So I go back here, and I'll reload my application. It takes a bit of time. Normally, hot reload is really fast on Quarkis, but in this case, I actually have a lot of OpenIDConnect configuration going on. And these hit a few endpoints going and querying Twitter and Facebook about configuration. So this is a bit slower than usual, but normally it should go like a few milliseconds for reload. So you can see here the root of my application is this. Now I want to make this a bit more complex. So I actually want to return a template instance. I want to be able to return a template called this index. And here I want to be able to say, I want a web page. So I'll use Qt and use check template, which is the way that we define type safe templates in Qt. And by convention, we put this in an aesthetic class called templates. And here, every method that is public static native and returns a template instance is going to be a template. And this is how we define the... Wait, why am I not... Should be... Well, okay, never mind. Yeah, that's the problem. I'm using the wrong key binding. So template instance here. This is how I define that I have a template and that it doesn't take any bar meters. Here I have a quick fix that I can use to create this template class. By convention, it's going to be in a folder called application with a class name and then the name of the method.html. But this is not really what I want. What I want here is to be able to include... Includes main.html, which is my main template. And then I'm going to pass a few parameters to it, such as the title. So this is going to be the title of my page. And this is going to be the body. So if you follow this link, you can see that we defined here the main template, which will insert the title. And create an HTML document with a bunch of stuff coming from Bootstrap that comes from the webjars. Which is something that we defined here as a dependency. We depend on Bootstrap, Bootstrap icons, and Quarkus webjars locator. That allows us to get all these CSS and JavaScript dependencies automatically downloaded and packaged in our application. And then I have a little bit of a menu and a little bit of a cute template that if I have a message, then I'm going to show it. And otherwise, I'm just not going to do anything. So here I have my template. It's called index. And what I need to do here is return this template. So we do that. Templates.index. And this is what I return. And now if I go back here and I click reload. I should have my application. All right, that's a bit big. So I have my application here. And now what I'm going to do is add a second page. I'm going to add an about page. So here I'm going to copy this method and say that if I go to the about URI, I'm going to call the about template, which I'm going to define here. And I call it about. And here I'm going to define that I pass a parameter. I don't really have a good name. And I'm going to say pass this parameter here and say image. So I'm passing this parameter to here. I'm going to create this file. And because this is a bit boring, I'm going to copy this one. And this is going to be my about page. It also extends main. And it says this is about param. That's the parameter that I defined. And you can see that the ID knows about it and knows its type. So I get even proper completion because I know it's string. Because I defined it here, a parameter of string. So now I can say this. I'll be about page. And I can go here to my application and go to slash about. And I should have my page. This is about the nation. All right. So now I need some navigation because I've been typing those links. Those URIs myself. And this is a bit annoying. So I want to be able to say that if I click on the to do place, I want a URI that directs me to application index. And here I want a menu item. Menu item. I have to remember exactly what I, how I named these attributes, but I think it's title. It's title and the implicit one. So title is going to be about. And the implicit one is going to be URI to application. So this is a tag from, from Qt. And you can see if I click there, I go, and you can see that all the files. HTML in the tags folder are automatically created as tags that you can include in other places. This allows for great composition in your, in your templates. And let's go back here and hit reload. And I should have a menu. Now if I click here, I go to the main page. If I click here, I go to the about page. All right. So you can see we make it really easy to get URIs in the, in the views with this, this special thing. So now we have the skeleton of our application. Let's go and create the to do application. So the first thing we do is we go in the model and we create the to do entity. How do we create the to do entity with, or I'm with Panache. We define that this is an entity. And we extend Panache entity, which allows us to get all a lot of good stuff for free, such as having an ID and being able to define all the columns that we want to persist in a public field. So I'm going to say that I need a task. I need Boolean to know if the task is done. And then I'll need it to know when the to do is done. So this is what I need for my entity. And I'm going to create also a static method, returning a list of two here to be able to get the list of to do. So I'm going to use methods defined on Panache entity, such as this whole, and I'm going to sort them by ID. This way that we always get the same list in the same order. Now I'm going to create a controller. And for to make it easier, I'm going to call this just going to rename it and copy it. So to do this controller, I'm going to have a single method for now, which is going to be to do slash index. And what I'm going to do is I'm going to say that the template is going to take a list of to do. So list. And here I don't need a special path. I don't think so. And I'm going to say I want the index and I'm going to call to do the poll. All right. So I've got my controller. I have an index template that I need to create that takes a list of to do. So let's do this here. I'm going to copy the about template that can be fine. Start. This is going to be my to do this page. So what I'm going to do here is I'm going to create table. And I'm going to have the first row with headers. I'm going to have the ID. And I'm going to have to do. And I'm going to have actions. Now I'm going to iterate. I'm going to create a row with three cells. I'm going to show the ID. Then I'm going to show the task. And let's start with no action. So I'm going to display the to do list. I have everything to display. Now I need to add a link to this in the menu. So here it's to do the index. Go back here. What happens when I click to do. Oh, I have an error. I'm doing a blocking operation on the I operate. Of course. Should have configured this. So what happens here is that I'm using having it. All right. And so I need to add the. An occasion here to say that I'm using a blocking framework. And this is then going to be fine. I should define this in on the package, but it doesn't matter. So here I have a list of two dues, but it's it's desperately empty. So what I do usually in this case is I add a file to the. have a list of to-dos, but it's desperately empty. So what I do usually in this case is I add a file called startup, which is going to be application scope, that is blocking. And I'm going to define a method startup, which is going to be called startup. And the way we do this on Quarkus is that we make it observe the startup even from Quarkus. And this is going to be called at the start of Quarkus. Now I'm going to create a new to-do with a task called Concentration. And this is going to be done. I've started this. When was it done? I'll say done date equals to date. Util. And then I'm going to process this to-do. I'm going to create a second to-do and I'm going to make this one not done yet. Do the definition talk. Because I'm going to create some persistence, I'm going to say that this is transactional because this is not a Quarkus run-out controller. So it's not automatic. So this should do the job. And now it's startup. I should have automatically created a set of to-dos which you show up here. All right. But here I don't see that one of them is done and when it was done. So let's go and improve the rendering of this. What I'm going to do here is that if to-do done, then I'm going to make it look removed and add on to-do done date since property. So at this point, you might be wondering, hold on, this is supposed to be calling this is supposed to be calling date methods, right? And there's no such thing as since property in date type. So what happens if I click there? You can see that I'm going to a file called Java extensions which has an annotation called template extension which allows me to add additional extension methods to existing types such as date by defining static methods and taking the first parameter as the type to which we have the method. So here the first parameter is type date and the method of the name, the method is named since which means we're adding a method named since to the date type and Qt will automatically call this static method to get extra methods on existing types. This is very handy. And in this case, we're going to use this. So let's go back to the view, click reload. Now you can see that one of them has been done 12 months ago. All right. So we can display to do this. Now I'd like to be able to add to do. So I'm going to have an action and I'm going to add here a form which points to to do the ad form. And I have here another tag that will help me which is called field. And I think I forgot the name. I need the name and the label. So name equals task. Equals task and I'll add placeholder as well. And this is going to call the ad method on the to do style. So now I need to create this ad method. And here the way I do this is that I define that it's both method doesn't do return anything, but it will take a rest form element which is the task, the element that you find in HTML holding. So here I'm going to say I need a new to do to do the task equals task to do the persist. And then I'm going to, so I'm persisting the new to do. And now I'm going to redirect to the index. I do this by just calling the index method. This actually triggers a redirect. It will not call this method at all. It will just throw an exception and redirect to the index. So now if I go back here, I have my field here. It's not exactly where I meant it to appear, but it will do. Hello, this works. Okay, well, here I have a problem. I'm missing validation because I've just submitted to use that warranty. And this is not what I want. All right, so I'll add validation. I'll add here that it can't be blank. This is having a validator in validation. This will make sure that if I call if validation failed, then everything will be set up automatically so that everything works and is forwarded back to the views. I'll show you exactly what I mean. If validation failed, I won't redirect to the index and be done. Again, this is this throws. So the flow doesn't continue. I don't need to change anything in my views. And here, if I go back here, I had reloads and I try to type enter. I get an error because it must not be blank. So what happens here is that if validation failed is a method which checks if the validation has errors. And if it has errors, it does a few things, such as prepare for an error redirect, which will take all the parameters we've taken, put them in the flash scope in preparation for a redirect, which happens here when I call index, I redirect to the get method. When I get to the get method, I see that I have a flash scope with values. I take all these values and restore them. And so that when I render the template index and I go back here and I go in the field, I can see I have here a bit of conditional says, if I have an error on this field, I will show the error for this field. This allows you to do validation very easily and pass all the validation errors that you need to your views. So now let's add a few more actions. I'm interested in two actions here. I want to be able to delete, to do. So I'm going to add a new form. Well, let's do it like this. To delete, to do.id. And I'm going to make a button, put strap between the danger, I think. And then I'm going to have another one. Well, I'm just going to keep this one for now. I think I will add them to the other one. So I go back to to dos. And here I want to have post method, leads and rest path. I'm going to do it on the path, this one. Now, id, I want to take the to do, id. So to do find by id, id here. I'm just doing a find by id. And I'm going to call a method called not find if null, to do, which will throw a 404 if the to do doesn't exist. And if it exists, then I'm going to delete it. And I'm going to flash a message. Add a message to the flash go saying the gym. And then I'm going to redirect the index. This is what happens when I hit delete. All right, so let's go back to the view, hit reload. Now I have here an action that allows me to delete items. And I can still add them and delete them. All right, so it works. So you can see it's fairly easy to define actions, model, controller, reviews, everything falls into place. But now I have this to do application. I like to secure it and make it possible to have users register on the application and verify that they have a set of to do's which belong to them. So to that end, I have here an entity called user, which has a bunch of properties such as username, first name, last name, password, email, confirmation codes, and a few methods such as find by username, find by confirmation code. And I have a login application, which allows me to do a number of things. I'll have to show you the UI for this maybe first. So I'll go here and I'll show you that I have here a login page and I'm going to add this to the menu. So in the main HTML, I'm going to have a login page here. I'm going to the login and show you the UI. This is going to be simpler. So here, if I go to the login page, then I have two options. I can either login by username and password or register and verify in email. So let's see what happens here. This login page, what is it exactly? It's pretty simple. We have two forms, one that calls the to login method on the login controller and passes username and password. And the other one calls register by email. So what do those do? If I go to the login controller, I have here the login view. And if I call do login, I pass it a username which cannot be blank and the password. I verify, I do validation verifying that the password has been set if the validation failed and I go back to the login form. If not, I find by username. If I don't find it, then I say that the username is wrong. If I do find it, I will verify using bcrypt that the password matches the encrypted password. Well, not encrypted but hashed. And if it failed, I go back to the login page. And if it doesn't fail, then I do a redirect using the get URI API to obtain a URI to the application index page. And I set a new cookie asking Ronaldo to make a user cookie for this, which is a JWT cookie, which will allow me to log in. So here I'm going to create a user. First name equals theft, user last name equals Ronaldo, user email equals theft, user password equals bcrypt util hash secret password. And then user purchased. And I'm going to save this user. I'm going to be able to say that the owner of this to do is my user because I want this to belong to this user. And the way I do this is by adding a new property, adding this field and say here, this is my user and this is a mini to one, this is the relation I have. And I want to be able to then display only the to do's from this user. So I'm going to change a bit my query here and call list, which takes a query by, this is the query, this is the sort, and these are the parameters that I want to pass. So I want to query by user and all those that match this user. And I have to adapt my to do's controller saying that here, I want to be able to say that this is a controller which has a user of this type. This allows me to get a bunch of very interesting operations such as getting the crunch user, pretty useful. Whoa, not this one, this user. And here I'm going to say that when I add a new to do, I'm going to remember the user. And now I'm going to here do another thing, make it blocking so that I can go in the main template and display the user. And let's go back here, main template. And I'm going to say that here, I'm going to display the user. I'm going to do this here, user, in fact, the crunch user. So let's go back. Oh no, I can do conditional here as well. I want to be able to say that if I don't have a user, I don't want to be able to add the link to the to-do page. So if I have a user, I want to be able to do, to go to the user page, to the to-do page, and also to be able to log out. And if I don't, I want to be able to log in. And then I close my, I think I have a final thing missing to be able to pass the user to the view, but I think, no, this is set up, okay. So this should work. Now, normally, to be able to log in my application, no, something is missing. Email, yeah, I didn't add the email. Did I, did I add the email? Users, I think the, probably the user was not found. Something happened weird. Here, main template, inject user, username, well, there is a practical email in the user. Is there not? This is, but I forget to set it. User to email, the user persists. No, I have everything. So, hey, I'm not sure what's happening here, but I can see that I'm getting a bit late. So probably I'll need to cut short this demo. I mean, I'm not sure what's happening. In cookie, no, I don't. Talk to you, there is now half, which would be a beauty. I don't think I can, I don't want, yeah. I'm already going late. I'm sorry about this. I'm going to show you quickly another feature before I go back to the slides and cut the short and read, sorry about this timing. So in the login, I have another method here called register, which allows you to register by email. So I pass the email and I create a new user. I persist the new user and then I'm going to send an email to the user and how do I do this? I do this by having another check template as I showed earlier, but instead of pointing to a view that will be showed to the user, this points to a mail template instance. So you just change the return type and this allows you to define a template that will be sent by email. So when you call templates.register, then you can say from this person to this person and send the email. And this allows you to define your emails using templating again with Qt. And so you have two variants, text file, HTML files and this allows you to send emails very easily. I'm really sorry about this demo not working. I don't understand what happened, but I'm already, so I'm going to skip this and I'm also going to skip showing you OpenIDConnect and web often. I'm really sorry about this, but I'm running late and when it's not working well, it happens. So let's go back here. So, what now? Quark is found out was released on Quarkiverse which is where we put all the non-core extensions in Quarkus which means it's integrated in, when you go to code.quarkus.io, you can get it and it's going to be listed and you can create your application from the start with the run out. The support for it was added in Quarkus 2.8 and the remaining OpenIDConnect providers such as Twitter and Spotify and also web often support is added in Quarkus 2.9 which should be out any moment now. Conditory release one was out the door already. The entire application, the entire code for Quarkus run out is on the Quarkiverse GitHub repo and the to-do sample app with everything working actually this time unlike my demo is also on GitHub under the Quarkus run out to do folder. Next in line is going to be integration with web components and maybe Quinoa which is another Quarkiverse extension aiming to do web framework at this time on the client side. Maybe we can have a mix of client and server side web frameworks together. That would be very interesting to experiment. I have a branch at the moment I'm working on with scoff-holding allowing you to automatically add a back office generated website to allow you to update your entities when you don't have time to deal with this. I'd like to make sure that internationalization is trivial to do in run out as well. So that's another thing that I want to add. And of course while we're looking for users and feedback so contributors would be even better but even then it's already something that has two applications, two real applications ported to it and several users looking at it. So it already has a lot of features but it's still early on so don't hesitate to try it out and tell us what you like, what you don't and tell us if you want to change anything or bring it to a different direction if anything is missing or what. We would really like some feedback about this. And I'm going to hand it back to you Edson. I'm sorry I ran over late and the demo fizzled at the end but sometimes it happens. Yeah, that's how we know it's a live demo and don't worry, content was great. We're happy to have you here. And my first, it's the first time I'm seeing Hennad and my impression is that, whoa, it looks super productive. So yeah, Kudos on the great work and we do have questions and actually some of them I was wondering myself. Let me try to see here on the list. And so the first question is from Daniel. So if I understood correctly the classes have to extend the controller. For endpoints, yes. This is how you opt in to all the magic that we do on top of Jack Serres. This allows you to have regular Jack Serres endpoints if you want that will not be touched and will not be magiced. And if you want them to be touched and magiced and opt into the whole magic thing, automatically adding annotations and redirects and all then you need to extend control. And Bruce has an interesting question. Can templates use JSF? No, no, I don't think you can use JSF. No, no, I'm sure you cannot. But I don't think he wants to really the the Qt templates are really nice and there's great integration with the IDE and there's a lot of features in Qt that you won't find in JSF. And it's old school but it's not that old school. JSF is really old and was not really built with the proper web mindset and had a lot of cost associated to it which we don't want to have in Qarkis. And also due to the ability to compile your application natively Qt has a lot of support, especially for that and benefits a lot for, we get a lot of benefits from using Qt with regards to native completion. So we get great performance and great features that would be very, very hard to do with JSF. And if I'm not mistaken, I think there is a Qarkis extension for a JSF too, don't we? Maybe there is, but I'm pretty sure it won't work with Ronaldo or I don't see how it would if people are very interested in this then maybe we could add it but I'm really not sure this is the right direction for templating. Yeah, I too understand that that they're completely different approaches. You have templating and have JSF components. So I understand the difference. And you can as well, I love JSF but I can see how I could use to love Ronaldo as well. And let's see another question from Daniel, probably about the email templating, like should we have two templates for the email, HTML and text? And if yes, how will be decided which one is sent? Actually both are sent. So you could have only one if you want to send only HTML emails and if you want to have only text emails, then you can have only a single one. But if you define both, then the email is sent with the proper, I can't remember how it's called but email supports a combination of multiple media types. So actually when you send an email, you can send the variance of the email within it. And so you have a text variance and HTML variance and then the user who's receiving it will, if he's reading it in mud, he will display the text email and if they're reading it in Thunderbird or Gmail, they will display the HTML email. So they're actually bundled together. It's a single email and that's supported by the email protocol. Oh, pretty cool. And Daniel seems to be very interested because he has another question. Like, is there a roadmap? No, when internationalization will be integrated? It's something that we really miss and he needs to support three or four languages? Well, there's no roadmap per se, but I also needed for porting more applications that I have. I have a bunch of applications running in production for 12 years now and they're all written in PlayOne. And I've ported one already to Quarkus with the Ronaldo and I want to port the other ones and they do need internationalization as well. So it's definitely on the roadmap and I know that Qt already supports internationalization. So maybe it's just a matter of documenting it or maybe there needs to be a bit of blue going on, but I'm sure it won't be hard. So it is on the roadmap and if it's urgent for you and you try Ronaldo and that's the thing blocking you then you can also help contribute it and then it will go faster. But I'm fairly confident that it should not be a lot of work because Qt already supports internationalization. Oh, and then you're saying, well, glad there is a version 1.0 now because he was waiting for it to use in production. So Qt is for the great work. Thanks a lot. And I'm trying to see here, I'm glad to say hi to DJ Maddy. DJ Maddy is one of our Dev Nation champions. He's always here watching the sessions. So hi, DJ Maddy. And I think we're out of questions. So, Stefan, thank you again so much for being here. You're always like, super welcome and super helpful when we ask you to present some of the great stuff that you're doing and let me put your screen here showing like the, the Ronaldo, is that the official logo that this is? Yes, I kind of minded it, it's a really like it. Cool, so thank you very much. Thank you all of our for our Dev Nation attendees, viewers. You're super welcome here. Just as Stefan is welcome here to present. And I'd like to thank you for all and see you soon in our next Dev Nation offering. Thank you very much. Thank you for coming. Take care. Bye.