Renarde renarde head Web Framework - Main Concepts

Models

By convention, you can place your model classes in the model package, but anywhere else works just as well. We recommend using Hibernate ORM with Panache. Here’s an example entity for our sample Todo application:

package model;

import java.util.Date;
import java.util.List;

import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Todo extends PanacheEntity {

    @ManyToOne
    public User owner;

    public String task;

    public boolean done;

    public Date doneDate;

    public static List<Todo> findByOwner(User user) {
        return find("owner = ?1 ORDER BY id", user).list();
    }
}

Controllers

By convention, you can place your controllers in the rest package, but anywhere else works just as well. You have to extend the Controller class in order to benefit from extra easy endpoint declarations and reverse-routing, but that superclass also gives you useful methods. We usually have one controller per model class, so we tend to use the plural entity name for the corresponding controller:

package rest;

import java.util.Date;
import java.util.List;

import jakarta.validation.constraints.NotBlank;
import jakarta.ws.rs.POST;

import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestPath;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import model.Todo;

public class Todos extends Controller {

    @CheckedTemplate
    static class Templates {
        public static native TemplateInstance index(List<Todo> todos);
    }

    public TemplateInstance index() {
        // list every todo
        List<Todo> todos = Todo.listAll();
        // render the index template
        return Templates.index(todos);
    }

    @POST
    public void delete(@RestPath Long id) {
        // find the Todo
        Todo todo = Todo.findById(id);
        notFoundIfNull(todo);
        // delete it
        todo.delete();
        // send loving message
        flash("message", "Task deleted");
        // redirect to index page
        index();
    }

    @POST
    public void done(@RestPath Long id) {
        // find the Todo
        Todo todo = Todo.findById(id);
        notFoundIfNull(todo);
        // switch its done state
        todo.done = !todo.done;
        if(todo.done)
            todo.doneDate = new Date();
        // send loving message
        flash("message", "Task updated");
        // redirect to index page
        index();
    }

    @POST
    public void add(@NotBlank @RestForm String task) {
        // check if there are validation issues
        if(validationFailed()) {
            // go back to the index page
            index();
        }
        // create a new Todo
        Todo todo = new Todo();
        todo.task = task;
        todo.persist();
        // send loving message
        flash("message", "Task added");
        // redirect to index page
        index();
    }
}

Methods

Every public method is a valid endpoint. If it has no HTTP method annotation (@GET, @HEAD, @POST, @PUT, @DELETE) then it is assumed to be a @GET method.

Most @GET methods will typically return a TemplateInstance for rendering an HTML server-side template, and should not modify application state.

Controller methods annotated with @POST, @PUT and @DELETE will typically return void and trigger a redirect to a @GET method after they do their action. This is not mandatory, you can also return a TemplateInstance if you want, but it is good form to use a redirect to avoid involuntary actions when browsers reload the page. Those methods also get an implicit @Transactional annotation so you don’t need to add it.

If your controller is not annotated with @Path it will default to a path using the class name. If your controller method is not annotated with @Path it will default to a path using the method name. The exception is if you have a @Path annotation on the method with an absolute path, in which case the class path part will be ignored. Here’s a list of example annotations and how they result:

Class declaration Method declaration URI

class Foo

public TemplateInstance bar()

Foo/bar

@Path("f") class Foo

public TemplateInstance bar()

f/bar

class Foo

@Path("b") public TemplateInstance bar()

Foo/b

@Path("f") class Foo

@Path("b") public TemplateInstance bar()

f/b

class Foo

@Path("/bar") public TemplateInstance bar()

bar

@Path("f") class Foo

@Path("/bar") public TemplateInstance bar()

f/bar

Furthermore, if you specify path parameters that are not present in your path annotations, they will be automatically appended to your path:

public class Orders extends Controller {

    // The URI will be Orders/get/{owner}/{id}
    public TemplateInstance get(@RestPath String owner, @RestPath Long id) {
    }

    // The URI will be /orders/{owner}/{id}
    @Path("/orders")
    public TemplateInstance otherGet(@RestPath String owner, @RestPath Long id) {
    }
}

Views

You can place your Qute views in the src/main/resources/templates folder, using the {className}/{methodName}.html naming convention.

Every controller that has views should declare them with a nested static class annotated with @CheckedTemplate:

public class Todos extends Controller {

    @CheckedTemplate
    static class Templates {
        public static native TemplateInstance index(List<Todo> todos);
    }

    public TemplateInstance index() {
        // list every todo
        List<Todo> todos = Todo.listAll();
        // render the index template
        return Templates.index(todos);
    }
}

Here we’re declaring the Todos/index.html template, specifying that it takes a todos parameter of type List<Todo> which allows us to validate the template at build-time.

Templates are written in Qute, and you can also declare imported templates in order to validate them using a toplevel class, such as the main.html template:

package rest;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

@CheckedTemplate
public class Templates {
    public static native TemplateInstance main();
}

Template composition

