Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Controllers and apiBuilder

When all handlers are lambdas in main, the method quickly grows to a hundred lines. The usual practice is to move handlers into a separate class — a controller. Each of its methods takes a Context and works as a handler. Routes stay in Main, and to group them Javalin has the apiBuilder — a set of static methods path, get, post, delete.

The controller class

Create the post controller file. It receives the store through its constructor:

// src/main/java/com/example/blog/PostController.java
package com.example.blog;
 
import io.javalin.http.Context;
import io.javalin.http.HttpStatus;
import io.javalin.http.NotFoundResponse;
 
public class PostController {
    private final PostStore store;
 
    public PostController(PostStore store) {
        this.store = store;
    }
 
    public void getAll(Context ctx) {
        ctx.json(store.findAll());
    }
 
    public void getOne(Context ctx) {
        long id = ctx.pathParamAsClass("id", Long.class).get();
        Post post = store.findById(id)
                .orElseThrow(() -> new NotFoundResponse("Post " + id + " not found"));
        ctx.json(post);
    }
 
    public void create(Context ctx) {
        NewPost data = ctx.bodyValidator(NewPost.class)
                .check(p -> p.title() != null && !p.title().isBlank(), "Title is required")
                .check(p -> p.body() != null && p.body().length() >= 5, "Body is too short")
                .get();
        ctx.status(HttpStatus.CREATED).json(store.create(data));
    }
 
    public void delete(Context ctx) {
        long id = ctx.pathParamAsClass("id", Long.class).get();
        if (!store.delete(id)) {
            throw new NotFoundResponse("Post " + id + " not found");
        }
        ctx.status(HttpStatus.NO_CONTENT);
    }
}

The method code is the same as in the lambdas, but now each piece has a name.

Routes with apiBuilder

In Main.java create the controller and replace the post routes with an apiBuilder block:

// src/main/java/com/example/blog/Main.java
package com.example.blog;
 
import io.javalin.Javalin;
 
import static io.javalin.apibuilder.ApiBuilder.delete;
import static io.javalin.apibuilder.ApiBuilder.get;
import static io.javalin.apibuilder.ApiBuilder.path;
import static io.javalin.apibuilder.ApiBuilder.post;
 
public class Main {
    public static void main(String[] args) {
        var posts = new PostController(new PostStore());
 
        Javalin app = Javalin.create(config -> {
            config.routes.apiBuilder(() -> {
                path("/posts", () -> {
                    get(posts::getAll);
                    post(posts::create);
                    path("/{id}", () -> {
                        get(posts::getOne);
                        delete(posts::delete);
                    });
                });
            });
            // the before, after and exception handlers from earlier lessons stay here
        });
        app.start(8080);
    }
}

path sets the shared part of the address, nested path calls continue it. posts::getAll is a reference to a method of the specific posts object. The structure of the block shows all API addresses at a glance.

Call inside the blockResulting route
get(posts::getAll) in path("/posts")GET /posts
post(posts::create) in path("/posts")POST /posts
get(posts::getOne) in path("/{id}")GET /posts/{id}
delete(posts::delete) in path("/{id}")DELETE /posts/{id}

The API works as before:

curl -X POST http://localhost:8080/posts \
     -d '{"title":"Controllers","body":"Code moved to a class.","author":"Oleg"}'
{"id":3,"title":"Controllers","body":"Code moved to a class.","author":"Oleg"}

Common mistake

The static get, post, path methods from ApiBuilder work only inside config.routes.apiBuilder(...). If you call get("/", ...) directly in the configuration block, the server fails at startup:

Exception in thread "main" java.lang.IllegalStateException: The static API can only be used within a routes() call.

Inside Javalin.create write config.routes.get(...), and use the short get(...) only inside apiBuilder.

Thanks for reading!

Donate to the site — we work hard for you.