<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 22:31:11 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="9XYXBPMBpvfIXBDCh8ChAGp-aKHaGZL0695T2C8DD8oap9wzAX8Ll34fNFqLz1F3qndFC9IubFZWPsmMfQTopA";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Создание CRUD на Javalin | Java: Веб-технологии</title>
<meta name="description" content="Создание CRUD на Javalin / Java: Веб-технологии: Знакомимся с концепцией CRUD и учимся реализовывать ее от начала до конца">
<link rel="canonical" href="https://ru.hexlet.io/courses/java-web/lessons/crud/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Создание CRUD на Javalin">
<meta property="og:title" content="Java: Веб-технологии">
<meta property="og:description" content="Создание CRUD на Javalin / Java: Веб-технологии: Знакомимся с концепцией CRUD и учимся реализовывать ее от начала до конца">
<meta property="og:url" content="https://ru.hexlet.io/courses/java-web/lessons/crud/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="_kCb_M94ze4jAHUZrSqlxT7qlGuMJaMyYdswZT3_ijMRkVDLPQZgjpVDUYGhJVWy_uO5wYQSXZDcO6oxb_htXQ" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczNSwicHVyIjoiYmxvYl9pZCJ9fQ==--883f3fd4e1b571538035b5680c8d4a9eb504b1f6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Source%20code-amico.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzkyMCwicHVyIjoiYmxvYl9pZCJ9fQ==--71cd9d863b21d7bfbd927cf623a7a2baaf4530ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Bug%20fixing-cuate.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIxOSwicHVyIjoiYmxvYl9pZCJ9fQ==--e79a18f1e9f90eae650f1f4f04115cba51f4e650/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-amico.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T22:31:11.425Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"ck1DWD7c_VULiTmcpAOkSYReX2x88izcyDeVhJ2UJyKdnIhvzKJQNb3KHQSoDFQ-RFdyxnTF0n511w_Qz5PATA","topics":[{"id":98272,"title":"Очепятки в ридми\n\narticles поменять на posts как в пути (dto/posts/PostsPage.java), или наоборот:\n```\n### src/main/java/exercise/dto/articles/PostsPage.java\n```\n\n\nпоменять путь файла на существующий (PostsController) или убрать лишнюю s в самом файле:\n```\n### src/main/java/exercise/controller/PostController.java\n```\n","plain_title":"Очепятки в ридми articles поменять на posts как в пути (dto/posts/PostsPage.java), или наоборот: ``` src/main/java/exercise/dto/articles/PostsPage.java поменять путь файла на существующий (PostsController) или убрать лишнюю s в самом файле: src/main/java/exercise/controller/PostController.java ","creator":{"public_name":"Алекс Гамбург","id":418830,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":189469,"body":"Поправил, спасибо!","topic_id":98272}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}},{"id":97125,"title":"Вопрос по уроку - в примерах маршрутов в уроке: \n\nМетод \tМаршрут \tШаблон \tОписание\nGET \t/users \tusers/index.jte \tСписок пользователей\nGET \t/users/{id} \tusers/show.jte \tПрофиль пользователя\nGET \t/users/build \tusers/build.jte \tФорма создания нового пользователя\nPOST \t/users \t\tСоздание нового пользователя\nGET \t/users/{id}/edit \tusers/edit.jte \tФорма редактирования пользователя\n**PATCH/PUT \t/users/{id} \t\tОбновление пользователя\nDELETE \t/users/{id} \t\tУдаление пользователя**\n\nниже в примере для проекта hexlet-javalin немного по-другому:\napp.get(\"/users\", UsersController::index);\napp.get(\"/users/{id}\", UsersController::show);\napp.get(\"/users/build\", UsersController::build);\napp.post(\"/users\", UsersController::create);\napp.get(\"/users/{id}/edit\", UsersController::edit);\n**app.patch(\"/users/{id}\", UsersController::update);\napp.delete(\"/users\", UsersController::destroy);**\n\nКакие маршруты правильные и как быть с тем, что в HTTP только 2 метода - GET и POST ?\nПолучается app.patch(\"/users/{id}\", UsersController::update); можно заменить на app.post(\"/users/{id}\", UsersController::update);, а с delete так не получится - будет повторяющийся маршрут ","plain_title":"Вопрос по уроку - в примерах маршрутов в уроке: Метод Маршрут Шаблон Описание GET /users users/index.jte Список пользователей GET /users/{id} users/show.jte Профиль пользователя GET /users/build users/build.jte Форма создания нового пользователя POST /users Создание нового пользователя GET /users/{id}/edit users/edit.jte Форма редактирования пользователя PATCH/PUT /users/{id} Обновление пользователя DELETE /users/{id} Удаление пользователя ниже в примере для проекта hexlet-javalin немного по-другому: app.get(\"/users\", UsersController::index); app.get(\"/users/{id}\", UsersController::show); app.get(\"/users/build\", UsersController::build); app.post(\"/users\", UsersController::create); app.get(\"/users/{id}/edit\", UsersController::edit); app.patch(\"/users/{id}\", UsersController::update); app.delete(\"/users\", UsersController::destroy); Какие маршруты правильные и как быть с тем, что в HTTP только 2 метода - GET и POST ? Получается app.patch(\"/users/{id}\", UsersController::update); можно заменить на app.post(\"/users/{id}\", UsersController::update);, а с delete так не получится - будет повторяющийся маршрут ","creator":{"public_name":"Александр Музалёв","id":668688,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":188109,"body":"Приветствую, Алексвндр! Там опечатка закралась, в маршруте на удадение должен быть id пользователя, как часть пути. Иначе как узнать, какого пользователя мы хотим удалить. Поправил, спасибо.\n\nВ HTTP не два метода, их гораздо больше https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods. Методы PATCH и DELETE присутствуют в том числе, так что никаких дублирующихся маршрутов тут не будет","topic_id":97125}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}},{"id":98338,"title":"Заголовок ДЗ поправьте пжста: \"Валидация\" вместо CRUD. \nИ в Тексте задания лучше конкретизировать, что не нужно посты сортировать, а то я сначала сделал сортировку по id и не понял почему тесты не проходят.","plain_title":"Заголовок ДЗ поправьте пжста: \"Валидация\" вместо CRUD. И в Тексте задания лучше конкретизировать, что не нужно посты сортировать, а то я сначала сделал сортировку по id и не понял почему тесты не проходят. ","creator":{"public_name":"Dmitry Sharypov","id":174965,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":189540,"body":"Приветствую, Дмитрий! Проправил, спасибо. Дописал в задании про порядок","topic_id":98338}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}},{"id":99961,"title":"Почему в домашнем задании есть требование по реализации пагинации, но теории по пагинации нет?","plain_title":"Почему в домашнем задании есть требование по реализации пагинации, но теории по пагинации нет? ","creator":{"public_name":"Никита Кудрявцев","id":449403,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":191549,"body":"Приветствую, Никита! По сути в этой концепции нет ничего не знакомого, все нужное для ее реализации мы уже знаем. Это просто получение среза коллекции постов. Я сейчас перенес получение постов с учетом пагинации из контроллера в репозиторий, теперь метод уже реализован. Единственный необычный момент тут - формирование ссылок на переключение станиц в шаблонах. Я добавил подказку, которая направит вас тут в нужном направлении. Должно стать полегче. Нужно будет перекачать домашку командой reset, чтобы получить новую версию","topic_id":99961},{"creator":{"public_name":"Никита Кудрявцев","id":449403,"is_tutor":false},"id":191570,"body":"Отлично. Спасибо огромное!","topic_id":99961}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}},{"id":100655,"title":"Как из урока сделать и запустить не получится.\n\nВ контроллере метод build\n public static void build(Context ctx) {\n ctx.render(\"courses/build.jte\");\n }\n\nА после прошлого урока он выглядит как \n public static void build(Context ctx) {\n var page = new BuildCoursePage();\n ctx.render(\"courses/build.jte\", model(\"page\", page));\n }\n\nБез page всё падает в ошибку.","plain_title":"Как из урока сделать и запустить не получится. В контроллере метод build public static void build(Context ctx) { ctx.render(\"courses/build.jte\"); } А после прошлого урока он выглядит как public static void build(Context ctx) { var page = new BuildCoursePage(); ctx.render(\"courses/build.jte\", model(\"page\", page)); } Без page всё падает в ошибку. ","creator":{"public_name":"Александр","id":659898,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":192373,"body":"Приветсвую, Александр! Как там в уроке написано, это упрощенный вариант контроллера. В нем только самый необходимый минимум, чтобы увидеть суть CRUD сущности, не отвлекаясь на детали. Этот контроллер не учитывает валидацию, посмотрите на методы create и update. Поэтому метод build только рендерит форму создания нового пользователя. Ну а дальше уже, поняв саму суть CRUD, вы можете накрутить контроллер всеми необходимыми опциями, валидацие и так далее","topic_id":100655}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}},{"id":103481,"title":"любопытное наблюдение из самостоятельного урока, надо поменять последовательность строк:\nсначала выводим app.get(NamedRoutes.buildUserPath(), UsersController::build);\nа уже потом app.get(\"/users/{id}\", UsersController::show);\nесли оставить как в уроке показано, то при попытке создания нового пользователя выкидывает ошибку.","plain_title":"любопытное наблюдение из самостоятельного урока, надо поменять последовательность строк: сначала выводим app.get(NamedRoutes.buildUserPath(), UsersController::build); а уже потом app.get(\"/users/{id}\", UsersController::show); если оставить как в уроке показано, то при попытке создания нового пользователя выкидывает ошибку. ","creator":{"public_name":"Артур Неустроев","id":787585,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":195983,"body":"**Артур Неустроев**, здравствуйте. Спасибо за замечание! Поправил. Мы об этом рассказываем [в этом уроке](https://ru.hexlet.io/courses/java-web/lessons/dynamic-routes/theory_unit).","topic_id":103481}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}},{"id":107368,"title":"Здравствуйте, подскажите, пожалуйста, как в шаблоне задать атрибут page в ссылке (чтобы реализовать перелистывание страниц)?\nЯ нашёл подходящий метод ctx.attribute(\"ключ\", значение), но не могу сообразить, как передать его из шаблона.\nЯ полагаю, там будет ссылка href=\"${NamedRoutes.postsPath()}\" (/posts), но как добавить сюда атрибут?","plain_title":"Здравствуйте, подскажите, пожалуйста, как в шаблоне задать атрибут page в ссылке (чтобы реализовать перелистывание страниц)? Я нашёл подходящий метод ctx.attribute(\"ключ\", значение), но не могу сообразить, как передать его из шаблона. Я полагаю, там будет ссылка href=\"${NamedRoutes.postsPath()}\" (/posts), но как добавить сюда атрибут? ","creator":{"public_name":"Михаил Хатьков","id":866940,"is_tutor":false},"comments":[{"creator":{"public_name":"Михаил Хатьков","id":866940,"is_tutor":false},"id":200992,"body":"Добавить к /posts ?page=номер_страницы?","topic_id":107368},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":201013,"body":"Привествую, Михаил! Я вам порекомендую вот этот урок повторить, там есть ответ на ваш вопрос\nhttps://ru.hexlet.io/courses/http_protocol/lessons/query_string/theory_unit\n\nДобавил ссылку на урок в домашнее задание, чтобы можно было освежить в памяти\n\nДа, параметры строки запроса передаются в адресе после знака ? в виде пар ключ=значение\n","topic_id":107368}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Создание CRUD на Javalin","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":4252,"slug":"java_web_crud_exercise","name":null,"state":"active","kind":"exercise","language":"java","locale":"ru","has_web_view":true,"has_test_view":true,"reviewable":true,"readme":"## src/main/java/io/hexlet/controller/PostsController.java\n\nРеализуйте контроллер с двумя обработчиками.\n\nПервый обработчик должен:\n\n* Показывать страницу конкретного поста\n* Отображать на ней данные поста\n* Позволяет вернуться в список всех постов\n\nЕсли поста не существует, то обработчик должен вернуть код ответа 404 и текст *Page not found*.\n\nВторой обработчик должен:\n\n* Показывать страницу со списком всех постов, причем каждый пост из списка должен вести на страницу конкретного поста. Посты выводятся в том порядке, в котором их возвращает метод репозитория\n* Выводить список постов с пейджингом по 5 постов на странице. На первой странице первые пять постов, на второй пять и так далее\n* Позволять переключаться между страницами с помощью двух ссылок — назад и вперед\n\nТо, какая сейчас страница открыта, определяется параметром строки запроса `page`. По умолчанию загружается первая страница.\n\nДля получения сущностей с учетом пагинации в репозитории предусмотрен метод `findAll()`, который принимает первым параметром номер страницы, а вторым - размер страницы. Используйте его для получения постов из репозитория\n\n## src/main/java/io/hexlet/dto/posts/PostsPage.java\n\nРеализуйте дата-класс для передачи списка статей в шаблон.\n\n## src/main/java/io/hexlet/util/NamedRoutes.java\n\nДобавьте методы для именованных маршрутов в класс `NamedRoutes`.\n\n## src/main/java/io/hexlet/App.java\n\nОпишите роутинг для просмотра списка всех постов */posts* и просмотра конкретного поста, например */posts/3*. Используйте именованные маршруты.\n\n## src/main/jte/posts/index.jte\n\nВыведите список добавленных постов. Каждый пост — это имя, представленное ссылкой, которая ведет на отображение.\n\n## src/main/jte/posts/show.jte\n\nРеализуйте вывод информации о конкретном посте.\n\n## Запуск приложения\n\nПри первом запуске приложения, веб-доступ не будет доступен, так как код еще не готов к запуску.\n\nПриложение запустится автоматически, если фоновая компиляция прошла успешно. Посмотреть вывод логов компиляции и приложения можно командой `make logs`.\n\n## Подсказки\n\n* Для формирования в шаблоне ссылок переключения страниц вам потребуется передать в шаблон помимо списка постов еще и номер текущей страницы\n* [Урок про передачу данных в параметрах строки запроса](https://ru.hexlet.io/courses/http_protocol/lessons/query_string/theory_unit)\n","prepared_readme":"## src/main/java/io/hexlet/controller/PostsController.java\n\nРеализуйте контроллер с двумя обработчиками.\n\nПервый обработчик должен:\n\n* Показывать страницу конкретного поста\n* Отображать на ней данные поста\n* Позволяет вернуться в список всех постов\n\nЕсли поста не существует, то обработчик должен вернуть код ответа 404 и текст *Page not found*.\n\nВторой обработчик должен:\n\n* Показывать страницу со списком всех постов, причем каждый пост из списка должен вести на страницу конкретного поста. Посты выводятся в том порядке, в котором их возвращает метод репозитория\n* Выводить список постов с пейджингом по 5 постов на странице. На первой странице первые пять постов, на второй пять и так далее\n* Позволять переключаться между страницами с помощью двух ссылок — назад и вперед\n\nТо, какая сейчас страница открыта, определяется параметром строки запроса `page`. По умолчанию загружается первая страница.\n\nДля получения сущностей с учетом пагинации в репозитории предусмотрен метод `findAll()`, который принимает первым параметром номер страницы, а вторым - размер страницы. Используйте его для получения постов из репозитория\n\n## src/main/java/io/hexlet/dto/posts/PostsPage.java\n\nРеализуйте дата-класс для передачи списка статей в шаблон.\n\n## src/main/java/io/hexlet/util/NamedRoutes.java\n\nДобавьте методы для именованных маршрутов в класс `NamedRoutes`.\n\n## src/main/java/io/hexlet/App.java\n\nОпишите роутинг для просмотра списка всех постов */posts* и просмотра конкретного поста, например */posts/3*. Используйте именованные маршруты.\n\n## src/main/jte/posts/index.jte\n\nВыведите список добавленных постов. Каждый пост — это имя, представленное ссылкой, которая ведет на отображение.\n\n## src/main/jte/posts/show.jte\n\nРеализуйте вывод информации о конкретном посте.\n\n## Запуск приложения\n\nПри первом запуске приложения, веб-доступ не будет доступен, так как код еще не готов к запуску.\n\nПриложение запустится автоматически, если фоновая компиляция прошла успешно. Посмотреть вывод логов компиляции и приложения можно командой `make logs`.\n\n## Подсказки\n\n* Для формирования в шаблоне ссылок переключения страниц вам потребуется передать в шаблон помимо списка постов еще и номер текущей страницы\n* [Урок про передачу данных в параметрах строки запроса](https://ru.hexlet.io/courses/http_protocol/lessons/query_string/theory_unit)\n","has_solution":true,"entity_name":"Создание CRUD на Javalin"},"units":[{"id":9246,"name":"theory","url":"/courses/java-web/lessons/crud/theory_unit"},{"id":9247,"name":"quiz","url":"/courses/java-web/lessons/crud/quiz_unit"},{"id":14481,"name":"exercise","url":"/courses/java-web/lessons/crud/exercise_unit"}],"links":[{"id":422534,"name":"Javalin CRUD Контроллер","url":"https://javalin.io/documentation#crudhandler"}],"ordered_units":[{"id":9246,"name":"theory","url":"/courses/java-web/lessons/crud/theory_unit"},{"id":9247,"name":"quiz","url":"/courses/java-web/lessons/crud/quiz_unit"},{"id":14481,"name":"exercise","url":"/courses/java-web/lessons/crud/exercise_unit"}],"id":4144,"slug":"crud","state":"approved","name":"Создание CRUD на Javalin","course_order":270,"goal":"Знакомимся с концепцией CRUD и учимся реализовывать ее от начала до конца","self_study":"1. Выполните шаги из урока на своем компьютере\n2. Приведите маршруты в соответствие с таблицей в уроке\n3. Выделите обработчики в отдельный контроллер и поправьте роутинг\n4. Сделайте то же самое для сущности курсов\n5. Залейте изменения на GitHub\n","theory_video_provider":null,"theory_video_uid":null,"theory":"Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциям. В этом уроке мы познакомимся с ними подробнее.\n\n## Что такое CRUD\n\nCRUD — это широко распространенный термин, означающий четыре стандартные операции над любой сущностью. Рассмотрим такой пример:\n\n- Создание (*Create*) — регистрация пользователя\n- Чтение (*Read*) — просмотр профиля пользователями сайта или в административном интерфейсе\n- Обновление (*Update*) — Обновление личных данных, смена электронной почты или пароля\n- Удаление (*Delete*) — удаление данных\n\nТочно так же можно расписать действия над любыми другими ресурсами: фотографиями пользователя, его друзьями, сообщениями и так далее. Чтобы создать полный CRUD, нужно выполнить следующие действия:\n\n- Создать сущность в коде (как правило, это класс)\n- Добавить таблицу в базу\n- Написать тесты для проверки обработчиков\n- Добавить обработчики\n- Добавить шаблоны\n\nНиже мы пройдемся по всему процессу создания CRUD пользователя за исключением работы с базой данных и тестов.\n\nНачнем с **роутинга**. Полный CRUD пользователя включает минимум семь маршрутов. Их может быть больше, потому что любое действие может повторяться более одного раза:\n\n| Метод | Маршрут | Шаблон | Описание |\n| --------- | ---------------- | --------------- | ---------------------------------- |\n| GET | /users | users/index.jte | Список пользователей |\n| GET | /users/{id} | users/show.jte | Профиль пользователя |\n| GET | /users/build | users/build.jte | Форма создания нового пользователя |\n| POST | /users | | Создание нового пользователя |\n| GET | /users/{id}/edit | users/edit.jte | Форма редактирования пользователя |\n| PATCH/PUT | /users/{id} | | Обновление пользователя |\n| DELETE | /users/{id} | | Удаление пользователя |\n\nТакое соглашение об именовании маршрутов изначально появилось во фреймворке Ruby On Rails, а затем его адаптировали во многих других. Здесь мы его используем из-за его универсальности и понятности.\n\n## Как работают контроллеры\n\nCRUD-операции объединяют маршруты и их обработчики в логические блоки вокруг каких-то сущностей — например, CRUD курсов, упражнений, уроков, статей в блоге и так далее. Такая структура позволяет разбить приложение на файлы так, чтобы его было удобно поддерживать. В веб-разработке принято объединять обработчики в **контроллеры** – классы, которые содержат в себе обработчики сущностей.\n\nРаботая с Javalin, мы можем добавлять контроллеры как классы, потому что фреймворк по умолчанию умеет работать с ними. Чтобы лучше понять тему, разберем упрощенный пример такого класса управления пользователями:\n\n```java\npackage org.example.hexlet.controller;\n\nimport static io.javalin.rendering.template.TemplateUtil.model;\n\nimport org.example.hexlet.NamedRoutes;\nimport org.example.hexlet.dto.users.UserPage;\nimport org.example.hexlet.dto.users.UsersPage;\nimport org.example.hexlet.model.User;\nimport org.example.hexlet.repository.UserRepository;\n\nimport io.javalin.http.Context;\nimport io.javalin.http.NotFoundResponse;\n\npublic class UsersController {\n public static void index(Context ctx) {\n var users = UserRepository.getEntities();\n var page = new UsersPage(users);\n ctx.render(\"users/index.jte\", model(\"page\", page));\n }\n\n public static void show(Context ctx) {\n var id = ctx.pathParamAsClass(\"id\", Long.class).get();\n var user = UserRepository.find(id)\n .orElseThrow(() -> new NotFoundResponse(\"Entity with id = \" + id + \" not found\"));\n var page = new UserPage(user);\n ctx.render(\"users/show.jte\", model(\"page\", page));\n }\n\n public static void build(Context ctx) {\n ctx.render(\"users/build.jte\");\n }\n\n public static void create(Context ctx) {\n var name = ctx.formParam(\"name\");\n var email = ctx.formParam(\"email\");\n var password = ctx.formParam(\"password\");\n\n var user = new User(name, email, password);\n UserRepository.save(user);\n ctx.redirect(NamedRoutes.usersPath());\n }\n\n public static void edit(Context ctx) {\n var id = ctx.pathParamAsClass(\"id\", Long.class).get();\n var user = UserRepository.find(id)\n .orElseThrow(() -> new NotFoundResponse(\"Entity with id = \" + id + \" not found\"));\n var page = new UserPage(user);\n ctx.render(\"users/edit.jte\", model(\"page\", page));\n }\n\n\n public static void update(Context ctx) {\n var id = ctx.pathParamAsClass(\"id\", Long.class).get();\n\n var name = ctx.formParam(\"name\");\n var email = ctx.formParam(\"email\");\n var password = ctx.formParam(\"password\");\n\n var user = UserRepository.find(id)\n .orElseThrow(() -> new NotFoundResponse(\"Entity with id = \" + id + \" not found\"));\n user.setName(name);\n user.setEmail(email);\n user.setPassword(password);\n UserRepository.save(user);\n ctx.redirect(NamedRoutes.usersPath());\n }\n\n public static void destroy(Context ctx) {\n var id = ctx.pathParamAsClass(\"id\", Long.class).get();\n UserRepository.delete(id);\n ctx.redirect(NamedRoutes.usersPath());\n }\n}\n```\n\nЭтот контроллер не учитывает валидацию, пагинацию и множество других деталей, которыми всегда обрастает реальное приложение. Сейчас нам важнее сфокусироваться на самой концепции.\n\nКак мы выяснили выше, **контроллер** — это класс, в котором каждый обработчик описан статическим методом, принимающим на вход контекст. Полный CRUD включает в себя реализацию семи методов, но все зависит от задачи — иногда могут понадобиться не все методы, иногда приходится добавлять какие-то более специфические обработчики.\n\nТеперь нужно поправить роутинг. Описание роутинга с добавлением контроллеров становится проще для восприятия:\n\n```java\napp.get(\"/users\", UsersController::index);\napp.get(\"/users/build\", UsersController::build);\napp.get(\"/users/{id}\", UsersController::show);\napp.post(\"/users\", UsersController::create);\napp.get(\"/users/{id}/edit\", UsersController::edit);\napp.patch(\"/users/{id}\", UsersController::update);\napp.delete(\"/users/{id}\", UsersController::destroy);\n```\n\nКогда стоит переходить на использование контроллеров? Практически в любой ситуации контроллеры не вносят сложности, но значительно помогают при расширении кода. Они улучшают навигацию по проекту и делают роутинг гораздо более понятным.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":7160,"name":"theory","url":"/courses/java-web/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":7160,"name":"theory","url":"/courses/java-web/lessons/intro/theory_unit"}],"id":3189,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Когда разработчики переходят от написания скриптов к созданию полноценных сайтов, они сталкиваются с новыми понятиями и инструментами. Эти знания выходят далеко за рамки языка, поэтому на этом этапе нужно углублять понимание операционных систем и сетей, осваивать работу с регистраторами и хостингом, отрабатывать навыки деплоя сайта.\n\nНа собеседованиях веб-программисты часто слышат такой вопрос:\n\n> _«Что происходит, когда пользователь набирает сайт google.com в адресной строке браузера и нажимает Enter?»_\n\nОчень подробный ответ на этот вопрос доступен [здесь](https://github.com/alex/what-happens-when). Через этот вопрос интервьюер хочет проверить, насколько хорошо вы разбираетесь в ключевых темах веб-разработки:\n\n1. Как DNS-запрос получает IP-адреса домена\n2. Как работает соединение с веб-сервером на порту 443 или 80 по TCP\n3. Как HTTP-запрос получает содержимого сайта по указанному домену\n4. Как происходит получение ответа и рендеринг содержимого во вкладке браузера\n\nКаждый из этих пунктов неявно подразумевает, что вы знакомы со следующими темами:\n\n* Протоколы HTTP и HTTPS\n* Виртуальные хосты\n* Принципы работы DNS\n* Порты, маски и подсети в TCP/IP\n* Модель OSI\n* Сетевые сокеты\n* Принципы работы веб-серверов\n\nИзучить эти темы можно с помощью наших [гайдов](https://guides.hexlet.io) или книг по операционным системам из нашего [списка рекомендаций](https://ru.hexlet.io/pages/recommended-books). Если говорить о самой разработке, то здесь открывается целый пласт неизведанного:\n\n* Фреймворки\n* Микрофреймворки\n* Роутинг\n* Куки и сессии\n* Безопасность\n* Шаблонизация\n* Взаимодействие с базой данных и многое другое\n\nА еще сайт находится на удаленном сервере, поэтому в разработку включается **деплой** — разворачивание сайта на хостинге. Чтобы развернуть сайт, нужно настроить удаленную машину с помощью Ansible или других похожих инструментов.\n\nКстати, сам хостинг бывает очень разный:\n\n* IaaS (AWS)\n* PaaS (Render)\n* Shared Hosting (виртуальный хостинг)\n* VPS/VDS\n\nВ этом курсе мы не успеем подробно раскрыть все эти важные темы, потому что основное внимание будет уделять веб-разработке с помощью микрофреймворков. Поэтому мы советуем выполнять все домашние задания и параллельно выкатывать код на [Render](https://render.com/) или другой подобный сервис. Так материал усвоится лучше.\n"},"id":241,"slug":"java-web","challenges_count":5,"name":"Java: Веб-технологии","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите веб-технологии в языке Java. Вы узнаете больше об особенностях клиент-серверной модели, принципах роутинга HTTP-запросов и формирования ответов с помощью шаблонизаторов. В итоге вы научитесь создавать собственные веб-приложения, подключать базу данных для хранения содержимого, разделять данные приложения и управляющую логику, используя подход MVC. Вы сможете отслеживать ошибки и процессы, происходящие в рабочем приложении. Знания из этого курса помогут вам создать свой сайт и сделать его доступным в интернете.","kind":"basic","updated_at":"2026-01-20T11:37:44.200Z","language":"java","duration_cache":86340,"skills":["Выполнять HTTP-запросы","Создавать свой собственный сайт и выкладывать его в публичный доступ","Подключать базы данных","Правильно отслеживать ошибки"],"keywords":[],"lessons_count":23,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTc1OSwicHVyIjoiYmxvYl9pZCJ9fQ==--5d65b504f97342a1a3a4df6f6bb44f66fa3601b7/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":3,"slug":"java","title":"Java-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":30,"duration_in_months":10},"id":3,"slug":"java","title":"Java-разработчик","subtitle":"Изучите Java и фреймворк Spring Boot и REST API","subtitle_for_lists":"Изучите Java и фреймворк Spring Boot и REST API","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"java","price_text":"от 6 792 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczNSwicHVyIjoiYmxvYl9pZCJ9fQ==--883f3fd4e1b571538035b5680c8d4a9eb504b1f6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Source%20code-amico.png"},{"stack":{"id":220,"slug":"qa-auto-engineer-python","title":"Автоматизатор тестирования на Python","audience":"for_programmers","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":100,"duration_in_months":6},"id":331,"slug":"qa-auto-engineer-python","title":"Автоматизатор тестирования на Python","subtitle":"Изучите Python, фреймворки для тестирования, автоматизация UI и API","subtitle_for_lists":"Изучите Python, фреймворки для тестирования, автоматизация UI и API","locale":"ru","current":true,"duration_in_months_text":"6 месяцев","stack_slug":"qa-auto-engineer-python","price_text":"от 4 281 ₽","duration_text":"6 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzkyMCwicHVyIjoiYmxvYl9pZCJ9fQ==--71cd9d863b21d7bfbd927cf623a7a2baaf4530ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Bug%20fixing-cuate.png"},{"stack":{"id":467,"slug":"middle-java","title":"Middle-java разработчик","audience":"for_programmers","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":2002,"duration_in_months":6},"id":595,"slug":"middle-java","title":"Middle-java разработчик","subtitle":"Углубите знания Java, Spring и архитектуры серверных приложений","subtitle_for_lists":"Углубите знания Java, Spring и архитектуры серверных приложений","locale":"ru","current":true,"duration_in_months_text":"6 месяцев","stack_slug":"middle-java","price_text":"от 4 050 ₽","duration_text":"6 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIxOSwicHVyIjoiYmxvYl9pZCJ9fQ==--e79a18f1e9f90eae650f1f4f04115cba51f4e650/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-amico.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/java-web/lessons/crud/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Java: Веб-технологии</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Создание CRUD на Javalin</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Создание CRUD на Javalin","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Java: Веб-технологии"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/CRUD" rel="noopener noreferrer" target="_blank">CRUD</a>-операциям. В этом уроке мы познакомимся с ними подробнее.</p>
<h2 id="heading-2-1">Что такое CRUD</h2>
<p>CRUD — это широко распространенный термин, означающий четыре стандартные операции над любой сущностью. Рассмотрим такой пример:</p>
<ul>
<li>Создание (<em>Create</em>) — регистрация пользователя</li>
<li>Чтение (<em>Read</em>) — просмотр профиля пользователями сайта или в административном интерфейсе</li>
<li>Обновление (<em>Update</em>) — Обновление личных данных, смена электронной почты или пароля</li>
<li>Удаление (<em>Delete</em>) — удаление данных</li>
</ul>
<p>Точно так же можно расписать действия над любыми другими ресурсами: фотографиями пользователя, его друзьями, сообщениями и так далее. Чтобы создать полный CRUD, нужно выполнить следующие действия:</p>
<ul>
<li>Создать сущность в коде (как правило, это класс)</li>
<li>Добавить таблицу в базу</li>
<li>Написать тесты для проверки обработчиков</li>
<li>Добавить обработчики</li>
<li>Добавить шаблоны</li>
</ul>
<p>Ниже мы пройдемся по всему процессу создания CRUD пользователя за исключением работы с базой данных и тестов.</p>
<p>Начнем с <strong>роутинга</strong>. Полный CRUD пользователя включает минимум семь маршрутов. Их может быть больше, потому что любое действие может повторяться более одного раза:</p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Шаблон</th><th>Описание</th></tr></thead><tbody><tr><td>GET</td><td>/users</td><td>users/index.jte</td><td>Список пользователей</td></tr><tr><td>GET</td><td>/users/{id}</td><td>users/show.jte</td><td>Профиль пользователя</td></tr><tr><td>GET</td><td>/users/build</td><td>users/build.jte</td><td>Форма создания нового пользователя</td></tr><tr><td>POST</td><td>/users</td><td></td><td>Создание нового пользователя</td></tr><tr><td>GET</td><td>/users/{id}/edit</td><td>users/edit.jte</td><td>Форма редактирования пользователя</td></tr><tr><td>PATCH/PUT</td><td>/users/{id}</td><td></td><td>Обновление пользователя</td></tr><tr><td>DELETE</td><td>/users/{id}</td><td></td><td>Удаление пользователя</td></tr></tbody></table></div></div></div></div>
<p>Такое соглашение об именовании маршрутов изначально появилось во фреймворке Ruby On Rails, а затем его адаптировали во многих других. Здесь мы его используем из-за его универсальности и понятности.</p>
<h2 id="heading-2-2">Как работают контроллеры</h2>
<p>CRUD-операции объединяют маршруты и их обработчики в логические блоки вокруг каких-то сущностей — например, CRUD курсов, упражнений, уроков, статей в блоге и так далее. Такая структура позволяет разбить приложение на файлы так, чтобы его было удобно поддерживать. В веб-разработке принято объединять обработчики в <strong>контроллеры</strong> – классы, которые содержат в себе обработчики сущностей.</p>
<p>Работая с Javalin, мы можем добавлять контроллеры как классы, потому что фреймворк по умолчанию умеет работать с ними. Чтобы лучше понять тему, разберем упрощенный пример такого класса управления пользователями:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">package org.example.hexlet.controller;
import static io.javalin.rendering.template.TemplateUtil.model;
import org.example.hexlet.NamedRoutes;
import org.example.hexlet.dto.users.UserPage;
import org.example.hexlet.dto.users.UsersPage;
import org.example.hexlet.model.User;
import org.example.hexlet.repository.UserRepository;
import io.javalin.http.Context;
import io.javalin.http.NotFoundResponse;
public class UsersController {
public static void index(Context ctx) {
var users = UserRepository.getEntities();
var page = new UsersPage(users);
ctx.render("users/index.jte", model("page", page));
}
public static void show(Context ctx) {
var id = ctx.pathParamAsClass("id", Long.class).get();
var user = UserRepository.find(id)
.orElseThrow(() -> new NotFoundResponse("Entity with id = " + id + " not found"));
var page = new UserPage(user);
ctx.render("users/show.jte", model("page", page));
}
public static void build(Context ctx) {
ctx.render("users/build.jte");
}
public static void create(Context ctx) {
var name = ctx.formParam("name");
var email = ctx.formParam("email");
var password = ctx.formParam("password");
var user = new User(name, email, password);
UserRepository.save(user);
ctx.redirect(NamedRoutes.usersPath());
}
public static void edit(Context ctx) {
var id = ctx.pathParamAsClass("id", Long.class).get();
var user = UserRepository.find(id)
.orElseThrow(() -> new NotFoundResponse("Entity with id = " + id + " not found"));
var page = new UserPage(user);
ctx.render("users/edit.jte", model("page", page));
}
public static void update(Context ctx) {
var id = ctx.pathParamAsClass("id", Long.class).get();
var name = ctx.formParam("name");
var email = ctx.formParam("email");
var password = ctx.formParam("password");
var user = UserRepository.find(id)
.orElseThrow(() -> new NotFoundResponse("Entity with id = " + id + " not found"));
user.setName(name);
user.setEmail(email);
user.setPassword(password);
UserRepository.save(user);
ctx.redirect(NamedRoutes.usersPath());
}
public static void destroy(Context ctx) {
var id = ctx.pathParamAsClass("id", Long.class).get();
UserRepository.delete(id);
ctx.redirect(NamedRoutes.usersPath());
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Этот контроллер не учитывает валидацию, пагинацию и множество других деталей, которыми всегда обрастает реальное приложение. Сейчас нам важнее сфокусироваться на самой концепции.</p>
<p>Как мы выяснили выше, <strong>контроллер</strong> — это класс, в котором каждый обработчик описан статическим методом, принимающим на вход контекст. Полный CRUD включает в себя реализацию семи методов, но все зависит от задачи — иногда могут понадобиться не все методы, иногда приходится добавлять какие-то более специфические обработчики.</p>
<p>Теперь нужно поправить роутинг. Описание роутинга с добавлением контроллеров становится проще для восприятия:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">app.get("/users", UsersController::index);
app.get("/users/build", UsersController::build);
app.get("/users/{id}", UsersController::show);
app.post("/users", UsersController::create);
app.get("/users/{id}/edit", UsersController::edit);
app.patch("/users/{id}", UsersController::update);
app.delete("/users/{id}", UsersController::destroy);</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Когда стоит переходить на использование контроллеров? Практически в любой ситуации контроллеры не вносят сложности, но значительно помогают при расширении кода. Они улучшают навигацию по проекту и делают роутинг гораздо более понятным.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/java?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Java-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Java и фреймворк Spring Boot и REST API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczNSwicHVyIjoiYmxvYl9pZCJ9fQ==--883f3fd4e1b571538035b5680c8d4a9eb504b1f6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Source%20code-amico.png" alt="Java-разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/qa-auto-engineer-python?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">6 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Автоматизатор тестирования на Python</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Python, фреймворки для тестирования, автоматизация UI и API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzkyMCwicHVyIjoiYmxvYl9pZCJ9fQ==--71cd9d863b21d7bfbd927cf623a7a2baaf4530ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Bug%20fixing-cuate.png" alt="Автоматизатор тестирования на Python" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 4 281 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/middle-java?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">6 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Middle-java разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Углубите знания Java, Spring и архитектуры серверных приложений</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIxOSwicHVyIjoiYmxvYl9pZCJ9fQ==--e79a18f1e9f90eae650f1f4f04115cba51f4e650/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-amico.png" alt="Middle-java разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 4 050 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/java-web/lessons/crud/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 23</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/java-web/lessons/crud/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>