→ Slide 1

Javalin - Lekki Framework Webowy dla Javy

→ Slide 2

Co to jest Javalin?

→ Slide 3

Dodanie Javalin do projektu Maven

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

Zawartość i zależności javalin-bundle

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

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

Uruchomienie serwera

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

Uchwyty

config.routes.get("/", ctx -> {
    ctx.result("Hello World!");
});
→ Slide 7

Routing: Metody HTTP

Javalin obsługuje wszystkie standardowe metody HTTP:

→ Slide 8

Składnia tras

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

Priorytety tras

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

Obiekt 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

Kluczowe metody Context

→ Slide 12

Parametry w ścieżce

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

Query Parameters

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

Body - Ciało żądania

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

Nagłówki żądania

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

Kody statusu HTTP

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

Wysłanie JSON

// 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 w Java 16+

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

record Product(int id, String name, double price) {}
 
Product p = new Product(1, "Laptop", 999.99);
System.out.println(p.name()); 
→ Slide 19

Serialiacja i deserializacja JSON

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

Przechwytywanie wyjątków

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

Error Handlers

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

Serwowanie HTML/CSS/JavaScript

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

Struktura projektu

src/main/resources/
└── public/
    ├── index.html
    ├── css/
    │   └── style.css
    └── js/
        └── app.js
→ Slide 24

Przykład index.html

<!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

Before Handler

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

After Handler

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

Resource-Oriented Design

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

→ Slide 28

Prawidłowe użycie metod HTTP

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

apiBuilder

config.routes.apiBuilder(() -> {
    path("/users", () -> {
        get(UserController::getAllUsers);
        post(UserController::createUser);
        path("/{id}", () -> {
            get(UserController::getUser);
 
        });
        ws("/events", UserController::webSocketEvents);
    });
});
→ Slide 30

Role

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