<!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 16:59:39 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="hK00Hypap2BuSj-eoCF7KgzoQpYamI7RmMTaMLW5F_FrfP8o2CQKANgJGwasLotdzOFvPBKvcHMlJEBk577wnw";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>REST | JS: Express</title>
<meta name="description" content="REST / JS: Express: Знакомимся поближе с концепцией механизма REST и отсутствием состояния при запросах">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-express/lessons/rest/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="REST">
<meta property="og:title" content="JS: Express">
<meta property="og:description" content="REST / JS: Express: Знакомимся поближе с концепцией механизма REST и отсутствием состояния при запросах">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-express/lessons/rest/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="fAqETruQZEbX5O2cQsuf43f3uQ4ACi-z_pvD6EcG2JWT2095Se7JJmGnyQROxG-Ut_6UpAg90RFDe1m8FQE_-w" />
<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">
<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-26T16:59:38.925Z","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":"lb4AZ10fLh3XXki0PTAyF-70thLOnQunDoX3XMwQgPJ6b8tQr2GDfWEdbCwxP8JgLv2buMaq9QWzZW0InhdnnA","topics":[{"id":41330,"title":"Удивляет:\n что редирект на /edit после удачного обновления поста, вроде логично открывать, или index, или страницу поста.\n И то, что в PATCH сначала идет обработка удачного сценария и return, а потом обработка ошибки, а не наоборот.","plain_title":"Удивляет: что редирект на /edit после удачного обновления поста, вроде логично открывать, или index, или страницу поста. И то, что в PATCH сначала идет обработка удачного сценария и return, а потом обработка ошибки, а не наоборот. ","creator":{"public_name":"Александр Ржаницын","id":53329,"is_tutor":false},"comments":[{"creator":{"public_name":"Maxim Balyura","id":244081,"is_tutor":false},"id":90216,"body":"Еще больше непонятно, почему в написанной части кода, при создании поста (роут `app.post('/posts'...)`) в случае успеха происходит редирект на `/posts/${post.id}/edit`? При том, что в прошлом задании тут был редирект на `/posts/${post.id}`, что более логично. А тут получается после добавления поста мы попадаем сразу в форму его редактирования. Честно говоря, сбил этот момент, думал в /edit надо что-то очень хитрое реализовать.","topic_id":41330},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90361,"body":"> Удивляет: что редирект на /edit после удачного обновления поста, вроде логично открывать, или index, или страницу поста.\n\nОчень сильно зависит от ситуации. Когда что-то меняешь, то это может происходить в несколько этапов, плюс хочется увидеть что получилось. Если отправлять на шоу, то придется ходить туда сюда все время. Со списком еще хуже, потому что тяжелее найти саму сущность если есть пейджинг.\n\n> И то, что в PATCH сначала идет обработка удачного сценария и return, а потом обработка ошибки, а не наоборот.\n\nfixed\n\n> А тут получается после добавления поста мы попадаем сразу в форму его редактирования.\n\nfixed!\n","topic_id":41330}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":47486,"title":"В коде проверки на присутствие ошибок можно сделать проще:\n\nСейчас:\n\n```\nif (Object.keys(errors).length === 0) {\n const post = new Post(title, body);\n posts.push(post);\n res.redirect(`/posts/${post.id}`);\n return;\n }\n\n res.status(422);\n res.render('posts/new', { form: req.body, errors });\n```\n\nМожно просто вывести код в else блок и не нужно будет пихать return. Плюс без разницы что вы проверяете – if (наличие ошибок) и else (отсутствие) или наоборот.\n```\nif (Object.keys(errors).length === 0)\n const post = new Post(title, body);\n posts.push(post);\n res.redirect(`/posts/${post.id}`);\n} else {\n res.status(422);\n res.render('posts/new', { form: req.body, errors });\n}\n\n```\n\nВ решении учителя в `app.patch('/posts/:id', () => ...)` сейчас return вообще убрали почему-то и тепеперь вылетает вот такая ошибка:\n\n```\n console.error\n Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client\n at ServerResponse.setHeader (_http_outgoing.js:536:11)\n at ServerResponse.header (/usr/lib/node_modules/express/lib/response.js:771:10)\n at ServerResponse.location (/usr/lib/node_modules/express/lib/response.js:888:15)\n at ServerResponse.redirect (/usr/lib/node_modules/express/lib/response.js:926:18)\n at /usr/src/app/application.js:90:9\n at Layer.handle [as handle_request] (/usr/lib/node_modules/express/lib/router/layer.js:95:5)\n at next (/usr/lib/node_modules/express/lib/router/route.js:137:13)\n at Route.dispatch (/usr/lib/node_modules/express/lib/router/route.js:112:3)\n at Layer.handle [as handle_request] (/usr/lib/node_modules/express/lib/router/layer.js:95:5)\n at /usr/lib/node_modules/express/lib/router/index.js:281:22\n\n at Function.logerror (../../lib/node_modules/express/lib/application.js:630:43)\n\n```\n\nРешение нужно подправить по всем упражнениям курса.","plain_title":"В коде проверки на присутствие ошибок можно сделать проще: Сейчас: if (Object.keys(errors).length === 0) { const post = new Post(title, body); posts.push(post); res.redirect(`/posts/${post.id}`); return; } res.status(422); res.render('posts/new', { form: req.body, errors }); Можно просто вывести код в else блок и не нужно будет пихать return. Плюс без разницы что вы проверяете – if (наличие ошибок) и else (отсутствие) или наоборот. `` if (Object.keys(errors).length === 0) const post = new Post(title, body); posts.push(post); res.redirect(/posts/${post.id}`); } else { res.status(422); res.render('posts/new', { form: req.body, errors }); } В решении учителя в `app.patch('/posts/:id', () => ...)` сейчас return вообще убрали почему-то и тепеперь вылетает вот такая ошибка: console.error Error [ERRHTTPHEADERSSENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (httpoutgoing.js:536:11) at ServerResponse.header (/usr/lib/nodemodules/express/lib/response.js:771:10) at ServerResponse.location (/usr/lib/nodemodules/express/lib/response.js:888:15) at ServerResponse.redirect (/usr/lib/nodemodules/express/lib/response.js:926:18) at /usr/src/app/application.js:90:9 at Layer.handle as handle_request (/usr/lib/node_modules/express/lib/router/layer.js:95:5) at next (/usr/lib/nodemodules/express/lib/router/route.js:137:13) at Route.dispatch (/usr/lib/nodemodules/express/lib/router/route.js:112:3) at Layer.handle as handle_request (/usr/lib/node_modules/express/lib/router/layer.js:95:5) at /usr/lib/node_modules/express/lib/router/index.js:281:22 at Function.logerror (../../lib/node_modules/express/lib/application.js:630:43) Решение нужно подправить по всем упражнениям курса. ","creator":{"public_name":"Павел Ким","id":50933,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":102356,"body":"Приветствую, Павел!\n\n> Можно просто вывести код в else блок и не нужно будет пихать return. \n\nДа, с else тоже будет работать. Проверка на наличие ошибок выделена сейчас как Guard Expression, что позволяет не использовать else и сократить цикломатическую сложность (уменьшить вложенность).\n\n> В решении учителя в app.patch('/posts/:id', () => ...) сейчас return вообще убрали почему-то и тепеперь вылетает вот такая ошибка\n\nОшибку поправил. Спасибо, что обратили внимание.","topic_id":47486}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":10819,"title":"Не работают три последних теста из-за строчки: \nconst url = res1.headers.location.split('/').slice(0, -1).join('/'); \nВ location приходит адрес поста '/posts/3', а после этой обработки url становится равным просто '/posts'.\nТ. е. нужно оставить только url = res1.headers.location;","plain_title":"Не работают три последних теста из-за строчки: const url = res1.headers.location.split('/').slice(0, -1).join('/'); В location приходит адрес поста '/posts/3', а после этой обработки url становится равным просто '/posts'. Т. е. нужно оставить только url = res1.headers.location; ","creator":{"public_name":"Максим Козляков","id":146180,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":22558,"body":"Поправил!","topic_id":10819}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":50127,"title":"Здравствуйте, можно чутка поподробнее.\n\n> Какое ограничение REST нарушает наличие аутентификации?\n> -отсутствие состояния\n\n","plain_title":"Здравствуйте, можно чутка поподробнее. Какое ограничение REST нарушает наличие аутентификации? -отсутствие состояния ","creator":{"public_name":"Daniyar Zhanakhmetov","id":239134,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":107399,"body":"**Daniyar Zhanakhmetov**, приветствую.\n\nЭто отсылка к данному предложению в теории:\n\n> На практике сайты активно используют понятие \"сессии\", при котором данные могут храниться на стороне сервера, что является нарушением REST.\n\nЕсли после аутентификации создана сессия, привязавшая пользователя к конкретному серверу, то это нарушает REST.","topic_id":50127}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":19929,"title":"Прошу подробнее пояснить следующее преимущество: \nПрозрачность системы взаимодействия (особенно необходимая для приложений обслуживания сети)\n\nСовсем не понятно о чем речь, что за приложения обслуживания сети...","plain_title":"Прошу подробнее пояснить следующее преимущество: Прозрачность системы взаимодействия (особенно необходимая для приложений обслуживания сети) Совсем не понятно о чем речь, что за приложения обслуживания сети... ","creator":{"public_name":"Артем Остащенко","id":129599,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":42319,"body":"Если нет скрытого состояния, то очень просто анализировать то что происходит, потому что запрос самодостаточен. Это и есть \"прозрачность\".","topic_id":19929}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":45066,"title":"Добрый вечер!\nСтранные впечатления от задания: \n1. Удаление: в вебе не работает (и почему-то открывает просмотр поста, а не список постов, без удаленного), но в тестах проходит.\n2. Редактирование зато не проходило в тестах изменение поста, хотя в вебе работало как надо (в отличии от прошедшего тест варианта, когда вместо патчинга поста, просто добавляется новый (старый, при этом тоже не исчезает)...\nhttps://ru.hexlet.io/code_reviews/288891\n","plain_title":"Добрый вечер! Странные впечатления от задания: 1. Удаление: в вебе не работает (и почему-то открывает просмотр поста, а не список постов, без удаленного), но в тестах проходит. 2. Редактирование зато не проходило в тестах изменение поста, хотя в вебе работало как надо (в отличии от прошедшего тест варианта, когда вместо патчинга поста, просто добавляется новый (старый, при этом тоже не исчезает)... ","creator":{"public_name":"Алексей Николаев","id":207995,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":97507,"body":"**Алексей Николаев**, приветствую.\n\nТесты проверяют только конкретные запросы на сервер, а не 100% соответствие всем требования. В данном случае надо смотреть как сделано в решении учителя и анализировать собственные ошибки.","topic_id":45066}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":43043,"title":"Не могу понять, что делаю не так: при попытке совершить PATCH-запрос получаю страницу с надписью \"Cannot PATCH /posts\". \nФорма: \n```\nform(action='/posts?_method=PATCH' method='post')\n``` \nкод запроса: \n```\napp.patch('/posts/:id', (req, res) => {...}\n```\n\nТекст ошибки в консоли: \n```\nPOST https://web-js-express-rest-1686352.evaluator6-3.hexlet.io/posts?_method=PATCH 404\n```\n","plain_title":"Не могу понять, что далею не так: при попытке совершить POST-запрос получаю страницу с надписью \"Cannot PATCH /posts\". Форма: form(action='/posts?_method=PATCH' method='post') Текст ошибки в консоли: POST https://web-js-express-rest-1686352.evaluator6-3.hexlet.io/posts?_method=PATCH 404 ","creator":{"public_name":"","id":50614,"is_tutor":false},"comments":[{"creator":{"public_name":"","id":50614,"is_tutor":false},"id":93699,"body":"**Станислав Дзисяк**, спасибо большое ) Этот момент упустил","topic_id":43043},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":93691,"body":"**Dale Barbara**, приветствую!\n\nОбратите внимание на маршрут `'/posts/:id'`. В action формы не хватает передачи id поста.","topic_id":43043}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":16084,"title":"Получается, что мы нарушаем REST требование \"Отсутствие состояния\", храня данные формы на сервере?","plain_title":"Получается, что мы нарушаем REST требование \"Отсутствие состояния\", храня данные формы на сервере? ","creator":{"public_name":"Владислав Прохоров","id":141309,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":34029,"body":"Можете пояснить? Я не очень понял что значит \"данные формы хранятся на сервере\".","topic_id":16084},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":34081,"body":"Ага, текст не очень удачный. Я его поменял.\n\nЗдесь речь идет про сессионное состояние, которое частично хранится на клиенте, а частично в памяти приложения (на конкретном сервере) и помогающее приложению понимать как работать с клиентом в рамках этой сессии. И все это не связано с созданием новых данных в базе. Главное чтобы этот запрос на создание был полностью самодостаточным и не зависел от сессионного состояния.","topic_id":16084},{"creator":{"public_name":"Владислав Прохоров","id":141309,"is_tutor":false},"id":34057,"body":"Например, мы храним title и body формы на сервере, хотя сейчас пишу и понимаю, что это скорее всего не то, иначе как нам выводить посты, не храня состояние на сервере. Просто тогда не совсем понятно требование REST \"Отсутствие состояния\" ведь написано `Запросы должны быть построены таким образом, чтобы сервер получал достаточное количество информации, чтобы выполнить операцию и не сохранять какого-либо состояния.`","topic_id":16084}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":25354,"title":"Собственно повтор вопроса, что bootstrap не подключен, браузер дает такую ошибку\n\n`assets/bootstrap/dist/css/bootstrap.css:1 Failed to load resource: net::ERR_BLOCKED_BY_CLIENT`\n\nИли самим подключать (будет работать?)...","plain_title":"Собственно повтор вопроса, что bootstrap не подключен, браузер дает такую ошибку assets/bootstrap/dist/css/bootstrap.css:1 Failed to load resource: net::ERR_BLOCKED_BY_CLIENT Или самим подключать (будет работать?)... ","creator":{"public_name":"Роман Воробьев","id":115925,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":54592,"body":"> net::ERR_BLOCKED_BY_CLIENT\n\nвопрос в том почему клиент (ваш браузер) блокирует загрузку?","topic_id":25354}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}},{"id":45505,"title":"Доброго времени суток) \nПочему внутри application.js мы оборачиваем всё в функцию из которой возвращаем app? \nТ.е почему мы просто не объявляем всё внутри и не экспортируем по дефолту app, без обёртки в функцию?","plain_title":"Доброго времени суток) Почему внутри application.js мы оборачиваем всё в функцию из которой возвращаем app? Т.е почему мы просто не объявляем всё внутри и не экспортируем по дефолту app, без обёртки в функцию? ","creator":{"public_name":"Евгений Бухаров","id":143484,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":98397,"body":"Приветствую, Евгений!\n\n> Почему внутри application.js мы оборачиваем всё в функцию из которой возвращаем app?\n\nВ случае, если наше приложение будет формироваться на уровне модуля, если нам понадобится запустить несколько приложений, мы не сможем этого сделать. Так как все они будут работать с одним тем же экземпляром app. А так мы можем получать каждый раз новый экземпляр app.","topic_id":45505},{"creator":{"public_name":"Евгений Бухаров","id":143484,"is_tutor":false},"id":98401,"body":"**Станислав Дзисяк**, понял, спасибо!)","topic_id":45505}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"REST","entity_url":null,"active":true}}],"lesson":{"exercise":null,"units":[{"id":1627,"name":"theory","url":"/courses/js-express/lessons/rest/theory_unit"},{"id":1642,"name":"quiz","url":"/courses/js-express/lessons/rest/quiz_unit"}],"links":[{"id":424921,"name":"Что такое HTTP API?","url":"https://guides.hexlet.io/ru/http-api/\n"},{"id":424922,"name":"Руководство по REST API","url":"https://restapitutorial.ru/\n"}],"ordered_units":[{"id":1627,"name":"theory","url":"/courses/js-express/lessons/rest/theory_unit"},{"id":1642,"name":"quiz","url":"/courses/js-express/lessons/rest/quiz_unit"}],"id":790,"slug":"rest","state":"approved","name":"REST","course_order":49,"goal":"Знакомимся поближе с концепцией механизма REST и отсутствием состояния при запросах","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Термин \"REST\" был введён Роем Филдингом, одним из создателей протокола \"HTTP\", лишь в 2000 году. В своей диссертации \"Архитектурные стили и дизайн сетевых программных архитектур\" (\"Architectural Styles and the Design of Network-based Software Architectures\") в Калифорнийском университете в Ирвайне он подвёл теоретическую основу под способ взаимодействия клиентов и серверов во Всемирной паутине, абстрагировав его и назвав \"передачей представительного состояния\".\n\nЧтобы протокол взаимодействия соответствовал REST-стилю, необходимо соблюсти, как минимум, 5 требований. Если сервис соблюдает только часть из них, то про такой протокол говорят, что он REST-like. Если соблюдаются все требования, то протокол является RESTful. На практике, есть ситуации, в которых невозможно следовать REST требованиям, поэтому большинство протоколов являются REST-like, даже если они утверждают другое.\n\n### Единообразие интерфейса\n\n1. Идентификация. В отличие от RPC (Remote Procedure Call), в котором протоколы ориентированы на действия, в REST стиле подразумевается ориентация на ресурсы (существительное). Взаимодействие с каждым ресурсом происходит посредством представлений ресурса, запрашиваемых по URN (Uniform Resource Name), который идентифицирует конкретный ресурс. То есть, мы никогда не взаимодействуем с самим ресурсом напрямую, а получаем лишь его представления, которых может быть много. Сервер может отдать данные в формате json или html, хотя при этом ни один из них не является реальным типом хранения внутри сервера.\n\n1. Манипуляция ресурсами через представление. Если клиент хранит представление ресурса, включая метаданные - он имеет достаточно данных для модификации или удаления ресурса.\n1. \"Самоописываемые\" сообщения. Каждое сообщение содержит достаточно информации, чтобы описать, каким образом его обрабатывать. К примеру, какой парсер необходимо применить для извлечения данных, может быть описано в Internet медиа-типе, другими словами, посылая на сервер json в теле http запроса, REST диктует обязательную установку типа контента в заголовке. В общем случае для обработки сообщения должно быть достаточно информации из самого сообщения (всё, что может передаваться по http).\n\nПо таблице ниже видно, что каждый URN является идентификатором либо одиночного ресурса, либо коллекции ресурсов (это тоже ресурс), а необходимые действия задаются посредством использования подходящего http метода. И всё это должно происходить в строгом соответствии семантике http, другими словами, REST использует то, что заложено в http, а не меняет это или добавляет свое.\n\n| Метод | Маршрут | Описание |\n|-----------|------------------|----------------------------------------------|\n| GET | /photos | display a list of all photos |\n| GET | /photos/new | return an HTML form for creating a new photo |\n| POST | /photos | create a new photo |\n| GET | /photos/:id | display a specific photo |\n| GET | /photos/:id/edit | return an HTML form for editing a photo |\n| PATCH/PUT | /photos/:id | update a specific photo |\n| DELETE | /photos/:id | delete a specific photo |\n\n### Кэширование\n\nКак и во Всемирной паутине, каждый из клиентов, а также промежуточные узлы между сервером и клиентами могут кэшировать ответы сервера. В каждом запросе клиента должно явно содержаться указание о возможности кэширования ответа и получения ответа из существующего кэша. В свою очередь, ответы могут явно или неявно определяться как кэшируемые или некэшируемые для предотвращения повторного использования клиентами в последующих запросах сохранённой информации. Правильное использование кэширования в REST-архитектуре устраняет избыточные клиент-серверные взаимодействия, что улучшает скорость и расширяемость системы.\n\n### Отсутствие состояния\n\nПротокол взаимодействия между клиентом и сервером не сохраняет какого-либо сессионного состояния после запроса и ответа (Stateless protocol). В случае необходимости, такое состояние должно сохраняться на клиенте. Только тогда пользователь отвязан от конкретного сервера, что, в свою очередь, позволяет масштабироваться и безболезненно переносить (балансировать) запросы между серверами.\n\nПримером такого состояния является корзина в интернет магазине. Если она привязана к пользовательской сессии и хранится на конкретном сервере (а не в браузере клиента), то отправить запрос пользователя на другой сервер станет невозможно. Он просто не увидит своей корзины. Эту проблему можно решить двумя способами. Первый - хранить её на клиенте, используя, например, cookie. Вторая - изменением способа хранения корзины на сервере и помещением её в базу данных, которая доступна со всех серверов.\n\nНа практике сайты активно используют понятие \"сессии\", при котором данные могут храниться на стороне сервера, что является нарушением REST.\n\n### Клиент-серверная архитектура\n\nРазграничение потребностей является принципом, лежащим в основе данного накладываемого ограничения. При отделении потребностей интерфейса клиента от потребностей сервера, хранящего данные, повышается переносимость кода клиентского интерфейса на другие платформы, а при упрощении серверной части улучшается масштабируемость.\n\n### Слои\n\nКлиент может взаимодействовать не напрямую с сервером, а через промежуточные узлы (слои). При этом клиент может не знать об их существовании, за исключением случаев передачи конфиденциальной информации. Промежуточные серверы выполняют балансировку нагрузки и могут использовать дополнительное кэширование.\n\n### Код по требованию (необязательное ограничение)\n\nREST может позволить расширить функциональность клиента за счёт загрузки кода с сервера в виде апплетов или сценариев. Филдинг утверждает, что дополнительное ограничение позволяет проектировать архитектуру, поддерживающую желаемую функциональность в общем случае, но возможно за исключением некоторых контекстов.\n\n## Преимущества REST\n\nФилдинг указывал, что приложения, не соответствующие приведённым условиям, не могут называться REST-приложениями. Если же все условия соблюдены, то, по его мнению, приложение получит следующие преимущества:\n\n* Надёжность (за счёт отсутствия необходимости сохранять информацию о состоянии клиента, которая может быть утеряна);\n* Производительность (за счёт использования кэша);\n* Масштабируемость;\n* Прозрачность системы взаимодействия (особенно необходимая для приложений обслуживания сети);\n* Простота интерфейсов;\n* Портативность компонентов;\n* Лёгкость внесения изменений;\n* Способность эволюционировать, приспосабливаясь к новым требованиям (на примере Всемирной паутины).\n\n## По-простому\n\nГрубо говоря, REST это набор рекомендаций о том, как лучше сделать для получения преимуществ, описанных абзацем выше. Чем больше рекомендаций вы выполните, тем более REST получается приложение. А поскольку жизнь сложна, то это нормально, что REST ориентированными сервисы являются только отчасти. Использования REST как догмы ни к чему хорошему не приведёт. И REST никоим образом не заменяет собой RPC. Выбор решения зависит от ситуации и предъявляемых требований.\n\n## JSONAPI\n\nХотя REST и выглядит хорошо, он всё же слишком далек от конкретных реализаций и описывает только фундаментальные аспекты взаимодействия. Конкретные способы организации урлов, передаваемые данные, поведение в случае ошибок и многое другое, придётся продумывать самостоятельно.\n\nЕсть и другой путь. В 2013 году появился стандарт под названием [jsonapi](https://jsonapi.org/), в котором очень подробно описано как создавать `REST`-like сервисы. Для его реализации написано множество библиотек под все популярные языки программирования. Как минимум, я рекомендую с ним ознакомиться, а ещё лучше - взять на вооружение. Использование открытых стандартов в промышленном программировании делает нашу жизнь проще, бизнес богаче, а волосы шелковистее.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1616,"name":"theory","url":"/courses/js-express/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":1616,"name":"theory","url":"/courses/js-express/lessons/intro/theory_unit"}],"id":785,"slug":"intro","state":"approved","name":"Введение","course_order":1,"goal":"Знакомимся с курсом и проектом","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Хотя модуль `http` и даёт нам возможность писать веб-приложения, этот\nспособ нельзя назвать удобным. Сильно помешает отсутствие роутинга и\nудобных механизмов расширения функциональности. И это мы ещё не копнули\nвглубь.\n\nРазработка веб-приложений это, в основном, стандартный процесс с понятным\nнабором \"хотелок\", многие из которых будут пройдены в рамках данного курса.\nЦентральной частью проекта курса является микрофреймворк Express.\n\n```javascript\nimport Express from 'express'\nconst app = new Express()\n\napp.get('/', (req, res) => {\n res.send('Hello World!')\n})\n\napp.listen(3000, () => {\n console.log('Example app listening on port 3000!')\n})\n```\n\nУдивительное дело: курсы, которые вы прошли до этого, гораздо сложнее\nдля понимания и освоения, чем курсы по конкретным инструментам, таким как Express.\nИ, скорее всего, тенденция будет продолжаться. Связано это с тем, что умение\nпрограммировать требует от вас хорошо развитого `computational thinking`\n(вычислительного мышления), включающего в себя много пунктов, помимо абстрактного\nи логического мышления. А работа с конкретным инструментарием больше похожа на\nмонотонный труд в стиле \"делай раз, делай два\". Вся сложность в инструментах, обычно,\nсосредоточена в количестве используемых концепций. Поэтому для новичков документация\nпо Express может показаться нереально сложной без шансов на понимание. Как вы\nскоро убедитесь, это дело наживное, и к концу курса вы сможете уверенно ориентироваться\nв возможностях фреймворка и сможете создавать свои приложения уже совсем по-взрослому.\n\n## Фреймворк\n\n> Программная платформа, определяющая способ структурирования кода приложения\n\nФреймворк противопоставляют понятию библиотека. С библиотеками мы уже хорошо знакомы и писали\nих не раз. В программировании библиотека это код, который может быть использован в программном\nпродукте для выполнения различных подзадач, важно, что при этом библиотека не влияет на архитектуру\nприложения и не накладывает на неё ограничений.\n\nФреймворк это, как ни странно, тоже код, который диктует правила построения\nархитектуры приложения, задавая на начальном этапе разработки поведение\nпо умолчанию — \"каркас\", который нужно будет расширять и изменять\nсогласно указанным требованиям.\n\nМожет показаться, что фреймворк — это штука, которая только мешает, но это не так. Большинство\nприложений укладывается в некоторые стандартные рамки, соблюдая которые можно автоматизировать\nочень много задач и писать намного меньше кода. А ещё это возможность создавать и\nпереиспользовать библиотеки, ориентированные на работу с фреймворками. В современном мире\nпопулярный каркас идет в нагрузку с сотнями полезных расширений, которые за вас\nделают всё, что только можно вообразить, за пивом только не ходят.\n\n## Микрофреймворк\n\nУстоявшееся название для минималистичных веб-фреймворков.\n\n* Представляет из себя набор `middlewares` (описываются позже)\n* Определяет прямую связь между маршрутом и обработчиком\n* Не определяет файловую структуру\n* Содержит минимальное количество встроенных возможностей\n\n```javascript\n// HTTP Verb + Route + Handler.\napp.get('/', (req, res) => {\n res.send('Hello World!')\n})\n```\n\nExpress как раз относится к классу микрофреймворков. Очень популярное направление,\nродоначальником которого считается `Sinatra`, Ruby-микрофреймворк, появившийся в далеком\n2007 году. С тех пор в каждом языке появились десятки подобных решений, среди которых\nесть минимум один-два очень популярных. Получается, что зная Express вам будет\nнесложно начать работать с подобным микрофреймворком на любом другом языке.\n\n## Проект\n\nПоскольку мы метим в веб-разработчики, то будет грехом не написать свой блог :)\nВ процессе создания блога мы рассмотрим следующие темы:\n\n* Express JS (Middlewares)\n* Роутинг\n* Логгирование\n* Функциональное тестирование\n* Шаблонизация\n* REST\n* Session\n* Twitter Bootstrap\n* Авторизация/Аутентификация\n* Flash\n\n## Веб-доступ\n\nВ этом курсе, почти в каждом задании будет открыт веб-доступ, через который можно и нужно\nсмотреть на то, что вы сделали. Веб-сервер по умолчанию не запущен. Для запуска наберите\n`make start` в терминале, убедитесь, что сервер нормально запустился, и пробуйте пользоваться.\nЭто справедливо для любого задания курса, в котором есть веб-доступ.\n"},"id":133,"slug":"js-express","challenges_count":0,"name":"JS: Express","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"free","description":"На этом курсе вы изучите микрофреймворк Express. Вы узнаете о роутинге, шаблонизации и мидлварах. В итоге познакомитесь с архитектурами REST и MVC. Express пригодится, если вы решите создать веб-приложение. Знания из этого курса помогают программистам использовать логгирование и отлаживать ошибки.","kind":"sandbox","updated_at":"2026-01-20T11:50:46.001Z","language":"javascript","duration_cache":14040,"skills":["Создавать полноценные сайты на самом популярном в Node.js мире фреймворке","Организовывать код в соответствии с MVC и REST архитектурами","Эффективно отлаживать ошибки в коде сайта используя логгирование"],"keywords":["шаблонизация","REST","мидлвары","сессия","тестирование"],"lessons_count":13,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODIzOCwicHVyIjoiYmxvYl9pZCJ9fQ==--6cfcb045ed646b0f361a4f0b6280e670356e3bdf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[],"lessonMemberUnit":null,"accessToLearnUnitExists":true,"accessToCourseExists":true},"url":"/courses/js-express/lessons/rest/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">JS: Express</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">Теория: REST</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"REST","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"JS: Express"},"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>Термин "REST" был введён Роем Филдингом, одним из создателей протокола "HTTP", лишь в 2000 году. В своей диссертации "Архитектурные стили и дизайн сетевых программных архитектур" ("Architectural Styles and the Design of Network-based Software Architectures") в Калифорнийском университете в Ирвайне он подвёл теоретическую основу под способ взаимодействия клиентов и серверов во Всемирной паутине, абстрагировав его и назвав "передачей представительного состояния".</p>
<p>Чтобы протокол взаимодействия соответствовал REST-стилю, необходимо соблюсти, как минимум, 5 требований. Если сервис соблюдает только часть из них, то про такой протокол говорят, что он REST-like. Если соблюдаются все требования, то протокол является RESTful. На практике, есть ситуации, в которых невозможно следовать REST требованиям, поэтому большинство протоколов являются REST-like, даже если они утверждают другое.</p>
<h3 id="heading-3-1">Единообразие интерфейса</h3>
<ol>
<li>
<p>Идентификация. В отличие от RPC (Remote Procedure Call), в котором протоколы ориентированы на действия, в REST стиле подразумевается ориентация на ресурсы (существительное). Взаимодействие с каждым ресурсом происходит посредством представлений ресурса, запрашиваемых по URN (Uniform Resource Name), который идентифицирует конкретный ресурс. То есть, мы никогда не взаимодействуем с самим ресурсом напрямую, а получаем лишь его представления, которых может быть много. Сервер может отдать данные в формате json или html, хотя при этом ни один из них не является реальным типом хранения внутри сервера.</p>
</li>
<li>
<p>Манипуляция ресурсами через представление. Если клиент хранит представление ресурса, включая метаданные - он имеет достаточно данных для модификации или удаления ресурса.</p>
</li>
<li>
<p>"Самоописываемые" сообщения. Каждое сообщение содержит достаточно информации, чтобы описать, каким образом его обрабатывать. К примеру, какой парсер необходимо применить для извлечения данных, может быть описано в Internet медиа-типе, другими словами, посылая на сервер json в теле http запроса, REST диктует обязательную установку типа контента в заголовке. В общем случае для обработки сообщения должно быть достаточно информации из самого сообщения (всё, что может передаваться по http).</p>
</li>
</ol>
<p>По таблице ниже видно, что каждый URN является идентификатором либо одиночного ресурса, либо коллекции ресурсов (это тоже ресурс), а необходимые действия задаются посредством использования подходящего http метода. И всё это должно происходить в строгом соответствии семантике http, другими словами, REST использует то, что заложено в http, а не меняет это или добавляет свое.</p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Описание</th></tr></thead><tbody><tr><td>GET</td><td>/photos</td><td>display a list of all photos</td></tr><tr><td>GET</td><td>/photos/new</td><td>return an HTML form for creating a new photo</td></tr><tr><td>POST</td><td>/photos</td><td>create a new photo</td></tr><tr><td>GET</td><td>/photos/<div></div></td><td>display a specific photo</td></tr><tr><td>GET</td><td>/photos/<div></div>/edit</td><td>return an HTML form for editing a photo</td></tr><tr><td>PATCH/PUT</td><td>/photos/<div></div></td><td>update a specific photo</td></tr><tr><td>DELETE</td><td>/photos/<div></div></td><td>delete a specific photo</td></tr></tbody></table></div></div></div></div>
<h3 id="heading-3-2">Кэширование</h3>
<p>Как и во Всемирной паутине, каждый из клиентов, а также промежуточные узлы между сервером и клиентами могут кэшировать ответы сервера. В каждом запросе клиента должно явно содержаться указание о возможности кэширования ответа и получения ответа из существующего кэша. В свою очередь, ответы могут явно или неявно определяться как кэшируемые или некэшируемые для предотвращения повторного использования клиентами в последующих запросах сохранённой информации. Правильное использование кэширования в REST-архитектуре устраняет избыточные клиент-серверные взаимодействия, что улучшает скорость и расширяемость системы.</p>
<h3 id="heading-3-3">Отсутствие состояния</h3>
<p>Протокол взаимодействия между клиентом и сервером не сохраняет какого-либо сессионного состояния после запроса и ответа (Stateless protocol). В случае необходимости, такое состояние должно сохраняться на клиенте. Только тогда пользователь отвязан от конкретного сервера, что, в свою очередь, позволяет масштабироваться и безболезненно переносить (балансировать) запросы между серверами.</p>
<p>Примером такого состояния является корзина в интернет магазине. Если она привязана к пользовательской сессии и хранится на конкретном сервере (а не в браузере клиента), то отправить запрос пользователя на другой сервер станет невозможно. Он просто не увидит своей корзины. Эту проблему можно решить двумя способами. Первый - хранить её на клиенте, используя, например, cookie. Вторая - изменением способа хранения корзины на сервере и помещением её в базу данных, которая доступна со всех серверов.</p>
<p>На практике сайты активно используют понятие "сессии", при котором данные могут храниться на стороне сервера, что является нарушением REST.</p>
<h3 id="heading-3-4">Клиент-серверная архитектура</h3>
<p>Разграничение потребностей является принципом, лежащим в основе данного накладываемого ограничения. При отделении потребностей интерфейса клиента от потребностей сервера, хранящего данные, повышается переносимость кода клиентского интерфейса на другие платформы, а при упрощении серверной части улучшается масштабируемость.</p>
<h3 id="heading-3-5">Слои</h3>
<p>Клиент может взаимодействовать не напрямую с сервером, а через промежуточные узлы (слои). При этом клиент может не знать об их существовании, за исключением случаев передачи конфиденциальной информации. Промежуточные серверы выполняют балансировку нагрузки и могут использовать дополнительное кэширование.</p>
<h3 id="heading-3-6">Код по требованию (необязательное ограничение)</h3>
<p>REST может позволить расширить функциональность клиента за счёт загрузки кода с сервера в виде апплетов или сценариев. Филдинг утверждает, что дополнительное ограничение позволяет проектировать архитектуру, поддерживающую желаемую функциональность в общем случае, но возможно за исключением некоторых контекстов.</p>
<h2 id="heading-2-7">Преимущества REST</h2>
<p>Филдинг указывал, что приложения, не соответствующие приведённым условиям, не могут называться REST-приложениями. Если же все условия соблюдены, то, по его мнению, приложение получит следующие преимущества:</p>
<ul>
<li>Надёжность (за счёт отсутствия необходимости сохранять информацию о состоянии клиента, которая может быть утеряна);</li>
<li>Производительность (за счёт использования кэша);</li>
<li>Масштабируемость;</li>
<li>Прозрачность системы взаимодействия (особенно необходимая для приложений обслуживания сети);</li>
<li>Простота интерфейсов;</li>
<li>Портативность компонентов;</li>
<li>Лёгкость внесения изменений;</li>
<li>Способность эволюционировать, приспосабливаясь к новым требованиям (на примере Всемирной паутины).</li>
</ul>
<h2 id="heading-2-8">По-простому</h2>
<p>Грубо говоря, REST это набор рекомендаций о том, как лучше сделать для получения преимуществ, описанных абзацем выше. Чем больше рекомендаций вы выполните, тем более REST получается приложение. А поскольку жизнь сложна, то это нормально, что REST ориентированными сервисы являются только отчасти. Использования REST как догмы ни к чему хорошему не приведёт. И REST никоим образом не заменяет собой RPC. Выбор решения зависит от ситуации и предъявляемых требований.</p>
<h2 id="heading-2-9">JSONAPI</h2>
<p>Хотя REST и выглядит хорошо, он всё же слишком далек от конкретных реализаций и описывает только фундаментальные аспекты взаимодействия. Конкретные способы организации урлов, передаваемые данные, поведение в случае ошибок и многое другое, придётся продумывать самостоятельно.</p>
<p>Есть и другой путь. В 2013 году появился стандарт под названием <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://jsonapi.org/" rel="noopener noreferrer" target="_blank">jsonapi</a>, в котором очень подробно описано как создавать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">REST</code>-like сервисы. Для его реализации написано множество библиотек под все популярные языки программирования. Как минимум, я рекомендую с ним ознакомиться, а ещё лучше - взять на вооружение. Использование открытых стандартов в промышленном программировании делает нашу жизнь проще, бизнес богаче, а волосы шелковистее.</p></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/js-express/lessons/rest/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/js-express/lessons/rest/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>