Spis treści

Javalin - Ćwiczenia demonstracyjne

Wstęp

Poniżej znajduje się zestaw ćwiczeń demonstracyjnych (hands-on), które obrazują praktyczne zastosowanie zagadnień z kursu Javalin. Każde ćwiczenie zawiera:


Ćwiczenie 1: Twój pierwszy serwer Javalin

Cel

Uruchomić prosty serwer HTTP, który odpowiada „Hello” na żądanie GET do /

Wskazówki

1. Utwórz nowy projekt Maven w IntelliJ
2. Dodaj ''javalin-bundle'' do ''pom.xml''
3. Utwórz klasę ''HelloApp'' z metodą ''main''
4. Następnie uruchom projekt i otwórz przeglądarkę na ''http://localhost:8080''

Rozwiązanie

import io.javalin.Javalin;
 
public class HelloApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.routes.get("/", ctx -> {
                ctx.result("Hello!");
            });
        });
 
        app.start(8080);
        System.out.println("Serwer uruchomiony na http://localhost:8080");
    }
}

Eksperymenty


Ćwiczenie 2: Parametry w ścieżce (Path Parameters)

Cel

Stworzyć serwer, który powituje użytkownika po imieniu. Przykład: /greet/Alice → „Hello, Alice!”

Wskazówki

  1. Użyj parametru w definicji trasy: ''/greet/{name}''
2. Pobierz parametr za pomocą ''ctx.pathParam("name")''
3. Zwróć spersonalizowaną wiadomość

Rozwiązanie

import io.javalin.Javalin;
 
public class GreetApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.routes.get("/greet/{name}", ctx -> {
                String name = ctx.pathParam("name");
                ctx.result("Hello, " + name + "!");
            });
        });
 
        app.start(8080);
        System.out.println("Spróbuj: http://localhost:8080/greet/Alice");
    }
}

Eksperymenty


Ćwiczenie 3: Query Parameters

Cel

Stworzyć wyszukiwanie. Użytkownik wpisuje zapytanie: /search?q=java&limit=5 a serwer zwraca wyniki.

Wskazówki

1. Użyj ''ctx.queryParam("q")'' do poboru parametru
2. Użyj ''ctx.queryParam("limit", "10")'' dla wartości domyślnej
3. Zwróć sformatowaną odpowiedź

Rozwiązanie

import io.javalin.Javalin;
 
public class SearchApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.routes.get("/search", ctx -> {
                String query = ctx.queryParam("q", "");
                String limit = ctx.queryParam("limit", "10");
 
                ctx.result("Szukam: '" + query + "' (limit: " + limit + ")");
            });
        });
 
        app.start(8080);
        System.out.println("Spróbuj: http://localhost:8080/search?q=java&limit=5");
    }
}

Eksperymenty


Ćwiczenie 4: POST i ciało żądania (Body)

Cel

Utwórz endpoint POST, który przyjmuje JSON z danymi użytkownika i zwraca je potwierdzenie.

Wskazówki

1. ZdefiniujRecord z danymi użytkownika
  2. Użyj ''config.routes.post()'' zamiast ''config.routes.get()''
3. Pobierz JSON za pomocą ''ctx.bodyAsClass(User.class)''
4. Ustaw status 201 (Created) i zwróć JSON odpowiedzi

Rozwiązanie

import io.javalin.Javalin;
 
record User(String name, String email) {}
 
public class UserApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.routes.post("/users", ctx -> {
                User user = ctx.bodyAsClass(User.class);
 
                // Symulacja zapisania w bazie
                ctx.status(201);
                ctx.result("Użytkownik " + user.name() + " został utworzony!");
            });
        });
 
        app.start(8080);
        System.out.println("Spróbuj: POST http://localhost:8080/users");
        System.out.println("Body: {\"name\": \"Alice\", \"email\": \"alice@example.com\"}");
    }
}

Eksperymenty


Ćwiczenie 5: Kody statusów HTTP i obsługa błędów

Cel

Stworzyć endpoint GET, który zwraca 404 jeśli użytkownik nie istnieje, 200 jeśli istnieje.

Wskazówki

1. Symuluj bazę użytkowników (Map)
2. Sprawdź czy użytkownik istnieje
3. Zwróć odpowiedni status i wiadomość

Rozwiązanie

import io.javalin.Javalin;
import java.util.Map;
 
public class UserGetApp {
    static Map<Integer, String> users = Map.of(
        1, "Alice",
        2, "Bob"
    );
 
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.routes.get("/users/{id}", ctx -> {
                int id = Integer.parseInt(ctx.pathParam("id"));
 
                if (users.containsKey(id)) {
                    ctx.status(200);
                    ctx.result("Użytkownik: " + users.get(id));
                } else {
                    ctx.status(404);
                    ctx.result("Użytkownik nie znaleziony");
                }
            });
        });
 
        app.start(8080);
    }
}

Eksperymenty


Ćwiczenie 6: JSON - Serializacja i deserializacja

Cel

Stworzyć API, które przesyła i odbiera obiekty w formacie JSON.

Wskazówki

