Представим, что мы разрабатываем видео-платформу, и нужно реализовать страницу со списком доступных фильмов. Страницы для браузера приходят с веб-приложения на Go. В этом случае нужно понять, как организовать хранение контента страниц, и как в них вставлять изменяющиеся данные, например, список фильмов.
В этом уроке мы рассмотрим, как работать с шаблонами HTML-страниц в Go-приложениях. Мы научимся хранить, подставлять динамические значения и возвращать в браузер готовые HTML-страницы.
Формирование HTML-страниц в веб-приложении
HTML — это язык разметки страниц для веб-браузеров, код которого состоит из тегов. Они обозначают различный вид контента, например: заголовки, блоки, абзацы. Благодаря HTML разработчики могут строить различные интерфейсы веб-сайтов для пользователей.
Когда пользователь заходит на веб-сайт, происходят следующие действия до отображения контента в веб-браузере:
- Веб-браузер отправляет GET HTTP-запрос по адресу веб-сайта
- Веб-приложение, ответственное за обработку запросов на этом адресе, формирует HTML-код и отправляет его в теле HTTP-ответа
- Веб-браузер получает HTML-код, анализирует и отображает его
HTML-страницы бывают двух типов:
Статические HTML-страницы не меняются от запроса к запросу. Например, страница с информацией о компании. Такие страницы формируются один раз и хранятся на сервере. Когда пользователь заходит на сайт, веб-приложение отправляет ему HTML-код страницы без внесения изменений.
Динамические HTML-страницы имеют изменяющийся контент. Например, страница с фильмами в видео-платформе зависит от фильмов, которые хранятся в хранилище этого веб-приложения. Когда пользователь заходит на сайт, веб-приложение берет шаблон HTML-страницы, подставляет актуальный список фильмов и отправляет HTML-код пользователю. Таким образом, конечный HTML-код постоянно меняется в зависимости от количества фильмов в хранилище.
Работа с шаблонами и динамическим контентом — это распространенная задача в веб-разработке, поэтому существуют специальные инструменты для работы с ними. Такие инструменты называют шаблонизаторами. Рассмотрим подробнее, как выглядит работа шаблонизатора с шаблонами HTML-страниц.
Как работают шаблонизаторы
В шаблонах хранится HTML-код с местами для вставки динамического контента. Когда веб-приложению нужно отправить HTML-страницу пользователю, оно берет шаблон, заполняет динамический контент и отправляет пользователю.
Представим, что мы зашли на веб-страницу своего аккаунта в видео-платформе. Шаблон этой страницы выглядит следующим образом:
На странице есть специальные места для вставки динамического контента. Такие места выделены двойными фигурными скобками. Например, {{.name}} означает место для вставки имени пользователя. Это специальный синтаксис для шаблонизатора в Go. Перед тем как отправить такой шаблон пользователю, веб-приложение заменит {{.name}} на имя текущего пользователя.
Например, если наше имя — John, а почта — john@doe.com, то после обработки веб-приложение вернет следующий HTML-код:
Использование шаблонизатора дает следующие преимущества:
- Мы можем использовать один и тот же шаблон для разного контента. Например, мы можем использовать один и тот же шаблон страницы профиля, но вставлять в него разные данные пользователей
- Нам не нужно реализовывать подстановку динамического контента в HTML-код. Мы просто указываем что и где должно быть, а остальное берет на себя шаблонизатор
- Логика веб-приложения, в частности работа с базой данных, не смешивается с логикой отображения. В шаблонах описывается только HTML-код, а заполняются динамические данные через передачу данных в шаблонизатор
Мы рассмотрели работу шаблонизаторов и теперь можем перейти к работе с HTML в Go Fiber.
Работа с HTML в Go Fiber
Начнем с реализации веб-страницы личного аккаунта. У нас есть шаблон веб-страницы. Когда пользователь запрашивает ее, Go веб-приложение подставляет информацию текущего пользователя в шаблон и возвращает готовую HTML-страницу.
В нашем веб-приложении шаблоны будут храниться в директории /template. Мы будем указывать путь до этой директории в коде веб-приложения, чтобы оно знало, откуда читать и формировать шаблоны. Структура файлов веб-приложения выглядит как:
А код веб-приложения имеет следующий вид:
Запускаем веб-приложение и переходим в веб-браузере на страницу http://localhost/profile. Мы видим следующий результат:
Мы реализовали веб-страницу личного аккаунта пользователя. Веб-страница состоит из шаблона и динамических данных, которые подставляются веб-приложением в момент HTTP-запроса.
Для простоты мы описали прямо в коде имя и электронную почту пользователя. В вашем приложении эти данные, скорее всего, будут храниться в хранилище, и мы будем читать их и подставлять в шаблон.
Мы рассмотрели шаблон с двумя динамическими подстановками: имя и электронная почта пользователя. Но что если нам требуется вывести список динамических данных неопределенного размера? Например, список фильмов в видео-платформе. Для этого в Go-шаблонизаторе используются специальные конструкции. Рассмотрим их.
Использование специальных конструкций в шаблонах
В шаблонах Go-шаблонизатора могут быть более сложные конструкции, чем просто подстановка конкретных данных. Шаблонизатор также поддерживает условные конструкции и циклы. В этом разделе мы рассмотрим, как использовать эти конструкции на примере видео-платформы.
Для начала создадим шаблон для страницы с видео-контентом. В шаблоне мы будем выводить список фильмов, которые пользователь может просмотреть. Для этого создадим файл template/film-list.tmpl со следующим содержимым:
В шаблоне мы используем две специальные конструкции: цикл range и условие if. В цикле range мы перебираем список фильмов, которые передаются в шаблон в качестве параметра. В условии if мы проверяем, просмотрен ли фильм пользователем. Если фильм просмотрен, то мы выводим веб-страницу с текстом «Просмотрено ✅», иначе — «Не просмотрено ❌».
Опишем код Go веб-приложения, которое будет возвращать веб-страницу со списком фильмов:
Запускаем веб-приложение и переходим в веб-браузере на страницу http://localhost/films. Мы видим следующий результат:
Мы создали веб-приложение на Go, которое возвращает веб-страницу со списком фильмов. Веб-страница создается на основе шаблона, который хранится в файле template/film-list.tmpl.
Шаблон веб-страницы содержит конструкцию-if и цикл. Это позволяет нам выводить столько фильмов, сколько у нас хранится в хранилище, не внося изменений в код веб-приложения.
Выводы
- HTML — это язык разметки, по которому веб-браузеры строят и отображают контент для пользователей
- При переходе по ссылке веб-браузер отправляет GET HTTP-запрос по адресу и отображает HTML-код, полученный в теле ответа
- Шаблонизаторы позволяют создавать HTML-страницы на основе шаблонов, если подставлять в них динамические данные из веб-приложений
- Go-шаблонизатор позволяет использовать как простые подстановки в виде конкретных переменных {{ .Name }}, так и сложные конструкции, например: циклы и условия {{ range .Films }} {{ .Title }} {{ end }}
<!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 17:00:16 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="2Qx7gZjs2tSsUN1K_f6dKu5GkNQdlx_6HQhvsfiKSQE23bC2apJ3tBoT-dLx8W1dLk-9fhWg4Vig6PXlqo2ubw";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>Шаблонизация в Go | Веб-разработка на Go</title>
<meta name="description" content="Шаблонизация в Go / Веб-разработка на Go: Знакомимся со стандартным шаблонизатором в Go">
<link rel="canonical" href="https://ru.hexlet.io/courses/go-web-development/lessons/template/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Шаблонизация в Go">
<meta property="og:title" content="Веб-разработка на Go">
<meta property="og:description" content="Шаблонизация в Go / Веб-разработка на Go: Знакомимся со стандартным шаблонизатором в Go">
<meta property="og:url" content="https://ru.hexlet.io/courses/go-web-development/lessons/template/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="NmfyN1FLIMAK4emsKEPw9Sthk9BVkrkZ4A9FaEOU46bZtjkAozWNoLyizTQkTACC62i-el2lR7td7988EZMEyA" />
<script src="/vite/assets/inertia-INZxX8jp.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-nkZBEvfU.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-6pOtQ3OW.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/eyJfcmFpbHMiOnsiZGF0YSI6NDAzNywicHVyIjoiYmxvYl9pZCJ9fQ==--c9a507d1b30c26185c312c95f68af4f0d8122afa/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-bro.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY2MywicHVyIjoiYmxvYl9pZCJ9fQ==--7e08550d68b7c2ad01e51109dfcf0861158d2e58/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Code%20review-amico.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg4MywicHVyIjoiYmxvYl9pZCJ9fQ==--9e90f48f058ab07777e0e79ecfcf2980f79fa277/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Code%20typing-cuate.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-26T17:00:16.229Z","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":"uAeyzpXqeqjdPIqIk5Sh7dZ8HhIOICb47jCzobDNdZBX1nn5Z5TXyGt_rhCfm1GaFnUzuAYX2FpT0Cn14sqS_g","topics":[{"id":88679,"title":"В вопросе `В каком виде HTML-страницы передаются из веб-приложения в веб-браузер для отображения?` есть 2 варианта ответа:\n- HTML-кодом в сыром виде на GET запрос\n- В теле HTTP-ответа на GET запрос\n\nВ чем их разница?","plain_title":"В вопросе В каком виде HTML-страницы передаются из веб-приложения в веб-браузер для отображения? есть 2 варианта ответа: - HTML-кодом в сыром виде на GET запрос - В теле HTTP-ответа на GET запрос В чем их разница? ","creator":{"public_name":"Andrey","id":438142,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":177131,"body":"Добрый день, поправил варианты ответов.","topic_id":88679}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация в Go","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2362,"slug":"go_web_development_template_exercise","name":null,"state":"active","kind":"exercise","language":"golang","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"В этой практике вам нужно реализовать веб-приложение, которое отображает список товаров. \n\n## app/solution.go\n\nЭто веб-приложение имеет два HTTP-обработчика:\n\n* Обработчик HTTP-запроса `POST /items` сохраняет новый товар в массиве `items []Item`, где `Item` это структура, которая содержит информацию о товаре.\n\n* Обработчик HTTP-запрос `GET /items/view` генерирует HTML-страницу, которая отображает список товаров, сохраненных в оперативной памяти веб-приложения. HTML-страница генерируется на основе шаблона **\"./templates/items.tmpl\"**.\n\nШаблон **\"./templates/items.tmpl\"** выглядит следующим образом:\n\n```html\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>Список Товаров</h1>\n\n{{ range $item := . }}\n<div>\n <h2>{{ $item.Name }}</h2>\n <p>Цена ${{ $item.Price }}</p>\n</div>\n{{ end }}\n\n</body>\n</html>\n```\n\nДавайте рассмотрим пример работы этого веб-приложения. Для начала отправляется HTTP-запрос на создание товара:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"name\":\"Socks\",\"price\":100}' http://localhost:8080/items\n```\n\nВеб-приложение сохраняет товар в оперативной памяти и возвращает HTTP-ответ со статусом 200 OK:\n\n```bash\nHTTP/1.1 200 OK\n```\n\nДалее отправляется HTTP-запрос на получение списка товаров:\n\n```bash\ncurl http://localhost:8080/items/view\n```\n\nВеб-приложение генерирует HTML-страницу на основе шаблона **\"./templates/items.tmpl\"** и возвращает ее в качестве HTTP-ответа со статусом 200 OK:\n\n```bash\nHTTP/1.1 200 OK\n\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>Список Товаров</h1>\n\n<div>\n <h2>Socks</h2>\n <p>Цена $100</p>\n</div>\n\n</body>\n</html>\n```\n\nРеализуйте HTTP-обработчики в веб-приложении для создания и отображения товаров.\n","prepared_readme":"В этой практике вам нужно реализовать веб-приложение, которое отображает список товаров. \n\n## app/solution.go\n\nЭто веб-приложение имеет два HTTP-обработчика:\n\n* Обработчик HTTP-запроса `POST /items` сохраняет новый товар в массиве `items []Item`, где `Item` это структура, которая содержит информацию о товаре.\n\n* Обработчик HTTP-запрос `GET /items/view` генерирует HTML-страницу, которая отображает список товаров, сохраненных в оперативной памяти веб-приложения. HTML-страница генерируется на основе шаблона **\"./templates/items.tmpl\"**.\n\nШаблон **\"./templates/items.tmpl\"** выглядит следующим образом:\n\n```html\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>Список Товаров</h1>\n\n{{ range $item := . }}\n<div>\n <h2>{{ $item.Name }}</h2>\n <p>Цена ${{ $item.Price }}</p>\n</div>\n{{ end }}\n\n</body>\n</html>\n```\n\nДавайте рассмотрим пример работы этого веб-приложения. Для начала отправляется HTTP-запрос на создание товара:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"name\":\"Socks\",\"price\":100}' http://localhost:8080/items\n```\n\nВеб-приложение сохраняет товар в оперативной памяти и возвращает HTTP-ответ со статусом 200 OK:\n\n```bash\nHTTP/1.1 200 OK\n```\n\nДалее отправляется HTTP-запрос на получение списка товаров:\n\n```bash\ncurl http://localhost:8080/items/view\n```\n\nВеб-приложение генерирует HTML-страницу на основе шаблона **\"./templates/items.tmpl\"** и возвращает ее в качестве HTTP-ответа со статусом 200 OK:\n\n```bash\nHTTP/1.1 200 OK\n\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>Список Товаров</h1>\n\n<div>\n <h2>Socks</h2>\n <p>Цена $100</p>\n</div>\n\n</body>\n</html>\n```\n\nРеализуйте HTTP-обработчики в веб-приложении для создания и отображения товаров.\n","has_solution":true,"entity_name":"Шаблонизация в Go"},"units":[{"id":7226,"name":"theory","url":"/courses/go-web-development/lessons/template/theory_unit"},{"id":7464,"name":"quiz","url":"/courses/go-web-development/lessons/template/quiz_unit"},{"id":8223,"name":"exercise","url":"/courses/go-web-development/lessons/template/exercise_unit"}],"links":[{"id":423677,"name":"Go HTML Template package","url":"https://golang.org/pkg/html/template/"},{"id":423678,"name":"Fiber Templates","url":"https://docs.gofiber.io/guide/templates"},{"id":423679,"name":"Go Templates Examples","url":"https://gowebexamples.com/templates/"}],"ordered_units":[{"id":7226,"name":"theory","url":"/courses/go-web-development/lessons/template/theory_unit"},{"id":7464,"name":"quiz","url":"/courses/go-web-development/lessons/template/quiz_unit"},{"id":8223,"name":"exercise","url":"/courses/go-web-development/lessons/template/exercise_unit"}],"id":3237,"slug":"template","state":"approved","name":"Шаблонизация в Go","course_order":155,"goal":"Знакомимся со стандартным шаблонизатором в Go","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Представим, что мы разрабатываем видео-платформу, и нужно реализовать страницу со списком доступных фильмов. Страницы для браузера приходят с веб-приложения на Go. В этом случае нужно понять, как организовать хранение контента страниц, и как в них вставлять изменяющиеся данные, например, список фильмов.\n\nВ этом уроке мы рассмотрим, как работать с шаблонами HTML-страниц в Go-приложениях. Мы научимся хранить, подставлять динамические значения и возвращать в браузер готовые HTML-страницы.\n\n## Формирование HTML-страниц в веб-приложении\n\n**HTML** — это язык разметки страниц для веб-браузеров, код которого состоит из тегов. Они обозначают различный вид контента, например: заголовки, блоки, абзацы. Благодаря HTML разработчики могут строить различные интерфейсы веб-сайтов для пользователей.\n\nКогда пользователь заходит на веб-сайт, происходят следующие действия до отображения контента в веб-браузере:\n\n1. Веб-браузер отправляет GET HTTP-запрос по адресу веб-сайта\n2. Веб-приложение, ответственное за обработку запросов на этом адресе, формирует HTML-код и отправляет его в теле HTTP-ответа\n3. Веб-браузер получает HTML-код, анализирует и отображает его\n\n\n\nHTML-страницы бывают двух типов:\n\n* Статические\n* Динамические\n\n**Статические** HTML-страницы не меняются от запроса к запросу. Например, страница с информацией о компании. Такие страницы формируются один раз и хранятся на сервере. Когда пользователь заходит на сайт, веб-приложение отправляет ему HTML-код страницы без внесения изменений.\n\n**Динамические** HTML-страницы имеют изменяющийся контент. Например, страница с фильмами в видео-платформе зависит от фильмов, которые хранятся в хранилище этого веб-приложения. Когда пользователь заходит на сайт, веб-приложение берет шаблон HTML-страницы, подставляет актуальный список фильмов и отправляет HTML-код пользователю. Таким образом, конечный HTML-код постоянно меняется в зависимости от количества фильмов в хранилище.\n\nРабота с шаблонами и динамическим контентом — это распространенная задача в веб-разработке, поэтому существуют специальные инструменты для работы с ними. Такие инструменты называют **шаблонизаторами**. Рассмотрим подробнее, как выглядит работа шаблонизатора с шаблонами HTML-страниц.\n\n## Как работают шаблонизаторы\n\nВ шаблонах хранится HTML-код с местами для вставки динамического контента. Когда веб-приложению нужно отправить HTML-страницу пользователю, оно берет шаблон, заполняет динамический контент и отправляет пользователю.\n\nПредставим, что мы зашли на веб-страницу своего аккаунта в видео-платформе. Шаблон этой страницы выглядит следующим образом:\n\n```html\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>My Profile</h1>\n<p>Name: {{.name}}</p>\n<p>Email: {{.email}}</p>\n\n</body>\n</html>\n```\n\nНа странице есть специальные места для вставки динамического контента. Такие места выделены двойными фигурными скобками. Например, `{{.name}}` означает место для вставки имени пользователя. Это специальный синтаксис для шаблонизатора в Go. Перед тем как отправить такой шаблон пользователю, веб-приложение заменит `{{.name}}` на имя текущего пользователя.\n\nНапример, если наше имя — *John*, а почта — *john@doe.com*, то после обработки веб-приложение вернет следующий HTML-код:\n\n```html\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>My Profile</h1>\n<p>Name: John</p>\n<p>Email: john@doe.com</p>\n\n</body>\n</html>\n```\n\nИспользование шаблонизатора дает следующие преимущества:\n\n* Мы можем использовать один и тот же шаблон для разного контента. Например, мы можем использовать один и тот же шаблон страницы профиля, но вставлять в него разные данные пользователей\n* Нам не нужно реализовывать подстановку динамического контента в HTML-код. Мы просто указываем что и где должно быть, а остальное берет на себя шаблонизатор\n* Логика веб-приложения, в частности работа с базой данных, не смешивается с логикой отображения. В шаблонах описывается только HTML-код, а заполняются динамические данные через передачу данных в шаблонизатор\n\nМы рассмотрели работу шаблонизаторов и теперь можем перейти к работе с HTML в Go Fiber.\n\n## Работа с HTML в Go Fiber\n\nНачнем с реализации веб-страницы личного аккаунта. У нас есть шаблон веб-страницы. Когда пользователь запрашивает ее, Go веб-приложение подставляет информацию текущего пользователя в шаблон и возвращает готовую HTML-страницу.\n\nВ нашем веб-приложении шаблоны будут храниться в директории */template*. Мы будем указывать путь до этой директории в коде веб-приложения, чтобы оно знало, откуда читать и формировать шаблоны. Структура файлов веб-приложения выглядит как:\n\n```bash\n.\n├── go.mod\n├── go.sum\n├── main.go\n└── template\n └── profile.tmpl\n\n```\n\nА код веб-приложения имеет следующий вид:\n\n```go\npackage main\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/template/html\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\t// Инициализируем стандартный Go-шаблонизатор\n\t// Указываем папку, в которой хранятся шаблоны\n\t// Указываем расширение файлов шаблонов\n\tviewsEngine := html.New(\"./template\", \".tmpl\")\n\n\t// Создаем новый экземпляр Fiber веб-приложения,\n\t// указывая наш шаблонизатор\n\twebApp := fiber.New(fiber.Config{\n\t\tViews: viewsEngine,\n\t})\n\n\t// Настраиваем обработчик для веб-страницы аккаунта пользователя\n\twebApp.Get(\"/profile\", func(c *fiber.Ctx) error {\n\t\treturn c.Render(\"profile\", fiber.Map{\n\t\t\t\"name\": \"John\",\n\t\t\t\"email\": \"john@doe.com\",\n\t\t})\n\t})\n\n\tlogrus.Fatal(webApp.Listen(\":80\"))\n}\n```\n\nЗапускаем веб-приложение и переходим в веб-браузере на страницу *http://localhost/profile*. Мы видим следующий результат:\n\n```text\nMy Profile\n\nName: John\n\nEmail: john@doe.com\n```\n\nМы реализовали веб-страницу личного аккаунта пользователя. Веб-страница состоит из шаблона и динамических данных, которые подставляются веб-приложением в момент HTTP-запроса.\n\nДля простоты мы описали прямо в коде имя и электронную почту пользователя. В вашем приложении эти данные, скорее всего, будут храниться в хранилище, и мы будем читать их и подставлять в шаблон.\n\nМы рассмотрели шаблон с двумя динамическими подстановками: имя и электронная почта пользователя. Но что если нам требуется вывести список динамических данных неопределенного размера? Например, список фильмов в видео-платформе. Для этого в Go-шаблонизаторе используются специальные конструкции. Рассмотрим их.\n\n## Использование специальных конструкций в шаблонах\n\nВ шаблонах Go-шаблонизатора могут быть более сложные конструкции, чем просто подстановка конкретных данных. Шаблонизатор также поддерживает условные конструкции и циклы. В этом разделе мы рассмотрим, как использовать эти конструкции на примере видео-платформы.\n\nДля начала создадим шаблон для страницы с видео-контентом. В шаблоне мы будем выводить список фильмов, которые пользователь может просмотреть. Для этого создадим файл *template/film-list.tmpl* со следующим содержимым:\n\n```html\n<!DOCTYPE html>\n<html>\n<body>\n\n<h1>Список фильмов</h1>\n\n{{ range $film := . }}\n<div>\n <h2>{{ $film.Title }}</h2>\n <p>\n {{ if $film.IsViewed }}\n Просмотрено ✅\n {{ else }}\n Не просмотрено ❌\n {{ end }}\n </p>\n</div>\n{{ end }}\n\n</body>\n</html>\n```\n\nВ шаблоне мы используем две специальные конструкции: цикл `range` и условие `if`. В цикле `range` мы перебираем список фильмов, которые передаются в шаблон в качестве параметра. В условии `if` мы проверяем, просмотрен ли фильм пользователем. Если фильм просмотрен, то мы выводим веб-страницу с текстом «Просмотрено ✅», иначе — «Не просмотрено ❌».\n\nОпишем код Go веб-приложения, которое будет возвращать веб-страницу со списком фильмов:\n\n```go\npackage main\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/template/html\"\n\t\"github.com/sirupsen/logrus\"\n)\n\n// Структура с информацией о фильме\ntype Film struct {\n\tTitle string\n\tIsViewed bool\n}\n\n// Для простоты описываем хранилище фильмов в коде\nvar films = []Film{\n\t{\n\t\tTitle: \"The Shawshank Redemption\",\n\t\tIsViewed: true,\n\t},\n\t{\n\t\tTitle: \"The Godfather\",\n\t\tIsViewed: true,\n\t},\n\t{\n\t\tTitle: \"The Godfather: Part II\",\n\t\tIsViewed: false,\n\t},\n}\n\nfunc main() {\n\t// Инициализируем стандартный Go-шаблонизатор\n\t// Указываем папку, в которой хранятся шаблоны\n\t// Указываем расширение файлов шаблонов\n\tviewsEngine := html.New(\"./template\", \".tmpl\")\n\n\t// Создаем новый экземпляр Fiber веб-приложения,\n\t// указывая наш шаблонизатор\n\twebApp := fiber.New(fiber.Config{\n\t\tViews: viewsEngine,\n\t})\n\n\t// Настраиваем обработчик для веб-страницы со списком фильмов\n\twebApp.Get(\"/films\", func(c *fiber.Ctx) error {\n\t\treturn c.Render(\"film-list\", films)\n\t})\n\n\tlogrus.Fatal(webApp.Listen(\":80\"))\n}\n```\n\nЗапускаем веб-приложение и переходим в веб-браузере на страницу *<http://localhost/films>*. Мы видим следующий результат:\n\n```text\nСписок фильмов\n\nThe Shawshank Redemption\n\nПросмотрено ✅\n\nThe Godfather\n\nПросмотрено ✅\n\nThe Godfather: Part II\n\nНе просмотрено ❌\n```\n\nМы создали веб-приложение на Go, которое возвращает веб-страницу со списком фильмов. Веб-страница создается на основе шаблона, который хранится в файле *template/film-list.tmpl*.\n\nШаблон веб-страницы содержит конструкцию-if и цикл. Это позволяет нам выводить столько фильмов, сколько у нас хранится в хранилище, не внося изменений в код веб-приложения.\n\n## Выводы\n\n* HTML — это язык разметки, по которому веб-браузеры строят и отображают контент для пользователей\n* При переходе по ссылке веб-браузер отправляет GET HTTP-запрос по адресу и отображает HTML-код, полученный в теле ответа\n* Шаблонизаторы позволяют создавать HTML-страницы на основе шаблонов, если подставлять в них динамические данные из веб-приложений\n* Go-шаблонизатор позволяет использовать как простые подстановки в виде конкретных переменных `{{ .Name }}`, так и сложные конструкции, например: циклы и условия `{{ range .Films }} {{ .Title }} {{ end }}`\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":7205,"name":"theory","url":"/courses/go-web-development/lessons/intro/theory_unit"}],"links":[{"id":423640,"name":"Установка Go","url":"https://go.dev/doc/install"},{"id":423641,"name":"Анатомия веб-сервиса от Highload","url":"https://highload.guide/blog/inside-webserver.html"},{"id":423642,"name":"Вытесняющий планировщик Go","url":"https://habr.com/ru/post/502506/"},{"id":423643,"name":"Сравнение производительности веб-сервера на Go с другими языками","url":"https://www.toptal.com/back-end/server-side-io-performance-node-php-java-go"},{"id":423644,"name":"Communicating sequential processes (теория о шаблонах взаимодействия в конкурентных системах)","url":"https://en.wikipedia.org/wiki/Communicating_sequential_processes"},{"id":423645,"name":"Конкурентность — Асинхронность","url":"https://habr.com/ru/post/319350/"}],"ordered_units":[{"id":7205,"name":"theory","url":"/courses/go-web-development/lessons/intro/theory_unit"}],"id":3218,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"На сегодняшний день интернет стал неотъемлемой частью жизни каждого из нас. Мы заказываем продукты, вещи, лекарства в интернете, смотрим фильмы в онлайн-кинотеатрах и общаемся с друзьями в социальных сетях. И этот список можно продолжать бесконечно. При этом в основе каждого из этих примеров лежит веб-приложение, которое выполняет определенную функцию.\n\nВеб-приложение — это программное обеспечение, в котором пользователь взаимодействует с веб-сервером при помощи веб-браузера. Например, Google — это веб-приложение для поиска информации в интернете, а Youtube — это веб-приложение для просмотра видео.\n\nВеб-приложения разделяются на два типа:\n\n* **Frontend (клиентская часть веб-приложения)** — отвечает за отображение информации пользователю и взаимодействие с ним. Чаще подразумевается взаимодействие в веб-браузере\n* **Backend (серверная часть веб-приложения)** — отвечает за управление данными и бизнес-логикой веб-приложения\n\nВ этом курсе мы сосредоточимся на backend, а именно на разработке веб-приложений на языке Go.\n\n## Почему именно Go\n\nGolang — это достаточно молодой язык программирования. Его разработали в Google с учетом следующих требований:\n\n* **Простота**. Язык Go должен быть простым в изучении и использовании. Чтобы быстро разрабатывать программное обеспечение, программисту нужно понимать, как работает язык и как его использовать\n* **Производительность**. Язык Go должен быть быстрым в работе. Веб-приложения компании Google — одни из самых высоконагруженных в мире. Поэтому им важно использовать язык программирования, который не уступит по производительности низкоуровневым языкам, таким как \"Си\"\n* **Параллелизм**. С современными веб-приложениями работают тысячи пользователей в один момент времени. Язык Go должен предоставлять удобные инструменты для эффективной параллельной обработки множества HTTP-запросов\n\nЭти требования были учтены при разработке языка Go. По этим причинам на сегодняшний день он активно используется при разработке социальных сетей, финтех и блокчейн платформ.\n\nВ таких проектах часто присутствуют большие нагрузки, обработка данных в режиме реального времени и микросервисная архитектура. И именно Go позволяет решить эти вопросы достаточно просто и эффективно.\n\n## Цели курса\n\nВ этом курсе мы научимся разрабатывать веб-приложения на языке Go с использованием микрофреймворка Fiber. Мы узнаем, как:\n\n* работать со стандартной библиотекой HTTP в Golang\n* использовать логирование в приложениях\n* читать запросы и отправлять ответы с микрофреймворком Fiber\n* описывать роутинг в Fiber веб-приложениях\n* сериализовать и десериализовать данные в JSON в Golang\n* строить слой хранения данных в Golang-приложении\n* проверять HTTP-запросы в Go\n* использовать middleware при обработке HTTP-запросов в Go\n* настраивать JWT-авторизацию на сервере\n* работать с шаблонами HTML-страниц в Go-приложениях\n* обрабатывать, логировать и возвращать ошибки клиенту\n"},"id":319,"slug":"go-web-development","challenges_count":3,"name":"Веб-разработка на Go","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите основы веб-разработки на Go. Вы узнаете, как обрабатывать запросы и формировать ответ, как работать сессиями, обрабатывать ошибки, что такое CRUD и как правильно работать с сущностями. В итоге вы научитесь создавать полноценные сайты на фреймворке Fiber, строить архитектуру веб-приложений и доставлять их до сервера. Веб-разработка на Fiber пригодится, если вы решите детально изучить принципы создания современных веб-приложений и создавать производительные приложения, которые обрабатывают тысячи запросов в секунду.","kind":"basic","updated_at":"2026-01-20T11:43:55.334Z","language":"golang","duration_cache":57240,"skills":["Разрабатывать веб-приложения на Go","Использовать микрофреймворк Fiber","Строить систему JWT-аутентификации на Go"],"keywords":[],"lessons_count":13,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjkyMCwicHVyIjoiYmxvYl9pZCJ9fQ==--77dee61425670e714a2a183b8687d836b504fb63/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":63,"slug":"go-web-development","title":"Веб-разработка на Go","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":1250,"duration_in_months":1},"id":113,"slug":"go-web-development","title":"Веб-разработка на Go","subtitle":"Навык создания веб-приложений на Go, необходимый для получения оффера на позицию Go-разработчика","subtitle_for_lists":"Изучите Go и разработку веб-приложений","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"go-web-development","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAzNywicHVyIjoiYmxvYl9pZCJ9fQ==--c9a507d1b30c26185c312c95f68af4f0d8122afa/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-bro.png"},{"stack":{"id":225,"slug":"devops-engineer-from-scratch","title":"DevOps-инженер с нуля","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":50,"duration_in_months":14},"id":355,"slug":"devops-engineer-from-scratch","title":"DevOps-инженер с нуля","subtitle":"Полное погружение в DevOps: весь стек от Linux до Kubernetes","subtitle_for_lists":"Полное погружение в DevOps: весь стек от Linux до Kubernetes","locale":"ru","current":true,"duration_in_months_text":"14 месяцев","stack_slug":"devops-engineer-from-scratch","price_text":"от 6 792 ₽","duration_text":"14 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png"},{"stack":{"id":229,"slug":"go","title":"GO-разработчик","audience":"for_programmers","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":70,"duration_in_months":6},"id":363,"slug":"go","title":"Go-разработчик","subtitle":"Изучите Go, работу с БД, HTTP, конкурентность, горутины, многопоточность ","subtitle_for_lists":"Изучите Go, работу с БД, HTTP, конкурентность, горутины, многопоточность ","locale":"ru","current":true,"duration_in_months_text":"6 месяцев","stack_slug":"go","price_text":"от 4 509 ₽","duration_text":"6 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY2MywicHVyIjoiYmxvYl9pZCJ9fQ==--7e08550d68b7c2ad01e51109dfcf0861158d2e58/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Code%20review-amico.png"},{"stack":{"id":461,"slug":"go-from-scratch","title":"Go-разработчик с нуля","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":250,"duration_in_months":9},"id":590,"slug":"go-from-scratch","title":"Go-разработчик с нуля","subtitle":"Изучите фреймворк Gin, горутины, многопоточность и работа с API","subtitle_for_lists":"Изучите фреймворк Gin, горутины, многопоточность и работа с API","locale":"ru","current":true,"duration_in_months_text":"9 месяцев","stack_slug":"go-from-scratch","price_text":null,"duration_text":"9 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg4MywicHVyIjoiYmxvYl9pZCJ9fQ==--9e90f48f058ab07777e0e79ecfcf2980f79fa277/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Code%20typing-cuate.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/go-web-development/lessons/template/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","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">Веб-разработка на Go</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">Теория: Шаблонизация в Go</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Шаблонизация в Go","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Веб-разработка на Go"},"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>Представим, что мы разрабатываем видео-платформу, и нужно реализовать страницу со списком доступных фильмов. Страницы для браузера приходят с веб-приложения на Go. В этом случае нужно понять, как организовать хранение контента страниц, и как в них вставлять изменяющиеся данные, например, список фильмов.</p>
<p>В этом уроке мы рассмотрим, как работать с шаблонами HTML-страниц в Go-приложениях. Мы научимся хранить, подставлять динамические значения и возвращать в браузер готовые HTML-страницы.</p>
<h2 id="heading-2-1">Формирование HTML-страниц в веб-приложении</h2>
<p><strong>HTML</strong> — это язык разметки страниц для веб-браузеров, код которого состоит из тегов. Они обозначают различный вид контента, например: заголовки, блоки, абзацы. Благодаря HTML разработчики могут строить различные интерфейсы веб-сайтов для пользователей.</p>
<p>Когда пользователь заходит на веб-сайт, происходят следующие действия до отображения контента в веб-браузере:</p>
<ol>
<li>Веб-браузер отправляет GET HTTP-запрос по адресу веб-сайта</li>
<li>Веб-приложение, ответственное за обработку запросов на этом адресе, формирует HTML-код и отправляет его в теле HTTP-ответа</li>
<li>Веб-браузер получает HTML-код, анализирует и отображает его</li>
</ol>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Njk2MywicHVyIjoiYmxvYl9pZCJ9fQ==--64db5d0054e35b25a07d8ab298186c620f8cd799/template1.jpg" alt="set" loading="lazy"/></p>
<p>HTML-страницы бывают двух типов:</p>
<ul>
<li>Статические</li>
<li>Динамические</li>
</ul>
<p><strong>Статические</strong> HTML-страницы не меняются от запроса к запросу. Например, страница с информацией о компании. Такие страницы формируются один раз и хранятся на сервере. Когда пользователь заходит на сайт, веб-приложение отправляет ему HTML-код страницы без внесения изменений.</p>
<p><strong>Динамические</strong> HTML-страницы имеют изменяющийся контент. Например, страница с фильмами в видео-платформе зависит от фильмов, которые хранятся в хранилище этого веб-приложения. Когда пользователь заходит на сайт, веб-приложение берет шаблон HTML-страницы, подставляет актуальный список фильмов и отправляет HTML-код пользователю. Таким образом, конечный HTML-код постоянно меняется в зависимости от количества фильмов в хранилище.</p>
<p>Работа с шаблонами и динамическим контентом — это распространенная задача в веб-разработке, поэтому существуют специальные инструменты для работы с ними. Такие инструменты называют <strong>шаблонизаторами</strong>. Рассмотрим подробнее, как выглядит работа шаблонизатора с шаблонами HTML-страниц.</p>
<h2 id="heading-2-2">Как работают шаблонизаторы</h2>
<p>В шаблонах хранится HTML-код с местами для вставки динамического контента. Когда веб-приложению нужно отправить HTML-страницу пользователю, оно берет шаблон, заполняет динамический контент и отправляет пользователю.</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"><!DOCTYPE html>
<html>
<body>
<h1>My Profile</h1>
<p>Name: {{.name}}</p>
<p>Email: {{.email}}</p>
</body>
</html></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>На странице есть специальные места для вставки динамического контента. Такие места выделены двойными фигурными скобками. Например, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">{{.name}}</code> означает место для вставки имени пользователя. Это специальный синтаксис для шаблонизатора в Go. Перед тем как отправить такой шаблон пользователю, веб-приложение заменит <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">{{.name}}</code> на имя текущего пользователя.</p>
<p>Например, если наше имя — <em>John</em>, а почта — <em><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:john@doe.com">john@doe.com</a></em>, то после обработки веб-приложение вернет следующий HTML-код:</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"><!DOCTYPE html>
<html>
<body>
<h1>My Profile</h1>
<p>Name: John</p>
<p>Email: john@doe.com</p>
</body>
</html></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>
<ul>
<li>Мы можем использовать один и тот же шаблон для разного контента. Например, мы можем использовать один и тот же шаблон страницы профиля, но вставлять в него разные данные пользователей</li>
<li>Нам не нужно реализовывать подстановку динамического контента в HTML-код. Мы просто указываем что и где должно быть, а остальное берет на себя шаблонизатор</li>
<li>Логика веб-приложения, в частности работа с базой данных, не смешивается с логикой отображения. В шаблонах описывается только HTML-код, а заполняются динамические данные через передачу данных в шаблонизатор</li>
</ul>
<p>Мы рассмотрели работу шаблонизаторов и теперь можем перейти к работе с HTML в Go Fiber.</p>
<h2 id="heading-2-3">Работа с HTML в Go Fiber</h2>
<p>Начнем с реализации веб-страницы личного аккаунта. У нас есть шаблон веб-страницы. Когда пользователь запрашивает ее, Go веб-приложение подставляет информацию текущего пользователя в шаблон и возвращает готовую HTML-страницу.</p>
<p>В нашем веб-приложении шаблоны будут храниться в директории <em>/template</em>. Мы будем указывать путь до этой директории в коде веб-приложения, чтобы оно знало, откуда читать и формировать шаблоны. Структура файлов веб-приложения выглядит как:</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">.
├── go.mod
├── go.sum
├── main.go
└── template
└── profile.tmpl</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 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 main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html"
"github.com/sirupsen/logrus"
)
func main() {
// Инициализируем стандартный Go-шаблонизатор
// Указываем папку, в которой хранятся шаблоны
// Указываем расширение файлов шаблонов
viewsEngine := html.New("./template", ".tmpl")
// Создаем новый экземпляр Fiber веб-приложения,
// указывая наш шаблонизатор
webApp := fiber.New(fiber.Config{
Views: viewsEngine,
})
// Настраиваем обработчик для веб-страницы аккаунта пользователя
webApp.Get("/profile", func(c *fiber.Ctx) error {
return c.Render("profile", fiber.Map{
"name": "John",
"email": "john@doe.com",
})
})
logrus.Fatal(webApp.Listen(":80"))
}</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>Запускаем веб-приложение и переходим в веб-браузере на страницу <em><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="http://localhost/profile" rel="noopener noreferrer" target="_blank">http://localhost/profile</a></em>. Мы видим следующий результат:</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">My Profile
Name: John
Email: john@doe.com</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>Мы реализовали веб-страницу личного аккаунта пользователя. Веб-страница состоит из шаблона и динамических данных, которые подставляются веб-приложением в момент HTTP-запроса.</p>
<p>Для простоты мы описали прямо в коде имя и электронную почту пользователя. В вашем приложении эти данные, скорее всего, будут храниться в хранилище, и мы будем читать их и подставлять в шаблон.</p>
<p>Мы рассмотрели шаблон с двумя динамическими подстановками: имя и электронная почта пользователя. Но что если нам требуется вывести список динамических данных неопределенного размера? Например, список фильмов в видео-платформе. Для этого в Go-шаблонизаторе используются специальные конструкции. Рассмотрим их.</p>
<h2 id="heading-2-4">Использование специальных конструкций в шаблонах</h2>
<p>В шаблонах Go-шаблонизатора могут быть более сложные конструкции, чем просто подстановка конкретных данных. Шаблонизатор также поддерживает условные конструкции и циклы. В этом разделе мы рассмотрим, как использовать эти конструкции на примере видео-платформы.</p>
<p>Для начала создадим шаблон для страницы с видео-контентом. В шаблоне мы будем выводить список фильмов, которые пользователь может просмотреть. Для этого создадим файл <em>template/film-list.tmpl</em> со следующим содержимым:</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"><!DOCTYPE html>
<html>
<body>
<h1>Список фильмов</h1>
{{ range $film := . }}
<div>
<h2>{{ $film.Title }}</h2>
<p>
{{ if $film.IsViewed }}
Просмотрено ✅
{{ else }}
Не просмотрено ❌
{{ end }}
</p>
</div>
{{ end }}
</body>
</html></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>В шаблоне мы используем две специальные конструкции: цикл <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">range</code> и условие <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">if</code>. В цикле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">range</code> мы перебираем список фильмов, которые передаются в шаблон в качестве параметра. В условии <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">if</code> мы проверяем, просмотрен ли фильм пользователем. Если фильм просмотрен, то мы выводим веб-страницу с текстом «Просмотрено ✅», иначе — «Не просмотрено ❌».</p>
<p>Опишем код Go веб-приложения, которое будет возвращать веб-страницу со списком фильмов:</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 main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html"
"github.com/sirupsen/logrus"
)
// Структура с информацией о фильме
type Film struct {
Title string
IsViewed bool
}
// Для простоты описываем хранилище фильмов в коде
var films = []Film{
{
Title: "The Shawshank Redemption",
IsViewed: true,
},
{
Title: "The Godfather",
IsViewed: true,
},
{
Title: "The Godfather: Part II",
IsViewed: false,
},
}
func main() {
// Инициализируем стандартный Go-шаблонизатор
// Указываем папку, в которой хранятся шаблоны
// Указываем расширение файлов шаблонов
viewsEngine := html.New("./template", ".tmpl")
// Создаем новый экземпляр Fiber веб-приложения,
// указывая наш шаблонизатор
webApp := fiber.New(fiber.Config{
Views: viewsEngine,
})
// Настраиваем обработчик для веб-страницы со списком фильмов
webApp.Get("/films", func(c *fiber.Ctx) error {
return c.Render("film-list", films)
})
logrus.Fatal(webApp.Listen(":80"))
}</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>Запускаем веб-приложение и переходим в веб-браузере на страницу <em><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="http://localhost/films" rel="noopener noreferrer" target="_blank">http://localhost/films</a></em>. Мы видим следующий результат:</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">Список фильмов
The Shawshank Redemption
Просмотрено ✅
The Godfather
Просмотрено ✅
The Godfather: Part II
Не просмотрено ❌</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>Мы создали веб-приложение на Go, которое возвращает веб-страницу со списком фильмов. Веб-страница создается на основе шаблона, который хранится в файле <em>template/film-list.tmpl</em>.</p>
<p>Шаблон веб-страницы содержит конструкцию-if и цикл. Это позволяет нам выводить столько фильмов, сколько у нас хранится в хранилище, не внося изменений в код веб-приложения.</p>
<h2 id="heading-2-5">Выводы</h2>
<ul>
<li>HTML — это язык разметки, по которому веб-браузеры строят и отображают контент для пользователей</li>
<li>При переходе по ссылке веб-браузер отправляет GET HTTP-запрос по адресу и отображает HTML-код, полученный в теле ответа</li>
<li>Шаблонизаторы позволяют создавать HTML-страницы на основе шаблонов, если подставлять в них динамические данные из веб-приложений</li>
<li>Go-шаблонизатор позволяет использовать как простые подстановки в виде конкретных переменных <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">{{ .Name }}</code>, так и сложные конструкции, например: циклы и условия <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">{{ range .Films }} {{ .Title }} {{ end }}</code></li>
</ul></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/go-web-development?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">1 месяц</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">Веб-разработка на Go</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Go и разработку веб-приложений</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/eyJfcmFpbHMiOnsiZGF0YSI6NDAzNywicHVyIjoiYmxvYl9pZCJ9fQ==--c9a507d1b30c26185c312c95f68af4f0d8122afa/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-bro.png" alt="Веб-разработка на Go" 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">от 3 900 ₽</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/devops-engineer-from-scratch?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">14 месяцев</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">DevOps-инженер с нуля</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полное погружение в DevOps: весь стек от Linux до Kubernetes</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/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png" alt="DevOps-инженер с нуля" 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/go?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">Go-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Go, работу с БД, HTTP, конкурентность, горутины, многопоточность </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/eyJfcmFpbHMiOnsiZGF0YSI6MzY2MywicHVyIjoiYmxvYl9pZCJ9fQ==--7e08550d68b7c2ad01e51109dfcf0861158d2e58/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Code%20review-amico.png" alt="Go-разработчик" 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 509 ₽</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/go-from-scratch?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">9 месяцев</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">Go-разработчик с нуля</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите фреймворк Gin, горутины, многопоточность и работа с 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/eyJfcmFpbHMiOnsiZGF0YSI6NDg4MywicHVyIjoiYmxvYl9pZCJ9fQ==--9e90f48f058ab07777e0e79ecfcf2980f79fa277/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Code%20typing-cuate.png" alt="Go-разработчик с нуля" 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"></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/go-web-development/lessons/template/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 / 13</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/go-web-development/lessons/template/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-CdBlNCiQ.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-nkZBEvfU.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>