→ Slide 1

Javalin - Lekki Framework Webowy dla Javy

→ Slide 2
  • Javalin to lekki framework do tworzenia aplikacji webowych i REST API w Javie
  • Oparty na serwerze Jetty (serwer HTTP i servletów)
  • Przeznaczony dla programistów, którzy chcą szybko tworzyć aplikacje bez dużej ilości boilerplate'u
  • Idealne do:
    • REST API
    • Mikroserwisów
    • Prototypów
    • Małych i średnich aplikacji webowych
  • „Czysty” kod Java, bez adnotacji i refleksji
  • Następca Spark Java, ale z nowoczesnym designem i lepszą wydajnością
  • Obecna wersja: 7.x (znaczące zmiany od wersji 6.x)
    • W Javalin 7 trasy definiujemy przez config.routes podczas Javalin.create(…)
→ Slide 3

Plik pom.xml:

<!-- Javalin Core - minimalna zależność -->
<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin</artifactId>
    <version>7.2.2</version>
</dependency>
 
<!-- Javalin Bundle zawiera wszystko co potrzebne -->
<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin-bundle</artifactId>
    <version>7.2.2</version>
</dependency>
→ Slide 4

javalin-bundle to „all-in-one” pakiet zawierający:

  • javalin-core - główny framework
  • jetty-server - serwer HTTP
  • jetty-servlet - obsługa servletów
  • jackson - serializacja JSON
  • slf4j-api - API loggowania

Zaleta: Nie musisz ręcznie dodawać zależności, wszystko jest już skonfigurowane i kompatybilne

Inne moduły: javalin-rendering-*, javalin-ssl, javalin-micrometer

→ Slide 5
import io.javalin.Javalin;
 