1. Zdefiniuj Recordy z danymi
2. Użyj ''ctx.json(object)'' do wysłania
3. Użyj ''ctx.bodyAsClass(Class.class)'' do odboru

Rozwiązanie

import io.javalin.Javalin;
import java.util.*;
 
record Product(int id, String name, double price) {}
 
public class ProductApp {
    static List<Product> products = new ArrayList<>(Arrays.asList(
        new Product(1, "Laptop", 999.99),
        new Product(2, "Mouse", 29.99)
    ));
 
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            // GET - zwróć listę produktów
            config.routes.get("/products", ctx -> {
                ctx.json(products);
            });
 
            // GET - zwróć jeden produkt
            config.routes.get("/products/{id}", ctx -> {
                int id = ctx.pathParamAsClass("id", Integer.class).get();
                Product product = products.stream()
                    .filter(p -> p.id() == id)
                    .findFirst()
                    .orElse(null);
 
                if (product != null) {
                    ctx.json(product);
                } else {
                    ctx.status(404).result("Produkt nie znaleziony");
                }
            });
 
            // POST - utwórz nowy produkt
            config.routes.post("/products", ctx -> {
                Product newProduct = ctx.bodyAsClass(Product.class);
                products.add(newProduct);
                ctx.status(201).json(newProduct);
            });
        });
 
        app.start(8080);
    }
}

Eksperymenty


Ćwiczenie 7: Middleware - Logging

Cel

Dodać middleware, które loguje każde żądanie i odpowiedź.

Wskazówki

  1. Użyj ''config.routes.before()'' do logowania żądania
  2. Użyj ''config.routes.after()'' do logowania odpowiedzi
3. Wyświetl ścieżkę, metodę, status, czas przetwarzania

Rozwiązanie

import io.javalin.Javalin;
 
public class LoggingApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            // Before - loguj żądanie
            config.routes.before(ctx -> {
                long startTime = System.currentTimeMillis();
                ctx.attribute("startTime", startTime);
                System.out.println(">>> " + ctx.method() + " " + ctx.path());
            });
 
            // After - loguj odpowiedź
            config.routes.after(ctx -> {
                long startTime = ctx.attribute("startTime");
                long duration = System.currentTimeMillis() - startTime;
                System.out.println("<<< Status: " + ctx.status() + " (" + duration + "ms)");
            });
 
            config.routes.get("/", ctx -> {
                ctx.result("Hello!");
            });
        });
 
        app.start(8080);
    }
}

Eksperymenty


Ćwiczenie 8: Walidacja - Before Handler

Cel

Dodać walidację, która sprawdza czy żądanie ma wymagany token autoryzacji.

Wskazówki

1. W ''before()'' pobierz nagłówek ''Authorization''
2. Sprawdź czy token istnieje i jest ważny
3. Jeśli nie, zwróć status 401

Rozwiązanie

import io.javalin.Javalin;
 
public class AuthApp {
    static String VALID_TOKEN = "secret123";
 
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            // Walidacja tokenu dla wszystkich endpointów /api
            config.routes.before("/api/*", ctx -> {
                String token = ctx.header("Authorization");
 
                if (token == null || !token.equals("Bearer " + VALID_TOKEN)) {
                    ctx.status(401);
                    ctx.result("Unauthorized - brak ważnego tokenu");
                }
            });
 
            config.routes.get("/api/secret", ctx -> {
                ctx.result("To jest sekretna wiadomość!");
            });
 
            config.routes.get("/public", ctx -> {
                ctx.result("To jest publiczne");
            });
        });
 
        app.start(8080);
    }
}

Eksperymenty


Ćwiczenie 9: Obsługa błędów - Exception Handler

Cel

Obsłużyć wyjątki i zwrócić przyjazne komunikaty błędów.

Wskazówki

1. Utwórz własny wyjątek
  2. Użyj obsługi wyjątków w konfiguracji aplikacji, np. ''config.exception()''
3. Zwróć JSON z opisem błędu i statusem 400 lub 500

Rozwiązanie

import io.javalin.Javalin;
import java.util.Map;
 
class ValidationException extends Exception {
    ValidationException(String message) {
        super(message);
    }
}
 
record User(String name, String email) {}
 
public class ErrorApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.exception(ValidationException.class, (e, ctx) -> {
                ctx.status(400);
                ctx.json(Map.of("error", e.getMessage()));
            });
 
            config.exception(NumberFormatException.class, (e, ctx) -> {
                ctx.status(400);
                ctx.json(Map.of("error", "Liczba musi być liczbą całkowitą"));
            });
 
            config.routes.post("/users", ctx -> {
                User user = ctx.bodyAsClass(User.class);
 
                if (user.name() == null || user.name().isEmpty()) {
                    throw new ValidationException("Nazwa nie może być pusta");
                }
 
                ctx.status(201).json(user);
            });
 
            config.routes.get("/divide/{a}/{b}", ctx -> {
                int a = ctx.pathParamAsClass("a", Integer.class).get();
                int b = ctx.pathParamAsClass("b", Integer.class).get();
                int result = a / b; // Może rzucić ArithmeticException
                ctx.json(Map.of("result", result));
            });
        });
 
        app.start(8080);
    }
}