Typical web applications will have a main template for their layout and use composition in every method. For example, we can declare the following main template in main.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{#insert title /}</title>
        <meta charset="UTF-8">
        <link rel="stylesheet" media="screen" href="/stylesheets/main.css">
        {#insert moreStyles /}
        <script src="/javascripts/main.js" type="text/javascript" charset="UTF-8"></script>
        {#insert moreScripts /}
    </head>
    <body>
        {#insert /}
    </body>
</html>

And then use it in our Todos/index.html template to list the todo items:

{#include main.html }
{#title}Todos{/title}

<table class="table">
  <thead>
    <tr>
      <th>#</th>
      <th>Task</th>
    </tr>
  </thead>
  <tbody>
    {#for todo in todos}
    <tr>
      <th>{todo.id}</th>
      <td>{todo.task}</td>
    </tr>
    {/for}
  </tbody>
</table>

{/include}

Standard tags

Tag Description

for/each

Iterate over collections

if/else

Conditional statement

switch/case

Switch statement

with

Adds value members to the local scope

let

Declare local variables

include/insert

Template composition

User tags

If you want to declare additional tags in order to be able to repeat them in your templates, simply place them in the templates/tags folder. For example, here is our user.html tag:

<span class="user-link" title="{it.userName}">
{#if img??}
{#gravatar it.email size=size.or(20) default='mm' /}
{/if}
{it.userName}</span>

Which allows us to use it in every template:

{#if inject:user}
    {#if inject:user.isAdmin}<span class="bi-star-fill" title="You are an administrator"></span>{/if}
    {#user inject:user img=true size=20/}
{/if}

You can pass parameters to your template with name=value pairs, and the first unnamed parameter value becomes available as the it parameter.

See the Qute documentation for more information.

Renarde tags

Renarde comes with a few extra tags to make your life easier:

Tag Description

{#authenticityToken/}

Generate a hidden HTML form element containing a CSRF token to be matched in the next request.

{#error 'field'/}

Inserts the error message for the given field name

{#form uri method='POST' class='css' id='id'}…​{/form}

Generates an HTML form for the given URI, method (defaults to POST) and optional CSS classes and IDs. Includes a CSRF token.

{#gravatar email size='mm'/}

Inserts a gravatar image for the given email, with optional size (defaults to mm)

{#ifError 'field'}…​{/ifError}

Conditional statement executed if there is an error for the given field

Extension methods

If you need additional methods to be registered to be used on your template expressions, you can declare static methods in a class annotated with @TemplateExtension:

package util;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import io.quarkus.qute.TemplateExtension;

@TemplateExtension
public class JavaExtensions {

    public static boolean isRecent(Date date){
        Date now = new Date();
        Calendar cal = new GregorianCalendar();
        cal.add(Calendar.MONTH, -6);
        Date sixMonthsAgo = cal.getTime();
        return date.before(now) && date.after(sixMonthsAgo);
    }

}

This one declares an additional method on the Date type, allowing you to test whether a date is recent or not:

{#if todo.done && todo.doneDate.isRecent()}
    This was done recently!
{/if}

Renarde extension methods

Target type Method Description

Date

format()

Formats the date to the dd/MM/yyyy format

Date

internetFormat()

Formats the date to the yyyy-MM-dd format

Date

future()

Returns true if the date is in the future

Date

since()

Formats the date in terms of X seconds/minutes/hours/days/months/years ago

String

md5()

Returns an MD5 hash of the given string

Object

instanceOf(className)

Returns true if the given object is exactly of the specified class name

Global Variables

If you need to pass variables to every template, instead of passing them manually to every view, you can define them as methods in a class annotated with @TemplateGlobal:

package util;

import io.quarkus.qute.TemplateGlobal;

@TemplateGlobal
public class Globals {

    public static String lineSeparator(){
        return System.lineSeparator();
    }

}

This one declares a lineSeparator global variable that you can use in the views:

This system uses this line separator: {lineSeparator}

Renarde Predefined Global Variables

Type Name Description

String

request.url

The absolute request url, including scheme, host, port, path

String

request.method

The request method (GET, POST…)

String

request.scheme

The request HTTP scheme (http, https)

String

request.authority

The request authority part (ex: localhost:8080)

String

request.host

The request host name (ex: localhost)

int

request.port

The request port (ex: 80)

String

request.path

The request path (ex: /Application/index)

String

request.action

The controller endpoint class and method (ex: Application.index)

boolean

request.ssl

True if the request is served over SSL/HTTPS

String

request.remoteAddress

The remote client IP address

String

request.remoteHost

The remote client Host name, if available

int

request.remotePort

The remote client port

External CSS, JavaScript libraries

You can use jars created by mvnpm.org to provide third-party JavaScript or CSS hosted on the NPM Registry. For example, here is how you can import Bootstrap and Bootstrap-icons in your pom.xml:

<dependency>
  <groupId>org.mvnpm</groupId>
  <artifactId>bootstrap</artifactId>
  <version>5.3.3</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.mvnpm</groupId>
  <artifactId>bootstrap-icons</artifactId>
  <version>1.11.3</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-web-dependency-locator</artifactId>
</dependency>

After that, you can include them in your Qute templates with:

<head>
    <link rel="stylesheet" media="screen" href="/_static/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" media="screen" href="/_static/bootstrap-icons/font/bootstrap-icons.css">
    <script src="/_static/bootstrap/js/bootstrap.min.js" type="text/javascript" charset="UTF-8"></script>
</head>

Check the web-dependency-locator extension guide for more information.