public class HelloWorldApp {
    public static void main(String[] args) {
 
        Javalin app = Javalin.create(config -> {
 
            config.routes.get("/", ctx -> {
                ctx.result("Hello World!");
            });
        });
 
        // Uruchom na porcie 8080
        app.start(8080);
    }
}
→ Slide 6
  • Podstawowe typy:
    • akcje przed: before, beforeMatched
    • punktów końcowych endpoints : get, post, put, delete, patch
    • akcje po: after, afterMatched
  • Ścieżka:
    • /, /hello-world, /hello/{name}, /user/*
  • Handler: ctx → { … } - lambda z obiektem Context
    • Pobieranie danych z żądania i wysyłanie odpowiedzi przez Context
  • Uchwyty do tych samych scieżek wykonywane w kolejności definicji
config.routes.get("/", ctx -> {
    ctx.result("Hello World!");
});
→ Slide 7

Javalin obsługuje wszystkie standardowe metody HTTP:

  • GET - pobranie danych
  • POST - utworzenie zasobu
  • PUT - aktualizacja zasobu
  • DELETE - usunięcie zasobu
  • PATCH - częściowa aktualizacja
→ Slide 8
Javalin app = Javalin.create(config -> {
 
    // GET - pobierz dane
    config.routes.get("/api/users", ctx -> {
        ctx.result("List of users");
    });
 
    // POST - utwórz zasób
    config.routes.post("/api/users", ctx -> {
        ctx.status(201); // Created
        ctx.result("User created");
    });
 
    // PUT - aktualizuj
    config.routes.put("/api/users/{id}", ctx -> {
        String id = ctx.pathParam("id");
        ctx.result("Updated user " + id);
    });
 
    // DELETE - usuń
    config.routes.delete("/api/users/{id}", ctx -> {
        String id = ctx.pathParam("id");
        ctx.result("Deleted user " + id);
    });
}).start(8080);
→ Slide 9
  • Trasy są przetwarzane w kolejności zdefiniowania
  • Bardziej konkretne trasy powinny być zdefiniowane przed ogólnymi
  • Wildcard * dopasowuje dowolny tekst
Javalin app = Javalin.create(config -> {
    config.routes.get("/admin/users", ctx -> ctx.result("Admin users")); // Konkretna
    config.routes.get("/admin/*", ctx -> ctx.result("Admin section"));   // Ogólna
    config.routes.get("/*", ctx -> ctx.result("Everything else"));       // Najogólniejsza
});
→ Slide 10
  • Context to centralny obiekt w Javalin reprezentujący:
    • Żądanie HTTP od klienta (request)
    • Odpowiedź HTTP dla klienta (response)
  • Każdy handler otrzymuje obiekt Context jako argument i poprzez niego:
    • Pobiera dane z żądania
    • Wysyła dane w odpowiedzi
    • Handler nic nie zwraca, wszystko idzie przez Context
Javalin app = Javalin.create(config -> {
    config.routes.get("/example", ctx -> {
 
        String query = ctx.queryParam("q");
        String path = ctx.path();
 
        ctx.status(200);
        ctx.result("Response, query=" + query + ", path=" + path);
    });
});
→ Slide 11
  • path() - ścieżka żądania
  • method() - metoda HTTP
  • pathParam(key) - parametr z ścieżki (np. /users/{id})
  • queryParam(key) - parametr z query string (/search?q=java)
  • body() - ciało żądania
  • result(String) - wysłanie odpowiedzi
  • json(Object) - wysłanie JSON
  • status(int) - kod statusu HTTP
→ Slide 12
Javalin app = Javalin.create(config -> {
 
    // Definicja: /users/{id}/posts/{postId}
    config.routes.get("/users/{id}/posts/{postId}", ctx -> {
 
        String userId = ctx.pathParam("id");
 
        // Z walidacją typu
        int postId = ctx.pathParamAsClass("postId", Integer.class).get();
 
        ctx.result("User: " + userId + ", Post: " + postId);
    });
}).start(8080);
 
// GET /users/123/posts/456  -> User: 123, Post: 456
// GET /users/123/posts/abc -> "Serwer error" (NumberFormatException)
→ Slide 13

Parametry po znaku ? w URL:

Javalin app = Javalin.create(config -> {
    config.routes.get("/search", ctx -> {
 
        String query = ctx.queryParam("q");            
        int limitInt = ctx.queryParamAsClass("limit", Integer.class).getOrDefault(42); 
 
        ctx.result("Szukam: " + query + " (limit: " + limitInt + ")");
    });
});
 
// GET /search?q=java&limit=5
// Odpowiedź: Szukam: java (limit: 5)

Wiele wartości tego samego parametru:

// GET /filter?tag=java&tag=web&tag=api
List<String> tags = ctx.queryParams("tag");
// tags = ["java", "web", "api"]
→ Slide 14

Dane przesłane w ciele żądania (zazwyczaj POST/PUT):

Javalin app = Javalin.create(config -> {
 
    config.routes.post("/echo", ctx -> {
        String body = ctx.body();
        ctx.result("Echo: " + body);
    });
 
});
 
// CMD: curl -X POST http://localhost:8080/echo -d "hello"
// POST /echo
// Body: hello
// Odpowiedź: Echo: hello
→ Slide 15
Javalin app = Javalin.create(config -> {
    config.routes.get("/headers", ctx -> {
 
        String userAgent = ctx.header("User-Agent");
        String contentType = ctx.header("Content-Type");
 
        ctx.result("User-Agent: " + userAgent);
 
        Map<String,String> headers = ctx.headerMap();
    });
});
→ Slide 16
Javalin app = Javalin.create(config -> {
    config.routes.post("/create", ctx -> {
        // Utworzenie zasobu
        ctx.status(201); // Created
        ctx.result("Zasób utworzony");
    });
 
    config.routes.get("/notfound", ctx -> {
        ctx.status(404); // Not Found
        ctx.result("Nie znaleziono");
    });
});
 
// Powszechne kody:
// 200 - OK (GET, POST, PUT zwrócił dane)
// 201 - Created (POST/PUT utworzył zasób)
// 204 - No Content (DELETE, operacja OK bez zawartości)
// 400 - Bad Request (błąd w żądaniu klienta)
// 401 - Unauthorized (brak autoryzacji)
// 404 - Not Found (nie znaleziono)
// 500 - Internal Server Error (błąd serwera)
→ Slide 17
// Record - idealny do przesyłania JSON
record User(int id, String name, String email) {}
 
Javalin app = Javalin.create(config -> {
    config.routes.get("/user", ctx -> {
        User user = new User(1, "Alice", "alice@example.com");
        ctx.json(user); // Automatyczna konwersja do JSON
    });
});
 
// Odpowiedź: {"id":1,"name":"Alice","email":"alice@example.com"}
→ Slide 18

Rekordy to specjalne klasy wprowadzone w Javie 16, idealne do przechowywania danych:

  • Automatycznie generują konstruktor, gettery, equals(), hashCode() i toString()
  • niemutowalne (immutable) - wartości nie mogą być zmieniane po utworzeniu
record Product(int id, String name, double price) {}
 
Product p = new Product(1, "Laptop", 999.99);
System.out.println(p.name()); 
→ Slide 19

Rekordy to idealne do przesyłania danych w JSON:

record User(int id, String name, String email) {}
record Product(int id, String title, double price) {}
 
Javalin app = Javalin.create(config -> {
    // Wysłanie
    config.routes.get("/user", ctx -> {
        User user = new User(1, "Alice", "alice@example.com");
        ctx.json(user); // Konwersja Record -> JSON
    });
 
    // Otrzymanie
    config.routes.post("/user", ctx -> {
        User user = ctx.bodyAsClass(User.class); // Konwersja JSON -> Record
        System.out.println("User: " + user.name());
        ctx.json(user);
    });
});
→ Slide 20
config.routes.exception(ValidationException.class, (e, ctx) -> {
    ctx.status(400);
    ctx.result("Liczba musi byc liczba calkowita\n" + e.getMessage());
 
});
 
config.routes.exception(RuntimeException.class, (e, ctx) -> {
    ctx.status(500);
    ctx.result("Blad serwera: " + e.getMessage());
});
 
config.routes.get("/boom", ctx -> {
    throw new RuntimeException("DEMO: kontrolowany wyjątek");
});
→ Slide 21

Obsługa błędów HTTP na poziomie aplikacji:

// Wszystkie 404
config.routes.error(404, ctx -> {
    ctx.result("Strona nie znaleziona");
});
 
// Wszystkie 500
config.routes.error(500, ctx -> {
    ctx.result("Błąd serwera");
});
→ Slide 22
Javalin app = Javalin.create(config -> {
    config.staticFiles.add("/public");  // Folder z plikami
});
 
app.start(8080);
 
// Pliki z src/main/resources/public/:
// - /index.html → http://localhost:8080/index.html
// - /css/style.css → http://localhost:8080/css/style.css
// - /js/app.js → http://localhost:8080/js/app.js
→ Slide 23
src/main/resources/
└── public/
    ├── index.html
    ├── css/
    │   └── style.css
    └── js/
        └── app.js
→ Slide 24
<!DOCTYPE html>
<html>
<head>
    <title>Moja aplikacja</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <h1>Witaj w Javalin</h1>
    <form id="userForm">
        <input type="text" id="name" placeholder="Imię">
        <button type="submit">Wyślij</button>
    </form>
    <div id="result"></div>
    <script src="/js/app.js"></script>
</body>
</html>

Ćwiczenie: Przykład formularza HTML z Javalin

→ Slide 25

Wykonywane przed obsługą żądania

// Globalny middleware
Javalin app = Javalin.create(config -> {
 
    config.routes.before(ctx -> {
        System.out.println(">>> " + ctx.method() + " " + ctx.path());
        // Logging, walidacja, autoryzacja
    });
 
    // Przed dopasowaniem trasy
    config.routes.beforeMatched("/api/*", ctx -> {
        String token = ctx.header("Authorization");
        if (token == null) {
            ctx.status(401).result("Brak tokenu");
        }
    });
});
→ Slide 26

Wykonywane po obsłudze żądania:

Javalin app = Javalin.create(config -> {
   config.routes.after(ctx -> {
        // run after all requests
    });
 
    config.routes.after("/path/*", ctx -> {
        // runs after request to /path/*
    });
 
    config.routes.afterMatched(
       // runs after all matched requests (including static files)
    );
});
→ Slide 27

REST API powinno być zorientowane na zasoby (rzeczy), nie na akcje (czasowniki):

  • Złe: /api/getUser, /api/createProduct
  • Dobre: /api/users, /api/products
→ Slide 28
Metoda Akcja Przykład
GET Pobranie GET /api/users (pobranie listy)
POST Utworzenie POST /api/users (utwórz nowego)
PUT Aktualizacja PUT /api/users/1 (aktualizuj)
DELETE Usunięcie DELETE /api/users/1 (usuń)
→ Slide 29
config.routes.apiBuilder(() -> {
    path("/users", () -> {
        get(UserController::getAllUsers);
        post(UserController::createUser);
        path("/{id}", () -> {
            get(UserController::getUser);
 
        });
        ws("/events", UserController::webSocketEvents);
    });
});
→ Slide 30

Interfejs RouteRole do definiowania ról użytkowników:

  enum Role implements RouteRole { ANYONE, USER_READ, USER_WRITE }

Przypisanie ról do endpointów:

config.routes.get("/public", ctx -> ctx.result("Hello public"), Role.ANYONE);
config.routes.get("/private", ctx -> ctx.result("Hello private"), Role.USER_READ);

Pobieranie ról z kontekstu:

config.routes.before("/private/*", ctx -> {
    Set<RouteRole> roles = ctx.roles();
    if (!roles.contains(Role.USER_READ)) {
        ctx.status(403).result("Forbidden");
    }
});
→ Slide 31

Materiały