====== 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: * **Cel** - co chcemy osiągnąć * **Wskazówki** - jak to zrobić * **Kod** - kompletne rozwiązanie * **Eksperymenty** - jak eksperymentować dalej --- ===== Ć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 ==== * Zmień port z 8080 na 3000, 9000, czy inny * Dodaj więcej tras: ''/hello'', ''/world'' * Zmień odpowiedź w zależności od trasy --- ===== Ć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 ==== * Dodaj dwa parametry: ''/greet/{firstName}/{lastName}'' * Dodaj konwersję na liczby: ''/multiply/{a}/{b}'' i zwróć iloczyn * Obsłuż przypadek gdy parametr jest pusty (np. ''/greet/''): ''config.routes.get("/greet/{name?}", ...)'' --- ===== Ć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 ==== * Dodaj walidację - jeśli ''q'' jest puste, zwróć błąd 400 * Obsługuj wiele tagów: ''/search?tag=java&tag=web'' * Symuluj wyszukiwanie w bazie danych (na razie statyczne wyniki) --- ===== Ć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 ==== * W Postman, utwórz POST request z JSON body * Zwróć utworzony użytkownik jako JSON odpowiedź: ''ctx.json(user)'' * Dodaj walidację - jeśli nazwa jest pusta, zwróć błąd 400 --- ===== Ć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 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 ==== * Zwróć JSON zamiast tekstu * Obsługuj wyjątek NumberFormatException i zwróć 400 * Dodaj zwracanie 204 No Content dla DELETE --- ===== Ć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 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 ==== * Dodaj PUT dla aktualizacji * Dodaj DELETE dla usunięcia * Zwróć listę pól w odpowiedzi na POST --- ===== Ć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 ==== * Zmień format logowania (JSON, CSV) * Loguj tylko określone ścieżki: ''config.routes.before("/api/*", ...)'' * Dodaj logowanie nagłówków lub body --- ===== Ć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 ==== * Zmień format tokenu (JWT, Base64) * Loguj nieudane próby autoryzacji * Zwróć JSON zamiast tekstu w przypadku błędu --- ===== Ć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 ==== * Dodaj obsługę dla ArithmeticException (dzielenie przez zero) * Dodaj ogólny exception handler dla wszystkich błędów * Loguj błędy na konsoli --- ===== Ć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 ==== Moja aplikacja

Witaj w Javalin!

==== 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 ==== * Dodaj więcej plików CSS i JavaScript * Serwuj obrazy z folderu ''images'' * Połącz API z frontend: w ''app.js'' pobierz dane z backendu --- ===== Ć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 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 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 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 ==== * Obsłużyć błędy bardziej elegancko * Dodać walidację (tytuł nie może być pusty) * Przetestować wszystkie operacje w Postmanie --- ===== 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!