Simple web application with template engine integration

Introduction

In this example you can explore how to implement template engines in DataKernel applications. This example is a Poll app which can create new polls with custom title, description and options. After poll is created, its unique link is generated. It leads to a page where you can vote.

See how simple it is to implement such features using DataKernel HTTP module. The embedded application server has only about 100 lines of code with no additional xml configurations. In this example we used Mustache as a template engine.

Here we will consider only ApplicationLauncher class, which is the main class of the application. You can find full example sources on GitHub.

Creating launcher

ApplicationLauncher launches our application and takes care of routing and generating needed content on HTML pages. We will extend DataKernel HttpServerLauncher, which manages application lifecycle:

Note: In this example we are omitting error handling to keep everything brief and simple.
public final class ApplicationLauncher extends HttpServerLauncher {

	private static ByteBuf applyTemplate(Mustache mustache, Map<String, Object> scopes) {
		ByteBufWriter writer = new ByteBufWriter();
		mustache.execute(writer, scopes);
		return writer.getBuf();
	}

	@Provides
	PollDao pollRepo() {
		return new PollDaoImpl();
	}

}

Let’s have a closer look at the launcher.

  • applyTemplate(Mustache mustache, Map<String, Object> scopes) fills the provided Mustache template with given data.
  • provide PollDaoImpl which includes business logic of our application.

Next, we provide AsyncServlet:

@Provides
AsyncServlet servlet(PollDao pollDao) {
	Mustache singlePollView = new DefaultMustacheFactory().compile("templates/singlePollView.html");
	Mustache singlePollCreate = new DefaultMustacheFactory().compile("templates/singlePollCreate.html");
	Mustache listPolls = new DefaultMustacheFactory().compile("templates/listPolls.html");

	return RoutingServlet.create()
			.map(GET, "/", request -> Promise.of(
					HttpResponse.ok200()
							.withBody(applyTemplate(listPolls, map("polls", pollDao.findAll().entrySet())))))

}

In the AsyncServlet we create three Mustache objects for our three HTML pages. Then we create a DataKernel RoutingServlet and define routing. Routing approach resembles Express. In the example we’ve added the request to the homepage.

In the request we are getting all current polls and information about them. This information is used to generate listPolls page correctly.

Method map(@Nullable HttpMethod method, String path, AsyncServlet servlet) adds the route to the RoutingServlet:

  • method is one of the HTTP methods (GET, POST and so on)
  • path is the path on the server
  • servlet defines the logic of request processing. If you need to get some data from the request while processing you can use:
    • request.getPathParameter(String key)/request.getQueryParameter(String key) (see example of query parameter usage to provide the key of the needed parameter and receive back a corresponding String
    • request.getPostParameters() to get a Map of all request parameters

Let’s add one more request:

.map(GET, "/poll/:id", request -> {
	int id = Integer.parseInt(request.getPathParameter("id"));
	return Promise.of(
			HttpResponse.ok200()
					.withBody(applyTemplate(singlePollView, map("id", id, "poll", pollDao.find(id)))));
})

This request returns a page with specific poll (if there is a poll with such id). Pay attention to the provided path /poll/:id. : states that the following characters until the next / is a variable which keyword is, in this case, id.

The next requests with /create, /vote, /add and /delete paths take care of providing page for creating new polls, voting, adding created polls to the pollDao and deleting them from the pollDao respectively:

.map(GET, "/create", request -> Promise.of(
		HttpResponse.ok200()
				.withBody(applyTemplate(singlePollCreate, emptyMap()))))
.map(POST, "/vote", loadBody()
		.serve(request -> {
			Map<String, String> params = request.getPostParameters();
			String option = params.get("option");
			String stringId = params.get("id");
			if (option == null || stringId == null) {
				return Promise.of(HttpResponse.ofCode(401));
			}

			int id = Integer.parseInt(stringId);
			PollDao.Poll question = pollDao.find(id);

			question.vote(option);

			String referer = request.getHeader(REFERER);
			return Promise.of(HttpResponse.redirect302(referer != null ? referer : "/"));
		}))
.map(POST, "/add", loadBody()
		.serve(request -> {
			Map<String, String> params = request.getPostParameters();
			String title = params.get("title");
			String message = params.get("message");

			String option1 = params.get("option1");
			String option2 = params.get("option2");

			int id = pollDao.add(new PollDao.Poll(title, message, list(option1, option2)));
			return Promise.of(HttpResponse.redirect302("poll/" + id));
		}))
.map(POST, "/delete", loadBody()
		.serve(request -> {
			Map<String, String> params = request.getPostParameters();
			String id = params.get("id");
			if (id == null) {
				return Promise.of(HttpResponse.ofCode(401));
			}
			pollDao.remove(Integer.parseInt(id));

			return Promise.of(HttpResponse.redirect302("/"));
		}));

Also, we defined main() method which will start our launcher:

public static void main(String[] args) throws Exception {
	Launcher launcher = new ApplicationLauncher();
	launcher.launch(args);
}

And that’s it, we have a full-functioning poll application!

Running the application

If you want to run the example, you need to clone DataKernel and import it as a Maven project. Before running the example, build the project (Ctrl + F9 for IntelliJ IDEA). Open PollLauncher class and run its main() method. Then open your favourite browser and go to localhost:8080.