{"id":336530,"date":"2022-08-04T09:00:11","date_gmt":"2022-08-04T09:00:11","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=336530"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=336530","title":{"rendered":"<span>\u0420\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0447\u0430\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Nest, React \u0438 Postgres<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/ma\/po\/lv\/mapolvqq4uunxfqoaviv3g9km9y.jpeg\" data-src=\"https:\/\/habrastorage.org\/webt\/ma\/po\/lv\/mapolvqq4uunxfqoaviv3g9km9y.jpeg\" data-blurred=\"true\"\/>  <\/p>\n<p>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435 \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0447\u0430\u0442 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439:<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a> \u2014 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0442\u043e\u0440;<\/li>\n<li><a href=\"https:\/\/nestjs.com\/\">NestJS<\/a> \u2014 \u0441\u0435\u0440\u0432\u0435\u0440;<\/li>\n<li><a href=\"https:\/\/socket.io\/\">Socket.IO<\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0432 [\u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u0430\u043c\u0438]();<\/li>\n<li><a href=\"https:\/\/ru.reactjs.org\/\">React<\/a> \u2014 \u043a\u043b\u0438\u0435\u043d\u0442;<\/li>\n<li><a href=\"https:\/\/tailwindcss.com\/\">TailwindCSS<\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0441\u0442\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u0438;<\/li>\n<li><a href=\"https:\/\/www.postgresql.org\/\">PostgreSQL<\/a> \u2014 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0434\u0430\u043b\u0435\u0435 \u2014 \u0411\u0414);<\/li>\n<li><a href=\"https:\/\/www.prisma.io\/\">Prisma<\/a> \u2014 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/ORM\">ORM<\/a>;<\/li>\n<li><a href=\"https:\/\/www.docker.com\/\">Docker<\/a> \u2014 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435 \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0447\u0430\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0444\u0435\u0439\u043a\u043e\u0432\u0430\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439:<br \/> \n<ul>\n<li>\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 (\u043e\u0431\u044a\u0435\u043a\u0442\u0435) \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435;<\/li>\n<li>\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d \u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 <a href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/Window\/localStorage\">localStorage<\/a> \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435;<\/li>\n<\/ul>\n<\/li>\n<li>\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u044d\u0442\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c;<\/li>\n<li>\u0437\u0430\u043f\u0438\u0441\u044c, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438\u0437 \u0411\u0414 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u044d\u0442\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c.<\/li>\n<\/ul>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/react-nest-postgres-chat\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b \u0434\u043b\u044f \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f (\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e):<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/ts\/\">\u041a\u0430\u0440\u043c\u0430\u043d\u043d\u0430\u044f \u043a\u043d\u0438\u0433\u0430 \u043f\u043e TS<\/a>;<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/cheatsheet\/ts\/\">\u0428\u043f\u0430\u0440\u0433\u0430\u043b\u043a\u0430 \u043f\u043e TS<\/a>;<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/cheatsheet\/react-typescript\/\">\u0428\u043f\u0430\u0440\u0433\u0430\u043b\u043a\u0430 \u043f\u043e React + TS<\/a>;<\/li>\n<li><a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/666470\/\">\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e NestJS<\/a>;<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/socket\/\">\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Socket.IO<\/a>;<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/prisma\/\">\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Prisma<\/a>;<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/docker\/\">\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Docker<\/a>.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0434\u043b\u044f <a href=\"https:\/\/code.visualstudio.com\/\">VSCode<\/a> (\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e):<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-azuretools.vscode-docker\">Docker<\/a>;<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=dsznajder.es7-react-js-snippets\">ES7+ React\/Redux\/React-Native\/JS snippets<\/a>;<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=dbaeumer.vscode-eslint\">ESLint<\/a>;<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=esbenp.prettier-vscode\">Prettier \u2014 Code formatter<\/a>;<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=Prisma.prisma\">Prisma<\/a>;<\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=bradlc.vscode-tailwindcss\">Tailwind CSS IntelliSense<\/a>.<\/li>\n<\/ul>\n<p>  <\/p>\n<h2 id=\"podgotovka-i-nastroyka-proekta\">\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h2>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u0434\u043b\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430 \u043d\u0430 \u0432\u0430\u0448\u0435\u0439 \u043c\u0430\u0448\u0438\u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u044b <a href=\"https:\/\/nodejs.org\/en\/download\/\">Node.js<\/a> \u0438 <a href=\"https:\/\/www.docker.com\/products\/docker-desktop\/\">Docker<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f <a href=\"https:\/\/yarnpkg.com\/\">Yarn<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043d\u0435\u0435 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c <code>Node.js-\u043f\u0440\u043e\u0435\u043a\u0442<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">mkdir react-nest-postgres-chat cd react-nest-postgres-chat  yarn init -yp<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"baza-dannyh\">\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b <code>docker-compose.yml<\/code> \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">services:   # \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430   postgres:     # \u043e\u0431\u0440\u0430\u0437     image: postgres     # \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430     restart: on-failure     # \u0444\u0430\u0439\u043b \u0441 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f     env_file:       - .env     # \u043f\u043e\u0440\u0442\u044b     ports:       - 5432:5432     # \u0442\u043e\u043c\u0430 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0433\u043e \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445     volumes:       - postgres-data:\/var\/lib\/postgresql\/data  volumes:   postgres-data:<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b <code>.env<\/code> \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0432 \u043d\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f <code>Postgres<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres POSTGRES_DB=chat<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440 <code>Postgres<\/code> \u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">docker compose up -d<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 <code>react-nest-postgres-chat-1<\/code> \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c <code>Postgres<\/code> \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u0435 <code>react-nest-postgres-chat<\/code>.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/_m\/nq\/q8\/_mnqq8km8zqt-n9bnbvucdsnhzm.png\" data-src=\"https:\/\/habrastorage.org\/webt\/_m\/nq\/q8\/_mnqq8km8zqt-n9bnbvucdsnhzm.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0411\u0414 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code>http:\/\/localhost:5432\/chat<\/code>.<\/p>\n<p>  <\/p>\n<h3 id=\"orm\">ORM<\/h3>\n<p>  <\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c <code>Prisma<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0435\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">yarn add -D prisma  prisma init<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0444\u0430\u0439\u043b\u0430 <code>prisma\/schema.prisma<\/code>. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0432 \u043d\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">model Message {   id        Int      @id @default(autoincrement())   userId    String   userName  String   text      String   createdAt DateTime @default(now()) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0441\u043e \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>.env<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">DATABASE_URL=postgresql:\/\/${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432\/${POSTGRES_DB}?schema=public<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">yarn prisma migrate dev --name init<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>migrations<\/code> \u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0439 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0435\u0439 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/SQL\">SQL<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 <code>Prisma<\/code> \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0442\u0438\u043f\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">yarn add @prisma\/client  yarn prisma generate<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"server-i-klient\">\u0421\u0435\u0440\u0432\u0435\u0440 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442<\/h3>\n<p>  <\/p>\n<p>\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c <a href=\"https:\/\/docs.nestjs.com\/cli\/overview\">Nest CLI<\/a> \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c <code>Nest-\u043f\u0440\u043e\u0435\u043a\u0442<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">yarn global add @nestjs\/cli  # \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c `yarn` \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438 nest new server<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d <code>React + TS<\/code> \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/www.npmjs.com\/package\/create-vite\">create-vite<\/a>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\"># client - \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (\u0438 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438) # react-ts - \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d yarn create vite client --template react-ts<\/code><\/pre>\n<p>  <\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c <a href=\"https:\/\/www.npmjs.com\/package\/concurrently\">concurrently<\/a>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">yarn add concurrently<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>package.json<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"json\">\"scripts\": {   \"dev:client\": \"yarn --cwd client dev\",   \"dev:server\": \"yarn --cwd server start:dev\",   \"dev\": \"concurrently \\\"yarn dev:client\\\" \\\"yarn dev:server\\\"\" }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>yarn dev<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u044b. \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/p>\n<p>  <\/p>\n<h2 id=\"server\">\u0421\u0435\u0440\u0432\u0435\u0440<\/h2>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0443\u043b\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u043e\u043a\u0435\u0442\u0430\u043c\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">cd server  yarn add @nestjs\/websockets @nestjs\/platform-socket.io<\/code><\/pre>\n<p>  <\/p>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0448\u043b\u044e\u0437 (gateway) \u0434\u043b\u044f \u043c\u043e\u0434\u0443\u043b\u044f <code>App<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\"># g ga - generate gateway nest g ga app<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u043e\u0434\u0438\u043c \u0444\u0430\u0439\u043b\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">- src   - app.gateway.ts   - app.module.ts   - app.service.ts   - main.ts   - prisma.service.ts - constants.ts - types.ts - ...<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441 <code>Prisma<\/code> \u0432 \u0444\u0430\u0439\u043b\u0435 <code>prisma.service.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { INestApplication, Injectable, OnModuleInit } from \"@nestjs\/common\"; import { PrismaClient } from \"@prisma\/client\";  @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit {   async onModuleInit() {     await this.$connect();   }    async enableShutdownHooks(app: INestApplication) {     this.$on(\"beforeExit\", async () => {       await app.close();     });   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>main.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { NestFactory } from \"@nestjs\/core\"; import { AppModule } from \".\/app.module\"; \/\/ ! import { PrismaService } from \".\/prisma.service\";  async function bootstrap() {   const app = await NestFactory.create(AppModule);    \/\/ !   const prismaService = app.get(PrismaService);   await prismaService.enableShutdownHooks(app);    \/\/ \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442   await app.listen(3001); } bootstrap();<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0435\u0433\u043e \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0432 \u043c\u043e\u0434\u0443\u043b\u0435 <code>App<\/code> (<code>app.module.ts<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Module } from \"@nestjs\/common\"; import { AppService } from \".\/app.service\"; \/\/ ! import { PrismaService } from \".\/prisma.service\";  @Module({   imports: [],   controllers: [],   \/\/ !   providers: [PrismaService, AppService] }) export class AppModule {}<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0430\u0434\u0440\u0435\u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>constants.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">export const CLIENT_URI = \"http:\/\/localhost:3000\";<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0442\u0438\u043f \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 <code>types.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Prisma } from \"@prisma\/client\";  \/\/ { id?: number, text?: string } export type MessageUpdatePayload = Prisma.MessageWhereUniqueInput &amp;   Pick&lt;Prisma.MessageUpdateInput, \"text\">;<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>app.service.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Injectable } from \"@nestjs\/common\"; import { Message, Prisma } from \"@prisma\/client\"; import { MessageUpdatePayload } from \"types\"; import { PrismaService } from \".\/prisma.service\";  @Injectable() export class AppService {   \/\/ \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 `Prisma`   constructor(private readonly prisma: PrismaService) {}    \/\/ \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439   async getMessages(): Promise&lt;Message[]> {     return this.prisma.message.findMany();   }    \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 - \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438   async clearMessages(): Promise&lt;Prisma.BatchPayload> {     return this.prisma.message.deleteMany();   }    \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   async createMessage(data: Prisma.MessageCreateInput) {     return this.prisma.message.create({ data });   }    \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   async updateMessage(payload: MessageUpdatePayload) {     const { id, text } = payload;     return this.prisma.message.update({ where: { id }, data: { text } });   }    \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   async removeMessage(where: Prisma.MessageWhereUniqueInput) {     return this.prisma.message.delete({ where });   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0441\u043e\u043a\u0435\u0442\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>app.gateway.ts<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u043e\u0447\u0435\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0438\u0441\u0435\u0439 &#171;\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u043e\u043a\u0435\u0442\u0430 \u2014 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f&#187;:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import {   MessageBody,   OnGatewayConnection,   OnGatewayDisconnect,   OnGatewayInit,   SubscribeMessage,   WebSocketGateway,   WebSocketServer } from \"@nestjs\/websockets\"; import { Prisma } from \"@prisma\/client\"; import { Server, Socket } from \"Socket.IO\"; import { MessageUpdatePayload } from \"types\"; import { CLIENT_URI } from \"..\/constants\"; import { AppService } from \".\/app.service\";  const users: Record&lt;string, string> = {};<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u043a\u0435\u0442-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0448\u043b\u044e\u0437 <code>App<\/code> \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0432 \u043d\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441 <code>App<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">@WebSocketGateway({   cors: {     origin: CLIENT_URI \/\/ \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c `*` \u0434\u043b\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f `CORS`   },   serveClient: false,   \/\/ \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043b\u044e\u0431\u044b\u043c, \u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435   namespace: \"chat\" }) export class AppGateway   implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {   constructor(private readonly appService: AppService) {}    \/**    * @todo   *\/ }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u043a\u0435\u0442-\u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">@WebSocketServer() server: Server;  afterInit(server: Server) {   console.log(server); }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 handleConnection(client: Socket, ...args: any[]) {   \/\/ \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 `handshake`   const userName = client.handshake.query.userName as string;   const socketId = client.id;   users[socketId] = userName;    \/\/ \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432\u0441\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c, \u043a\u0440\u043e\u043c\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e   client.broadcast.emit(\"log\", `${userName} connected`); }  \/\/ \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 handleDisconnect(client: Socket) {   const socketId = client.id;   const userName = users[socketId];   delete users[socketId];    client.broadcast.emit(\"log\", `${userName} disconnected`); }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0441\u043e\u043a\u0435\u0442\u043e\u0432:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 @SubscribeMessage(\"messages:get\") async handleMessagesGet(): Promise&lt;void> {   const messages = await this.appService.getMessages();   this.server.emit(\"messages\", messages); }  \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 @SubscribeMessage(\"messages:clear\") async handleMessagesClear(): Promise&lt;void> {   await this.appService.clearMessages(); }  \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f @SubscribeMessage(\"message:post\") async handleMessagePost(   @MessageBody()   payload: \/\/ { userId: string, userName: string, text: string }   Prisma.MessageCreateInput ): Promise&lt;void> {   const createdMessage = await this.appService.createMessage(payload);   \/\/ \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u043e \u043a\u0430\u0436\u0434\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438   this.server.emit(\"message:post\", createdMessage);   \/\/ \u043d\u043e \u043c\u044b \u043f\u043e\u0439\u0434\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u043f\u0443\u0442\u0435\u043c   this.handleMessagesGet(); }  \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f @SubscribeMessage(\"message:put\") async handleMessagePut(   @MessageBody()   payload: \/\/ { id: number, text: string }   MessageUpdatePayload ): Promise&lt;void> {   const updatedMessage = await this.appService.updateMessage(payload);   this.server.emit(\"message:put\", updatedMessage);   this.handleMessagesGet(); }  \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f @SubscribeMessage(\"message:delete\") async handleMessageDelete(   @MessageBody()   payload: \/\/ { id: number }   Prisma.MessageWhereUniqueInput ) {   const removedMessage = await this.appService.removeMessage(payload);   this.server.emit(\"message:delete\", removedMessage);   this.handleMessagesGet(); }<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 app.gateway.ts:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {   MessageBody,   OnGatewayConnection,   OnGatewayDisconnect,   OnGatewayInit,   SubscribeMessage,   WebSocketGateway,   WebSocketServer } from \"@nestjs\/websockets\"; import { Prisma } from \"@prisma\/client\"; import { Server, Socket } from \"socket.io\"; import { MessageUpdatePayload } from \"types\"; import { CLIENT_URI } from \"..\/constants\"; import { AppService } from \".\/app.service\";  const users: Record&lt;string, string> = {};  @WebSocketGateway({   cors: {     origin: CLIENT_URI   },   serveClient: false,   namespace: \"chat\" }) export class AppGateway   implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {   constructor(private readonly appService: AppService) {}    @WebSocketServer() server: Server;    @SubscribeMessage(\"messages:get\")   async handleMessagesGet(): Promise&lt;void> {     const messages = await this.appService.getMessages();     this.server.emit(\"messages\", messages);   }    @SubscribeMessage(\"messages:clear\")   async handleMessagesClear(): Promise&lt;void> {     await this.appService.clearMessages();   }    @SubscribeMessage(\"message:post\")   async handleMessagePost(     @MessageBody()     payload: \/\/ { userId: string, userName: string, text: string }     Prisma.MessageCreateInput   ): Promise&lt;void> {     const createdMessage = await this.appService.createMessage(payload);     this.server.emit(\"message:post\", createdMessage);     this.handleMessagesGet();   }    @SubscribeMessage(\"message:put\")   async handleMessagePut(     @MessageBody()     payload: \/\/ { id: number, text: string }     MessageUpdatePayload   ): Promise&lt;void> {     const updatedMessage = await this.appService.updateMessage(payload);     this.server.emit(\"message:put\", updatedMessage);     this.handleMessagesGet();   }    @SubscribeMessage(\"message:delete\")   async handleMessageDelete(     @MessageBody()     payload: \/\/ { id: number }     Prisma.MessageWhereUniqueInput   ) {     const removedMessage = await this.appService.removeMessage(payload);     this.server.emit(\"message:delete\", removedMessage);     this.handleMessagesGet();   }    afterInit(server: Server) {     console.log(server);   }    handleConnection(client: Socket, ...args: any[]) {     const userName = client.handshake.query.userName as string;     const socketId = client.id;     users[socketId] = userName;      client.broadcast.emit(\"log\", `${userName} connected`);   }    handleDisconnect(client: Socket) {     const socketId = client.id;     const userName = users[socketId];     delete users[socketId];      client.broadcast.emit(\"log\", `${userName} disconnected`);   } }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0448\u043b\u044e\u0437 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0432 \u043c\u043e\u0434\u0443\u043b\u0435 <code>App<\/code> (<code>app.module.ts<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Module } from \"@nestjs\/common\"; import { AppService } from \".\/app.service\"; \/\/ ! import { AppGateway } from \".\/app.gateway\"; import { PrismaService } from \".\/prisma.service\";  @Module({   imports: [],   controllers: [],   \/\/ !   providers: [PrismaService, AppService, AppGateway] }) export class AppModule {}<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<p>  <\/p>\n<h2 id=\"klient\">\u041a\u043b\u0438\u0435\u043d\u0442<\/h2>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c <code>Tailwind<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">cd client  yarn add -D tailwindcss postcss autoprefixer yarn tailwindcss init -p<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b <code>tailwind.config.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/** @type {import('tailwindcss').Config} *\/ module.exports = {   \/\/ !   content: ['.\/src\/**\/*.{ts,tsx}'],   theme: {     extend: {}   },   plugins: [] }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u044b <code>@tailwind<\/code> \u0432 \u0444\u0430\u0439\u043b <code>App.css<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"css\">@tailwind base; @tailwind components; @tailwind utilities;<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439 \u0448\u0440\u0438\u0444\u0442 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0441\u0442\u0438\u043b\u0435\u0439 (reused styles):<\/p>\n<p>  <\/p>\n<pre><code class=\"css\">@layer base {   html {     font-family: \"Montserrat\", sans-serif;   } }  @layer components {   #root {     width: 100vw;     height: 100vh;     overflow: hidden;   }    .title {     @apply mb-4 text-2xl text-center font-bold;   }    .btn {     @apply py-2 px-4 text-white rounded-md shadow-md focus:outline-none focus:ring-2 focus:ring-opacity-75 transition-all duration-150;   }    .btn-primary {     @apply btn bg-blue-500 hover:bg-blue-600 focus:ring-blue-400;   }    .btn-success {     @apply btn bg-green-500 hover:bg-green-600 focus:ring-green-400;   }    .btn-error {     @apply btn bg-red-500 hover:bg-red-600 focus:ring-red-400;   }    .input {     @apply py-2 px-4 border border-blue-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400 transition-all duration-150;   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0448\u0440\u0438\u0444\u0442 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>index.html<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"html\">&lt;!DOCTYPE html> &lt;html lang=\"en\">   &lt;head>     &lt;meta charset=\"UTF-8\" \/>     &lt;link rel=\"icon\" href=\"data:.\" \/>     &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>     &lt;title>React Nest Postgres Chat&lt;\/title>     &lt;!-- ! -->     &lt;link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\" \/>     &lt;link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin \/>     &lt;link       href=\"https:\/\/fonts.googleapis.com\/css2?family=Montserrat&amp;display=swap\"       rel=\"stylesheet\"     \/>     &lt;!-- ! -->   &lt;\/head>   &lt;body>     &lt;div id=\"root\">&lt;\/div>     &lt;script type=\"module\" src=\"\/src\/main.tsx\">&lt;\/script>   &lt;\/body> &lt;\/html><\/code><\/pre>\n<p>  <\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">yarn add socket.io-client react-icons react-timeago react-toastify<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/Socket.IO-client\">socket.io-client<\/a> \u2014 \u043a\u043b\u0438\u0435\u043d\u0442 <code>Socket.IO<\/code>;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/react-icons\">react-icons<\/a> \u2014 \u0438\u043a\u043e\u043d\u043a\u0438 \u0432 \u0432\u0438\u0434\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/react-timeago\">react-timeago<\/a> \u2014 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u0442\u044b \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/react-toastify\">react-toastify<\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0438\u0445 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u043e\u0434\u0438\u043c \u0444\u0430\u0439\u043b\u044b \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">- src   - components     - ChatScreen.tsx     - index.ts     - WelcomeScreen.tsx   - hooks     - useChat.ts   - App.css   - App.tsx   - constants.ts   - main.ts   - types.ts   - utils.ts - postcss.config.js - tailwind.config.js - ...<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u0432 \u0444\u0430\u0439\u043b\u0435 <code>constants.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043a\u043b\u044e\u0447 \u0434\u043b\u044f `localStorage` export const USER_INFO = \"user-info\"; \/\/ \u0430\u0434\u0440\u0435\u0441 \u0448\u043b\u044e\u0437\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 export const SERVER_URI = \"http:\/\/localhost:3001\/chat\";<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u0438\u043f\u044b \u0432 \u0444\u0430\u0439\u043b\u0435 <code>types.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Prisma } from \"@prisma\/client\";  export type UserInfo = {   userId: string;   userName: string; };  export type MessageUpdatePayload = Prisma.MessageWhereUniqueInput &amp;   Pick&lt;Prisma.MessageUpdateInput, \"text\">;<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0432 \u0444\u0430\u0439\u043b\u0435 <code>utils.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f export const getId = () => Math.random().toString(36).slice(2);  \/\/ \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 `localStorage` export const storage = {   set&lt;T>(key: string, value: T) {     localStorage.setItem(key, JSON.stringify(value));   },   get&lt;T>(key: string): T | null {     return localStorage.getItem(key)       ? JSON.parse(localStorage.getItem(key) as string)       : null;   } };<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c, \u043f\u043e\u0436\u0430\u043b\u0443\u0439, \u0441 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (<code>App.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import \"react-toastify\/dist\/ReactToastify.css\"; import \".\/App.css\"; import { ChatScreen, WelcomeScreen } from \".\/components\"; import { USER_INFO } from \".\/constants\"; import { UserInfo } from \".\/types\"; import { storage } from \".\/utils\";  function App() {   const userInfo = storage.get&lt;UserInfo>(USER_INFO);    return (     &lt;section className=\"w-[480px] h-full mx-auto flex flex-col py-4\">       {userInfo ? &lt;ChatScreen \/> : &lt;WelcomeScreen \/>}     &lt;\/section>   ); }  export default App;<\/code><\/pre>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435, \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u044d\u043a\u0440\u0430\u043d \u0447\u0430\u0442\u0430. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u044d\u043a\u0440\u0430\u043d \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u042d\u043a\u0440\u0430\u043d \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f (<code>ChatScreen.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import React, { useState } from \"react\"; import { FiUser } from \"react-icons\/fi\"; import { USER_INFO } from \"..\/constants\"; import { UserInfo } from \"..\/types\"; import { getId, storage } from \"..\/utils\";  export const WelcomeScreen = () => {   const [userName, setUserName] = useState(\"\");    const changeUserName = (e: React.ChangeEvent&lt;HTMLInputElement>) => {     setUserName(e.target.value);   };    const setUserInfo = (e: React.FormEvent) => {     e.preventDefault();      const trimmed = userName.trim();     if (!trimmed) return;      \/\/ \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     const userId = getId();     \/\/ \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435     storage.set&lt;UserInfo>(USER_INFO, { userName: trimmed, userId });      \/\/ \u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043b\u043e\u043a\u0430\u0446\u0438\u044e     location.reload();   };    return (     &lt;section>       &lt;h1 className=\"title\">Welcome, friend!&lt;\/h1>       &lt;form onSubmit={setUserInfo} className=\"flex flex-col items-center gap-4\">         &lt;div className=\"flex flex-col gap-2\">           &lt;label             htmlFor=\"username\"             className=\"text-lg flex items-center justify-center\"           >             &lt;span className=\"mr-1\">               &lt;FiUser \/>             &lt;\/span>             &lt;span>What is your name?&lt;\/span>           &lt;\/label>           &lt;input             type=\"text\"             id=\"username\"             name=\"userName\"             value={userName}             onChange={changeUserName}             required             autoComplete=\"off\"             className=\"input\"           \/>         &lt;\/div>         &lt;button className=\"btn-success\">Start chat&lt;\/button>       &lt;\/form>     &lt;\/section>   ); };<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u043b\u043e\u0433\u0438\u043a\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0441\u043e\u043a\u0435\u0442\u043e\u0432 \u0432 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u043c \u0445\u0443\u043a\u0435 (<code>useChat.ts<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Message, Prisma } from \"@prisma\/client\"; import { useCallback, useEffect, useMemo, useState } from \"react\"; import { io, Socket } from \"Socket.IO-client\"; import { SERVER_URI, USER_INFO } from \"..\/constants\"; import { MessageUpdatePayload, UserInfo } from \"..\/types\"; import { storage } from \"..\/utils\";  \/\/ \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0441\u043e\u043a\u0435\u0442\u0430 let socket: Socket;  export const useChat = () => {   const userInfo = storage.get&lt;UserInfo>(USER_INFO) as UserInfo;    \/\/ \u044d\u0442\u043e \u0432\u0430\u0436\u043d\u043e: \u043e\u0434\u0438\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c - \u043e\u0434\u0438\u043d \u0441\u043e\u043a\u0435\u0442   if (!socket) {     socket = io(SERVER_URI, {       \/\/ \u043f\u043e\u043c\u043d\u0438\u0442\u0435 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 `handshake` \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435?       query: {         userName: userInfo.userName       }     });   }    const [messages, setMessages] = useState&lt;Message[]>();   const [log, setLog] = useState&lt;string>();    useEffect(() => {     \/\/ \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\/\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     socket.on(\"log\", (log: string) => {       setLog(log);     });      \/\/ \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439     socket.on(\"messages\", (messages: Message[]) => {       setMessages(messages);     });      socket.emit(\"messages:get\");   }, []);    \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   const send = useCallback((payload: Prisma.MessageCreateInput) => {     socket.emit(\"message:post\", payload);   }, []);    \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   const update = useCallback((payload: MessageUpdatePayload) => {     socket.emit(\"message:put\", payload);   }, []);    \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   const remove = useCallback((payload: Prisma.MessageWhereUniqueInput) => {     socket.emit(\"message:delete\", payload);   }, []);    \/\/ \u043e\u0447\u0438\u0441\u0442\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f - \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435   \/\/ \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440   window.clearMessages = useCallback(() => {     socket.emit(\"messages:clear\");     location.reload();   }, []);    \/\/ \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438   const chatActions = useMemo(     () => ({       send,       update,       remove     }),     []   );    return { messages, log, chatActions }; };<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u044d\u043a\u0440\u0430\u043d \u0447\u0430\u0442\u0430 (<code>ChatScreen.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import React, { useEffect, useState } from \"react\"; import { FiEdit2, FiSend, FiTrash } from \"react-icons\/fi\"; import { MdOutlineClose } from \"react-icons\/md\"; import TimeAgo from \"react-timeago\"; import { Slide, toast, ToastContainer } from \"react-toastify\"; import { USER_INFO } from \"..\/constants\"; import { useChat } from \"..\/hooks\/useChat\"; import { UserInfo } from \"..\/types\"; import { storage } from \"..\/utils\";  \/\/ \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438\/\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f const notify = (message: string) =>   toast.info(message, {     position: \"top-left\",     autoClose: 1000,     hideProgressBar: true,     transition: Slide   });  export const ChatScreen = () => {   const userInfo = storage.get&lt;UserInfo>(USER_INFO) as UserInfo;   const { userId, userName } = userInfo;    \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043b\u043e\u0433 \u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438   const { messages, log, chatActions } = useChat();    const [text, setText] = useState(\"\");   \/\/ \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   const [editingState, setEditingState] = useState(false);   \/\/ \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   const [editingMessageId, setEditingMessageId] = useState(0);    const changeText = (e: React.ChangeEvent&lt;HTMLInputElement>) => {     setText(e.target.value);   };    const sendMessage = (e: React.FormEvent) => {     e.preventDefault();      const trimmed = text.trim();     if (!trimmed) return;      const message = {       userId,       userName,       text     };      \/\/ \u0435\u0441\u043b\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f     if (editingState) {       \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435       chatActions.update({ id: editingMessageId, text });       setEditingState(false);     \/\/ \u0438\u043d\u0430\u0447\u0435     } else {       \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435       chatActions.send(message);     }      setText(\"\");   };    const removeMessage = (id: number) => {     chatActions.remove({ id });   };    \/\/ \u044d\u0444\u0444\u0435\u043a\u0442 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043b\u043e\u0433\u0430   useEffect(() => {     if (!log) return;      notify(log);   }, [log]);    return (     &lt;>       &lt;h1 className=\"title\">Let's Chat&lt;\/h1>       &lt;div className=\"flex-1 flex flex-col\">         {messages &amp;&amp;           messages.length > 0 &amp;&amp;           messages.map((message) => {             \/\/ \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e             const isMsgBelongsToUser = message.userId === userInfo.userId;              return (               &lt;div                 key={message.id}                 \/\/ \u0446\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 2 \u0444\u0430\u043a\u0442\u043e\u0440\u043e\u0432:                 \/\/ 1) \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e;                 \/\/ 2) \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f                 className={[                   \"my-2 p-2 rounded-md text-white w-1\/2\",                   isMsgBelongsToUser                     ? \"self-end bg-green-500\"                     : \"self-start bg-blue-500\",                   editingState ? \"bg-gray-300\" : \"\"                 ].join(\" \")}               >                 &lt;div className=\"flex justify-between text-sm mb-1\">                   &lt;p>                     By &lt;span>{message.userName}&lt;\/span>                   &lt;\/p>                   &lt;TimeAgo date={message.createdAt} \/>                 &lt;\/div>                 &lt;p>{message.text}&lt;\/p>                 {\/* \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0435\u043c\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f *\/}                 {isMsgBelongsToUser &amp;&amp; (                   &lt;div className=\"flex justify-end gap-2\">                     &lt;button                       disabled={editingState}                       className={`${                         editingState ? \"hidden\" : \"text-orange-500\"                       }`}                       \/\/ \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f                       onClick={() => {                         setEditingState(true);                         setEditingMessageId(message.id);                         setText(message.text);                       }}                     >                       &lt;FiEdit2 \/>                     &lt;\/button>                     &lt;button                       disabled={editingState}                       className={`${                         editingState ? \"hidden\" : \"text-red-500\"                       }`}                       \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f                       onClick={() => {                         removeMessage(message.id);                       }}                     >                       &lt;FiTrash \/>                     &lt;\/button>                   &lt;\/div>                 )}               &lt;\/div>             );           })}       &lt;\/div>       {\/* \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f *\/}       &lt;form onSubmit={sendMessage} className=\"flex items-stretch\">         &lt;div className=\"flex-1 flex\">           &lt;input             type=\"text\"             id=\"message\"             name=\"message\"             value={text}             onChange={changeText}             required             autoComplete=\"off\"             className=\"input flex-1\"           \/>         &lt;\/div>         {editingState &amp;&amp; (           &lt;button             className=\"btn-error\"             type=\"button\"             \/\/ \u043e\u0442\u043c\u0435\u043d\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f             onClick={() => {               setEditingState(false);               setText(\"\");             }}           >             &lt;MdOutlineClose fontSize={18} \/>           &lt;\/button>         )}         &lt;button className=\"btn-primary\">           &lt;FiSend fontSize={18} \/>         &lt;\/button>       &lt;\/form>       {\/* \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0434\u043b\u044f \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 *\/}       &lt;ToastContainer \/>     &lt;\/>   ); };<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043d\u0430\u0448 \u0447\u0430\u0442 \u0438 \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f \u0432 \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>  <\/p>\n<h2 id=\"rezultat\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/h2>\n<p>  <\/p>\n<p>\u041d\u0430\u0445\u043e\u0434\u044f\u0441\u044c \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>yarn dev<\/code> \u0438 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c 2 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code>http:\/\/localhost:3000<\/code> (\u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u043d\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u043d\u043a\u043e\u0433\u043d\u0438\u0442\u043e):<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/gf\/rx\/w1\/gfrxw1dcgdxy0m0rcl8ish-nxvo.png\" data-src=\"https:\/\/habrastorage.org\/webt\/gf\/rx\/w1\/gfrxw1dcgdxy0m0rcl8ish-nxvo.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0412\u0432\u043e\u0434\u0438\u043c \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>Bob<\/code> \u0432 \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u0432\u043a\u043b\u0430\u0434\u043e\u043a \u0438 \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c <code>Start chat<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/it\/cv\/ng\/itcvngvjcg9pfw0oo7cvszppko4.png\" data-src=\"https:\/\/habrastorage.org\/webt\/it\/cv\/ng\/itcvngvjcg9pfw0oo7cvszppko4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0414\u0435\u043b\u0430\u0435\u043c \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435 (\u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0434\u0440\u0443\u0433\u0438\u043c \u0438\u043c\u0435\u043d\u0435\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>Alice<\/code>) \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/f2\/mu\/pb\/f2mupbl50ytfb1qe8a25ye9gryo.png\" data-src=\"https:\/\/habrastorage.org\/webt\/f2\/mu\/pb\/f2mupbl50ytfb1qe8a25ye9gryo.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 <code>Alice<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <code>Storage -> Local Storage<\/code> \u0432\u043a\u043b\u0430\u0434\u043a\u0438 <code>Application<\/code> \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/qe\/if\/qc\/qeifqc3bocollapqrzib-kssrb8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/qe\/if\/qc\/qeifqc3bocollapqrzib-kssrb8.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u043c\u0435\u043d\u0438\u0432\u0430\u0435\u043c\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/zt\/sq\/yz\/ztsqyzekwoheisqxwnptczi6tn0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/zt\/sq\/yz\/ztsqyzekwoheisqxwnptczi6tn0.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0411\u0414? \u0421 <code>Prisma<\/code> \u2014 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043f\u0440\u043e\u0449\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e. \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>yarn prisma studio<\/code> \u0438 \u0432 \u043e\u0442\u043a\u0440\u044b\u0432\u0448\u0435\u0439\u0441\u044f \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code>http:\/\/localhost:5555<\/code> \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c <code>Message<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/a6\/47\/qr\/a647qrqozkex9qtn-dtekvc2ji4.png\" data-src=\"https:\/\/habrastorage.org\/webt\/a6\/47\/qr\/a647qrqozkex9qtn-dtekvc2ji4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u043e\u0431\u0443\u0435\u043c \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u2014 \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043a\u0430\u043a \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ld\/vc\/g6\/ldvcg62fynz1kvui2xxpok0bkb4.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ld\/vc\/g6\/ldvcg62fynz1kvui2xxpok0bkb4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0417\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043e\u0434\u043d\u0443 \u0438\u0437 \u0432\u043a\u043b\u0430\u0434\u043e\u043a:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/f2\/2h\/hu\/f22hhupahshtnifpqfmbmqvwvns.png\" data-src=\"https:\/\/habrastorage.org\/webt\/f2\/2h\/hu\/f22hhupahshtnifpqfmbmqvwvns.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 <code>Alice<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 <code>clearMessages<\/code>. \u0412\u0441\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f, \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/kp\/-t\/zr\/kp-tzrtv4l-d54en7enj7aev-ay.png\" data-src=\"https:\/\/habrastorage.org\/webt\/kp\/-t\/zr\/kp-tzrtv4l-d54en7enj7aev-ay.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0432\u0441\u0435, \u0447\u0435\u043c \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0432\u044b \u043d\u0430\u0448\u043b\u0438 \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0447\u0442\u043e-\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435 \u0438 \u043d\u0435 \u0437\u0440\u044f \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b\u0438 \u0432\u0440\u0435\u043c\u044f. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438 happy coding!<\/p>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p><a href=\"https:\/\/cloud.timeweb.com\/vds-promo-8-rub?utm_source=habr&amp;utm_medium=blog_1560_476&amp;utm_campaign=habr&amp;utm_content=1560_476\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/co\/e2\/kh\/coe2kha8u8_pypip-2k3wk3ppa0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/co\/e2\/kh\/coe2kha8u8_pypip-2k3wk3ppa0.png\"\/><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/680670\/\"> https:\/\/habr.com\/ru\/company\/timeweb\/blog\/680670\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/ma\/po\/lv\/mapolvqq4uunxfqoaviv3g9km9y.jpeg\" data-src=\"https:\/\/habrastorage.org\/webt\/ma\/po\/lv\/mapolvqq4uunxfqoaviv3g9km9y.jpeg\" data-blurred=\"true\"\/>  <\/p>\n<p>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435 \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0447\u0430\u0442 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439:<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a> \u2014 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0442\u043e\u0440;<\/li>\n<li><a href=\"https:\/\/nestjs.com\/\">NestJS<\/a> \u2014 \u0441\u0435\u0440\u0432\u0435\u0440;<\/li>\n<li><a href=\"https:\/\/socket.io\/\">Socket.IO<\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0432 [\u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u0430\u043c\u0438]();<\/li>\n<li><a href=\"https:\/\/ru.reactjs.org\/\">React<\/a> \u2014 \u043a\u043b\u0438\u0435\u043d\u0442;<\/li>\n<li><a href=\"https:\/\/tailwindcss.com\/\">TailwindCSS<\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0441\u0442\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u0438;<\/li>\n<li><a href=\"https:\/\/www.postgresql.org\/\">PostgreSQL<\/a> \u2014 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0434\u0430\u043b\u0435\u0435 \u2014 \u0411\u0414);<\/li>\n<li><a href=\"https:\/\/www.prisma.io\/\">Prisma<\/a> \u2014 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/ORM\">ORM<\/a>;<\/li>\n<li><a href=\"https:\/\/www.docker.com\/\">Docker<\/a> \u2014 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435 \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0447\u0430\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0444\u0435\u0439\u043a\u043e\u0432\u0430\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439:<br \/> \n<ul>\n<li>\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 (\u043e\u0431\u044a\u0435\u043a\u0442\u0435) \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435;<\/li>\n<li>\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d \u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 <a href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/Window\/localStorage\">localStorage<\/a> \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435;<\/li>\n<\/ul>\n<\/li>\n<li>\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u044d\u0442\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c;<\/li>\n<li>\u0437\u0430\u043f\u0438\u0441\u044c, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438\u0437 \u0411\u0414 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u044d\u0442\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c.<\/li>\n<\/ul>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/react-nest-postgres-chat\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-336530","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/336530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=336530"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/336530\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=336530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=336530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=336530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}