Eksperymenty


Ćwiczenie 10: Obsługa plików statycznych

Cel

Serwować plik HTML z folderu public.

Wskazówki

1. Utwórz folder ''src/main/resources/public''
2. Utwórz plik ''index.html''
3. W kodzie skonfiguruj: ''config.staticFiles.add("/public")''
4. Otwórz ''http://localhost:8080/index.html''

Struktura projektu

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

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Moja aplikacja</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <h1>Witaj w Javalin!</h1>
    <button id="btn">Kliknij mnie</button>
    <script src="/js/app.js"></script>
</body>
</html>

css/style.css

body {
    font-family: Arial, sans-serif;
    padding: 20px;
}
 
button {
    padding: 10px 20px;
    background-color: blue;
    color: white;
    cursor: pointer;
}

js/app.js

document.getElementById('btn').addEventListener('click', function() {
    alert('Przycisk został kliknięty!');
});

Kod Javy

import io.javalin.Javalin;
 
public class StaticApp {
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            config.staticFiles.add("/public");
        });
 
        app.start(8080);
        System.out.println("Otwórz http://localhost:8080/index.html");
    }
}

Eksperymenty


Ćwiczenie 11: Pełny RESTful CRUD

Cel

Stworzyć kompletne CRUD API dla TODO. Wszystkie operacje: Create, Read, Update, Delete.

Wskazówki

1. Utwórz listę TODO w pamięci
2. GET - zwróć wszystkie lub jedno TODO
3. POST - utwórz nowe TODO
4. PUT - aktualizuj TODO
5. DELETE - usuń TODO

Rozwiązanie

import io.javalin.Javalin;
import java.util.*;
 
record Todo(int id, String title, String description, boolean completed) {}
 
public class TodoCrudApp {
    static Map<Integer, Todo> todos = new HashMap<>();
    static int nextId = 1;
 
    static {
        todos.put(1, new Todo(1, "Nauczyć się Javalin", "Obejrzeć kurs", false));
        todos.put(2, new Todo(2, "Zrobić projekt", "Aplikacja Todo", true));
        nextId = 3;
    }
 
    public static void main(String[] args) {
        Javalin app = Javalin.create(config -> {
            // GET - wszystkie TODO
            config.routes.get("/api/todos", ctx -> {
                ctx.json(new ArrayList<>(todos.values()));
            });
 
            // GET - jedno TODO
            config.routes.get("/api/todos/{id}", ctx -> {
                int id = ctx.pathParamAsClass("id", Integer.class).get();
                Todo todo = todos.get(id);
 
                if (todo != null) {
                    ctx.json(todo);
                } else {
                    ctx.status(404).json(Map.of("error", "TODO nie znaleziono"));
                }
            });
 
            // POST - utwórz TODO
            config.routes.post("/api/todos", ctx -> {
                Map<String, Object> body = ctx.bodyAsClass(Map.class);
                String title = (String) body.get("title");
                String description = (String) body.get("description");
 
                Todo newTodo = new Todo(nextId, title, description, false);
                todos.put(nextId, newTodo);
                nextId++;
 
                ctx.status(201).json(newTodo);
            });
 
            // PUT - aktualizuj TODO
            config.routes.put("/api/todos/{id}", ctx -> {
                int id = ctx.pathParamAsClass("id", Integer.class).get();
                Map<String, Object> body = ctx.bodyAsClass(Map.class);
 
                if (!todos.containsKey(id)) {
                    ctx.status(404).json(Map.of("error", "TODO nie znaleziono"));
                    return;
                }
 
                Todo existing = todos.get(id);
                String newTitle = (String) body.getOrDefault("title", existing.title());
                String newDesc = (String) body.getOrDefault("description", existing.description());
                boolean newCompleted = (boolean) body.getOrDefault("completed", existing.completed());
 
                Todo updated = new Todo(id, newTitle, newDesc, newCompleted);
                todos.put(id, updated);
 
                ctx.json(updated);
            });
 
            // DELETE - usuń TODO
            config.routes.delete("/api/todos/{id}", ctx -> {
                int id = ctx.pathParamAsClass("id", Integer.class).get();
 
                if (todos.containsKey(id)) {
                    todos.remove(id);
                    ctx.status(204);
                } else {
                    ctx.status(404).json(Map.of("error", "TODO nie znaleziono"));
                }
            });
        });
 
        app.start(8080);
        System.out.println("TODO API dostępne na http://localhost:8080/api/todos");
    }
}

Eksperymenty


Podsumowanie

Te ćwiczenia obejmują:

✓ Uruchamianie serwera
✓ Routing i parametry
✓ Obsługę żądań i odpowiedzi
✓ JSON
✓ Statusy HTTP
✓ Middleware
✓ Obsługę błędów
✓ Pliki statyczne
✓ Kompletny CRUD API

Po wykonaniu wszystkich ćwiczeń będziesz gotowy do samodzielnego tworzenia aplikacji w Javalin!