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