config.routes podczas Javalin.create(…)
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>
javalin-bundle to „all-in-one” pakiet zawierający:
javalin-core - główny frameworkjetty-server - serwer HTTPjetty-servlet - obsługa servletówjackson - serializacja JSONslf4j-api - API loggowaniaZaleta: Nie musisz ręcznie dodawać zależności, wszystko jest już skonfigurowane i kompatybilne
Inne moduły: javalin-rendering-*, javalin-ssl, javalin-micrometer
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); } }
before, beforeMatchedendpoints : get, post, put, delete, patchafter, afterMatched /, /hello-world, /hello/{name}, /user/*ctx → { … } - lambda z obiektem ContextContextconfig.routes.get("/", ctx -> { ctx.result("Hello World!"); });
Javalin obsługuje wszystkie standardowe metody HTTP:
GET - pobranie danychPOST - utworzenie zasobuPUT - aktualizacja zasobuDELETE - usunięcie zasobuPATCH - częściowa aktualizacjaJavalin 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);
* dopasowuje dowolny tekstJavalin 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 });
Context to centralny obiekt w Javalin reprezentujący:Context jako argument i poprzez niego:ContextJavalin 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); }); });
path() - ścieżka żądaniamethod() - metoda HTTPpathParam(key) - parametr z ścieżki (np. /users/{id})queryParam(key) - parametr z query string (/search?q=java)body() - ciało żądaniaresult(String) - wysłanie odpowiedzijson(Object) - wysłanie JSONstatus(int) - kod statusu HTTPJavalin 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)
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"]
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
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(); }); });
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)
// 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"}
Rekordy to specjalne klasy wprowadzone w Javie 16, idealne do przechowywania danych:
equals(), hashCode() i toString()record Product(int id, String name, double price) {} Product p = new Product(1, "Laptop", 999.99); System.out.println(p.name());
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); }); });
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"); });
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"); });
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
src/main/resources/
└── public/
├── index.html
├── css/
│ └── style.css
└── js/
└── app.js
<!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
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"); } }); });
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) ); });
REST API powinno być zorientowane na zasoby (rzeczy), nie na akcje (czasowniki):
/api/getUser, /api/createProduct/api/users, /api/products| 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ń) |
config.routes.apiBuilder(() -> { path("/users", () -> { get(UserController::getAllUsers); post(UserController::createUser); path("/{id}", () -> { get(UserController::getUser); }); ws("/events", UserController::webSocketEvents); }); });
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"); } });