Как настроить Node.js Express сервер для React
React — это библиотека JavaScript для разработки пользовательских интерфейсов. Она позволяет создавать эффективные и масштабируемые веб-приложения, основанные на компонентной архитектуре.
Express.js — это минималистичный и гибкий веб-фреймворк для Node.js, который облегчает разработку веб-приложений и API. Он предоставляет простой интерфейс и набор функций, позволяющих быстро создавать серверы и маршрутизировать запросы.
Введение
Это руководство поможет вам разработать простое приложение на React и подключить его к серверу, созданному с использованием Node.js. Мы начнем с создания React приложения с помощью команды create-react-app , а затем настроим его подключение к серверу Node.js c помощью proxy .
Необходимые условия
Для успешного выполнения данного гайда будет полезно иметь следующее:
- Предварительный опыт работы с Node.js, Express, npm и React.js.
- Установленный Node.js.
- Текстовый редактор, предпочтительно VS Code.
- Веб-браузер, в данном случае Google Chrome.
Настройка структуры папок

Первым шагом будет создание корневой папки для нашего приложения с именем express-react-app , в котором будут содержаться все файлы приложения. Затем мы создадим папку client , которая будет содержать все файлы React приложения.
Папка node_modules будет содержать все пакеты NPM для файла server.js . Папка node_modules будет автоматически создана при установке пакетов NPM.
Далее нам потребуется создать файл server.js . В этом файле будет размещен сервер Express, который будет выступать в качестве нашего бэкенда. Файл package.json будет автоматически сгенерирован, когда в терминале будет выполнена команда npm init -y .
Создание React приложения
Из терминала перейдите в корневую директорию с помощью команды cd и выполните следующие команды:
$cd express-react-app $npx create-react-app client
Вышеуказанные команды создадут React приложение названием client внутри корневой директории.
Настройка сервера Express
Следующий шаг состоит в создании сервера Express в файле server.js .
Из терминала перейдите в корневую директорию и выполните следующую команду:
$npm init -y
Команда автоматически сгенерирует файл package.json . Затем нам потребуется выполнить следующую команду для установки Express, и она будет сохранена в качестве зависимости в файле package.json .
$npm install express --save
Теперь отредактируйте файл server.js следующим образом:
const express = require('express'); //Строка 1 const app = express(); //Строка 2 const port = process.env.PORT || 5000; //Строка 3 // Сообщение о том, что сервер запущен и прослушивает указанный порт app.listen(port, () => console.log(`Listening on port $`)); //Строка 6 // Создание GET маршрута app.get('/express_backend', (req, res) => < //Строка 9 res.send(< express: 'YOUR EXPRESS BACKEND IS CONNECTED TO REACT' >); //Строка 10 >); //Строка 11
Строки 1 и 2 — подключают модуль Express и позволяют использовать его внутри файла server.js .
Строка 3 — Установка порта, на котором будет работать сервер Express.
Строка 6 — будет отображено сообщение в консоли о том, что сервер работает исправно.
Строка 9 и 11 — установка GET маршрута, который позже мы будем получать из нашего клиентского React приложения.
Настройка proxy
На этом шаге Webpack Development Server был автоматически сгенерирован при выполнении команды create-react-app . Наше React приложение работает на Webpack Development Server на стороне фронденда.
Webpack Development Server (WDS) — это инструмент, который помогает разработчикам вносить изменения во фронтенд веб-приложения и автоматически отображает эти изменения в браузере без необходимости обновления страницы.
Он уникален по сравнению с другими инструментами, которые делают то же самое, поскольку содержимое пакета не записывается на диск в виде файлов, а хранится в памяти. Это преимущество крайне важно при отладке кода и стилей.
Мы можем настроить проксирование запросов API с клиентской стороны на API на серверной стороне. API на серверной стороне (Express сервер) будет работать на порту 5000.
Сначала настройте прокси для перехода в директорию client и найдите файл package.json . Добавьте следующую строку в него.
“proxy”: “http://localhost:5000”
Измененный файл package.json будет выглядеть следующим образом:
< "name": "client", "version": "0.1.0", "private": true, "dependencies": < "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" >, "scripts": < "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" >, "eslintConfig": < "extends": [ "react-app", "react-app/jest" ] >, "browserslist": < "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] >, "proxy": "http://localhost:5000" >
Измененный файл package.json позволит Webpack проксировать запросы API на сервер бэкенда Express, работающий на порту 5000.
Вызов бэкенд сервера Express из React
Сначала перейдите в папку client/src и отредактируйте файл App.js , чтобы он выглядел следующим образом:
import React, < useEffect, useState >from 'react'; import logo from './logo.svg'; import './App.css'; function App() < const [state, setState] = useState(null); const callBackendAPI = async () => < const response = await fetch('/express_backend'); const body = await response.json(); if (response.status !== 200) < throw Error(body.message) >return body; >; // получение GET маршрута с сервера Express, который соответствует GET из server.js useEffect(() => < callBackendAPI() .then(res =>setState(res.express)) .catch(err => console.log(err)); >, []) return (
className="App-logo" alt="logo" /> Edit src/App.js and save to reload.
Learn React * вывод данных, полученных с сервера Express */> ); > export default App;
Внутри хука useEffect() вызывается функция callBackendAPI() . Эта функция будет получать данные с ранее созданного маршрута на сервере Express. При получении ответа от запроса fetch , значение res.express устанавливается в состояние state с помощью функции setState() . Затем значение state выводится внутри элемента для отображения на странице.
Запуск приложения
Для запуска приложения перейдите в корневую директорию express-react-app и выполните следующую команду:
$cd express-react-app $node server.js
После запуска файла server.js следующим шагом будет переход в веб-браузер по адресу «http://localhost:5000/express_backend«, и будет отображено следующее сообщение:

Вышеуказанное демонстрирует, что наш сервер Express работает должным образом, и созданный нами маршрут GET функционирует, а также возможно получение этого маршрута с клиентской стороны.
Также обратите внимание, что путь URL совпадает с путем, который мы указали в нашем маршруте GET в файле server.js .
Затем перейдите в директорию client в терминале и выполните следующие команды:
$cd client $npm start
Вышеуказанные команды запустят React сервер разработки, который работает на порту 3000, и автоматически откроется в веб-браузере.
На экране будет отображено следующее сообщение:

Наконец, мы отобразили данные, полученные с маршрута GET в server.js , в нашем фронтенд React приложении, как было показано выше.
Если сервер Express отключен, сервер React всё равно будет продолжать работать. Однако связь с бэкендом будет потеряна, и ничего не будет отображаться.
Заключение
С помощью сервера Express можно сделать многое, например, осуществлять вызовы к базе данных. Однако в этом руководстве мы сосредоточились на том, как быстро подключить клиентское React приложение к серверу Express на стороне бэкенда.
Кодовые фрагменты и файлы, использованные в этом руководстве, можно найти в репозитории GitHub по этой ссылке.
React.js для новичков в программировании: что это, как устроен и зачем нужен
Если стажёр или совсем зелёный джун попросят вас объяснить, что такое React.js, — просто покажите им эту статью.


Иллюстрация: OlyaSnow для Skillbox Media

Тимур Тукаев
Фанат Free Software Foundation, использует Linux и недолюбливает Windows. Пишет истории про кодинг и программы на Python. Влюблён в Lisp, но пока что не умеет на нём программировать.
В интернете полно руководств по React.js с названиями типа for dummies, for idiots — вроде бы для чайников. Но они по большей части негуманны и довольно сложны — человеку без знания JavaScript пользы не будет, только сильнее запутается и почувствует себя тем самым dummy. Поэтому мы решили максимально просто объяснить, что такое React.js и для чего он нужен, и даже показать, как на нём написать небольшую онлайн-игру.
- Что такое React.js
- Что такое компоненты и состояния
- Зачем нужен React.js
- Какие у него аналоги
- Где его скачать и как начать пользоваться
- Как написать и настроить простой проект на React.js
Что такое React.js
React.js — это JavaScript-библиотека от «Фейсбука»* для удобной разработки интерфейсов, то есть внешней части сайтов и приложений, с которой взаимодействует пользователь.
Главная фишка React.js — компоненты и состояния.
Компонент — это кусочек кода, который отвечает за внешний вид одного из элементов сайта или приложения. Причём такие кусочки-компоненты могут быть вложенными.
Состояние — это вся информация об элементе, в том числе о его отображении. Например, состояние объекта «термометр» может описываться свойствами current_temperature, min и max.
Что такое компоненты и состояния: переводим на понятный язык
Пока звучит немного абстрактно и сложно, но на деле всё гораздо проще. Потренируемся на Цукерберге: в конце концов, это его детище — ему и отвечать.
На скриншоте три крупных компонента — пост в ленте «Фейсбука»*, блок краткой информации и блок с выводом фотографий. В каждый из них входят более мелкие компоненты. Например, внутри поста есть текст, изображение, аватарка, имя автора, лайки, комментарии, различные информационные элементы. Внутри блока с фотографиями — отдельные фото, а внутри блока с краткой информацией — собственно та самая краткая информация.

У каждого из этих компонентов есть состояния. Например, блок краткой информации будет по-разному выглядеть в мобильной и десктопной версиях, у сердечка меняется число с лайками или цвет (если вы лайкнули или не лайкнули запись), а пост может обрезать текст, показывать содержимое полностью, меняться в зависимости от содержания. Ведь содержание поста — это тоже его состояние. Именно в этом проявляется гибкость и сила React.js: вы пишете компонент один раз, а потом просто передаёте ему разные состояния.
Посмотрите, как в зависимости от состояния меняется размер аватарки:

- Большая — в шапке личной страницы.
- Стандартная — в верхнем меню и записи в ленте.
- Совсем крошечная — в строке «у вас столько-то подписчиков».
Уже получилось три состояния, но это не предел — ведь внешний вид аватарки различается в мобильной и десктопной версиях, в приложениях для Android, iOS и так далее. Отметим, что аватарка практически везде будет вложенной — в составе более крупных компонентов React.js, таких как пост, шапка, боковая панель или меню.
В React.js есть собственные средства для управления состояниями, но на практике в средних и крупных проектах чаще используют Redux — сторонний менеджер состояний. Он удобнее и лучше масштабируется.
Зачем нужен React.js, если есть HTML, JavaScript и CSS
Никаких огородов. React.js — это всего лишь способ в удобном виде представить код JavaScript и HTML, сделать его повторяемым и наглядным. Компоненты React.js пишут на особом языке — JSX, который выглядит как смесь JavaScript и HTML. Вот пример кода на этом языке:

А ещё существует React Native — тоже разработка «Фейсбука»*, которую не следует путать с обычным React.js. В какой-то момент компания поняла, что на React.js пишут приложения не только для браузеров, но и для разных операционных систем. Правда, работали такие приложения довольно медленно. Чтобы решить эту проблему, в «Фейсбуке»* и создали нативную версию библиотеки.
Синтаксис React Native похож на JSX, но переводится на понятный и привычный для Windows, macOS, Android и других операционных систем язык. То есть приложение становится нативным — использует стандартные для разных платформ возможности и протоколы, а не запускается в браузере. Мало того, на платформе React Native можно использовать и другие языки программирования — например, Java, Swift, Objective-C.
Где скачать React.js и как начать им пользоваться
Получить доступ к React.js можно двумя способами.
Использовать онлайн-редакторы кода
Это самый простой способ. Можно поиграться с библиотекой в онлайн-песочницах CodePen и CodeSandBox или скачать HTML-файл с шаблоном c GitHub. После этого свой код можно прогнать через Babel — тоже онлайн, чтобы не устанавливать лишние приложения.
Установить React.js на компьютер
Инсталляция и настройка полноценного окружения React.js требует больше времени и подходит для более продвинутых пользователей.
Для этого предварительно нужно установить Node.js — это среда, которая позволяет запускать JS-код как серверное приложение.
Затем скачайте пакет create-react-app с помощью npm:
Разработка и настройка простого React-приложения
Напишем на React.js веб-версию бессмертного хита — игры «крестики-нолики». Весь код, за исключением стилей, будем писать в файле App.js в папке src.
Сначала импортируем модуль React, вебхук из неё и библиотеку ReactDOM для рендеринга компонентов:
Итог
React.js — довольно простая библиотека, хотя, как и в любом деле, здесь есть нюансы. Например, вебхуки, однонаправленная передача данных, callback-функции, виртуальный DOM, методы жизненного цикла. Написать новый «Фейсбук»* или «Инстаграм»* на React.js не так-то просто, но всё равно это очень дружественная библиотека.
Обязательно ознакомьтесь с документацией проекта и учебником по синтаксису JSX на официальном сайте React.js. Начните с Hello, World! и смело экспериментируйте в CodePen — так советуют авторы гайда.
Как обычно, легче всего осваивать технологию на реальных задачах — например, поставьте себе цель скопировать часть интерфейса «Фейсбука»* или «Инстаграма»*. Почему бы и нет?
Больше интересного про код в нашем телеграм-канале. Подписывайтесь!
Читайте также:
- Библиотеки в программировании: для чего нужны и какими бывают
- «Я не скучаю по ретейлу»: как директору «Пятёрочки» удалось стать IT-специалистом с зарплатой 3000 долларов
- Создаём первую игру на Python и Pygame
* Решением суда запрещена «деятельность компании Meta Platforms Inc. по реализации продуктов — социальных сетей Facebook и Instagram на территории Российской Федерации по основаниям осуществления экстремистской деятельности».
React и node js что это
В прошлых вводных темах весь код приложения определялся на веб-странице:
Hello React
Такой подход хорош для вывода сообщения «Hello React», но в реальности весь код JSX обычно выносится во внешние файлы. Поэтому определим в той же папке, где находится данная веб-страница, новый файл index.jsx :
const rootNode = document.getElementById("app"); // элемент для рендеринга приложения React // получаем корневой элемент const root = ReactDOM.createRoot(rootNode); // рендеринг в корневой элемент root.render( Hello React
// элемент, который мы хотим создать );
Обычно код JSX определяется в файлах с расшиением *.jsx , хотя можно использовать и стандартное расширение js.
И изменим веб-страницу:
Hello React
Вроде бы у нас все то же самое, используется тот же код, только в отдельном файле. Но если мы просто кинем страницу в браузер, то мы столкнемся с ошибкой:

Дело в том, что Babel подтягивает код JSX через AJAX-запросы. Поэтому просто кинуть веб-страницу в браузер у нас не получится, нам надо использовать для обращения к ней веб-сервер, то есть все файлы приложения должны быть размещены на веб-сервере. Здесь не столь важно, какой именно веб-сервер использовать — Apache, Node.js, IIS. Но в рамках данных статей преимущественно будет использоваться Node.js как более демократичный вариант. Поэтому для работы также необходимо установить Node.js.
Подробнее про работу с Node.js можно почитать в соответствующем руководстве.
После установки Node.js в каталог проекта добавим новый файл app.js . То есть в итоге в папке проекта будет три файла:

- index.html
- index.jsx
- app.js
Определим в файле app.js следующий код:
const http = require("http"); const fs = require("fs"); http.createServer(function(request, response) < let filePath = "index.html"; if(request.url !== "/")< // получаем путь после слеша filePath = request.url.substring(1); >fs.readFile(filePath, function(error, data) < if(error)< response.statusCode = 404; response.end("Resourse not found!"); >else < response.end(data); >>); >).listen(3000, function()< console.log("Server started at 3000"); >);
Это простейший веб-сервер на Node.js, который мы бы могли определить в нашей ситуации для наших скромных целей — оправить пользователю страницу index.html со всеми сопутствующими файлами. В частности, в начале подключаем для модуля — http и fs , которые необходимы соответственно для обработки http-запросов и для работы с файлами:
const http = require("http"); const fs = require("fs");
Далее создаем собственно веб-сервер с помощью функции http.createServer() :
http.createServer(function(request, response)
В качестве параметра она принимает функцию, которая будет запускаться для обработки каждого приходящего на сервер запроса. И эта функция имеет два параметра: request — объект, который хранит все данные запроса, и response — объект, который позволяет определить нам ответ на запрос.
В данном случае у нас очень простая ситуация — нам надо отправить статический файл: либо index.html , либо index.jsx . Для этого в начале смотрим на запрошенный путь. Если запрос идет к корню сайта, то есть по адресу «localhost:3000/», то отправляем страницу «index.html». А для всех остальных адресов, в частности, для файла «index.jsx», получаем запрошенный путь
filePath = request.url.substring(1);
Далее с помощью функции fs.readFile() считываем из файловой системы запрошенный файл:
fs.readFile(filePath, function(error, data)И с помощью метода response.end() отправляем считанный данные пользователю:
response.end(data);В конце функция listen() запускает созданный веб-сервер на 3000-м порту, и после этого мы сможем обращаться к приложению по адресу "http://localhost:3000"
.listen(3000, function()< console.log("Server started at 3000"); >);И теперь, когда все готово, запустим приложение. Для этого откроем командную строку/терминал и перейдем с помощью команды cd к папке проекта. Затем запустим приложение с помощью команды
node app.js
После этого в браузере перейдем по адресу "http://localhost:3000", по которому запущен сервер, и нам откроется веб-страница index.html:
В итоге компилятор Babel при запуске страницы подгрузит файл index.jsx и скомпилирует из него код JavaScript, который будет применяться к странице.
Node JS и React - как создать фулстек приложение. Полное руководство
React приложение и бэкенд на NodeJS это хорошее сочетание, которое подойдет для реализации практически любых сервисов. Эта статья поможет вам быстро создать фулстек приложение.
Необходимые инструменты
- NodeJS и npm. Их можно скачать с официального сайта nodejs.org. npm установится автоматически вместе с NodeJS.
- Предпочитаемый IDE, например, Visual Studio Code.
- Опционально, установить git для удобной работы с кодом.
О приложении
В этой статье напишем приложение, которое будет получать и отображать список дел. Структура папок будет выглядеть следующим образом.
app/ frontend/ backend/Создание бэкэнда на NodeJS
Запустим команду в папке app/backend для инициализации проекта:
npm init -yЭта команда создаст файл package.json . Этот файл содержит как общую информацию о проекте (название, версия, описание и т.д.), так и информацию о зависимостях, скрипты для запуска, сборки и тестирования. Для создания сервера будем использовать express. Установим его с помощью команды:
npm i expressСоздадим файл index.js , который будет содержать код для запуска сервера. Этот код запускает веб сервер на порту 3010 , если он не задан в переменных среды.
// backend/index.js const express = require('express'); const PORT = process.env.PORT || 3010; const app = express(); app.use((req, res, next) => < res.setHeader('Access-Control-Allow-Origin', '*'); next(); >); app.listen(PORT, () => < console.log(`Server listening on $`); >);Добавим команду для запуска сервера в package.json . В результате сможем запускать наш сервер с помощью команды npm start .
// backend/package.json . "scripts": < "start": "node ./index.js" >, .Из директории app/backend запустим команду npm start . Если ошибок нет, получим сообщение, что сервер прослушивает порт 3010.
PS C:\tutorials-coding\nodejs-react-app\backend> npm start > backend@1.0.0 start > node ./index.js Server listening on 3010Создание API
API это интерфейс, с помощью которого React приложение будет общаться с веб-сервером, т.е. запрашивать, изменять или удалять данные. В нашем случае мы создадим API для получения списка дел в формате JSON. Создадим файл todo-items.json c объектами todo. Этот массив будем отдавать по запросу /api/todo-items .
Следующий код создает эндпоинт /api/todo-items . React приложение будет отправлять GET запрос на этот эндпоинт.
// backend/index.js // . const todoItems = require('./todo-items.json'); app.get('/api/todo-items', (req, res) => < res.json(< data: todoItems >); >); app.listen(PORT, () => < console.log(`Server listening on $`); >);
Для того чтобы изменения вступили в силу, нужно перезапустить NodeJS сервер. Для остановки скрипта - в терминале, в котором запущен npm start , нужно нажать Ctrl + C ( Command + C ). Далее снова запускаем npm start . Для проверки эндпоинта, в браузере перейдем по адресу http://localhost:3010/api/todo-items . В результате получим, такой ответ.
Создание фронтенда на React
В папке app/ откроем новый терминал и запустим команду для создания React приложения, где frontend имя нашего приложения.
npx create-react-app@latest frontendДождемся установки всех зависимостей. В терминале перейдем в папку frontend .
cd ./frontendУстановим библиотеку bootstrap для дальнейшего использования готовых компонентов.
npm install react-bootstrap bootstrapЗаимпортируем bootstrap.min.css в файле frontend/src/index.js .
import 'bootstrap/dist/css/bootstrap.min.css';Запустим приложение командой npm start .
npm startПолучим следующее сообщение. Перейдем по указанному адресу в браузере.
Compiled successfully! You can now view frontend in the browser. Local: http://localhost:3003 On Your Network: http://192.168.99.1:3003 Note that the development build is not optimized. To create a production build, use npm run build.Отправка HTTP запроса из React в NodeJS
К этому моменту у нас уже есть рабочий сервер, который умеет принимать запросы и отдавать данные. Сделаем запрос на /api/todo-items из React приложения. Для этого вызовем функцию fetch из хука useEffect в файле App.js .
// frontend/src/App.js import < useState, useEffect >from 'react'; import Form from 'react-bootstrap/Form'; import './App.css'; function App() < const [todoItems, setTodoItems] = useState([]); useEffect(() => < fetch('http://localhost:3010/api/todo-items') .then((res) =>res.json()) .then((result) => setTodoItems(result.data)); >, []); return (); > export default App;( className="app__todo-item"> /> /> ))>
Открыв приложение в браузере, получим такой результат. Исходный код
Чистая архитектура в Node.js
7 месяцев назад · 10 мин. на чтение
Чистая архитектура (Clean Architecture) — это концепция разработки программного обеспечения, которая фокусируется на создании модульного и поддерживаемого кода. Архитектура позволяет разработчику создавать кодовую базу, которую легко понять, протестировать и расширить.
Основное внимание в чистой архитектуре уделяется отделению основной бизнес-логики приложения от деталей инфраструктуры. В этой статье мы обсудим чистую архитектуру в Node.js и рассмотрим, как реализовать ее в проектах Node.js примерами кода и как применять к ним концепции.
Выявление и решение проблем
При разработке приложений Node.js мы часто сталкиваемся с некоторыми проблемами, такими как дублирование кода, неорганизованная кодовая база и тесная связь (tight coupling). В следующем разделе мы используем простой пример приложения Node.js, которое извлекает данные из базы данных и возвращает их в качестве ответа JSON. Чистая архитектура предлагает решения этих проблем, делая упор на следующие принципы:
Разделение ответственности (Separation of Concerns, SoC)
Фундаментальный принцип разработки программного обеспечения, который подчеркивает важность разделения системы на отдельные модули или компоненты, каждый из которых несет определенную и независимую ответственность. В Node.js SoC часто реализуется с использованием модульной архитектуры, где каждый модуль отвечает за одну задачу или функциональность. Вот пример того, как разделение ответственности может быть применено к этому коду:
// index.js - Плохой код! const express = require('express') const app = express() const database = require('./database') app.get('/users', async (req, res) => < try < const users = await database.getUsers() res.json(users) >catch (error) < res.status(500).send('Internal Server Error') >>) app.listen(3000, () => < console.log('Server is listening on port 3000') >)В этом коде мы смешали задачи маршрутизации, извлечения данных и обработки ошибок в одном файле. Это может затруднить обслуживание и масштабирование кода по мере роста приложения.
Вот как мы можем применить разделение ответственности к этому коду:
// index.js const express = require('express') const app = express() const usersRouter = require('./routes/users') app.use('/users', usersRouter) app.listen(3000, () => < console.log('Server is listening on port 3000') >)// routes/users.js const express = require('express') const router = express.Router() const usersController = require('../controllers/users') router.get('/', usersController.getUsers) module.exports = router// controllers/users.js const database = require('../database') async function getUsers(req, res) < try < const users = await database.getUsers() res.json(users) >catch (error) < res.status(500).send('Internal Server Error') >> module.exports =В этом переработанном коде мы разделили задачи маршрутизации, контроллера и извлечения данных на три отдельных файла. Файл index.js отвечает за создание приложения Express и регистрацию маршрутов, файл routes/users.js отвечает за обработку маршрута /users и делегирование запроса usersController , а файл controllers/users.js отвечает за извлечение данных из базы данных и возврат ответа клиенту. Разделив эти задачи, мы можем достичь лучшей модульности и удобства сопровождения кода. Например, если мы хотим добавить больше маршрутов, мы можем создать новый файл в routes и зарегистрировать его в index.js без изменения существующего кода. Аналогичным образом, если мы хотим изменить способ извлечения данных из базы данных, мы можем изменить database.js файл без изменения маршрута или кода контроллера.
Инверсия зависимостей (Dependency Inversion, DI)
В Clean Architecture зависимости инвертируются, что означает, что модули более высокого уровня должны зависеть от модулей более низкого уровня. Это гарантирует, что бизнес-логика не будет связана с деталями инфраструктуры. Вот пример того, как инверсия зависимостей может быть применена к этому коду:
// index.js - Плохой код! const express = require('express') const app = express() const usersController = require('./controllers/users') app.get('/users', async (req, res) => < try < const users = await usersController.getUsers() res.json(users) >catch (error) < res.status(500).send('Internal Server Error') >>) app.listen(3000, () => < console.log('Server is listening on port 3000') >)// controllers/users.js const database = require('../database') async function getUsers() < try < const users = await database.getUsers() return users >catch (error) < throw new Error('Error retrieving users') >> module.exports =В этом коде index.js напрямую зависит от модуля usersController , который в свою очередь напрямую зависит от модуля database . Это нарушает принцип инверсии зависимостей, поскольку высокоуровневые модули не должны зависеть от низкоуровневых модулей.
Вот как мы можем применить инверсию зависимостей к этому коду:
// index.js const express = require('express') const app = express() const usersController = require('./controllers/users') const database = require('./database') app.get('/users', async (req, res) => < try < const users = await usersController.getUsers(database) res.json(users) >catch (error) < res.status(500).send('Internal Server Error') >>) app.listen(3000, () => < console.log('Server is listening on port 3000') >)// controllers/users.js async function getUsers(database) < try < const users = await database.getUsers() return users >catch (error) < throw new Error('Error retrieving users') >> module.exports =В этом переработанном коде мы применили Dependency Inversion, передав модуль database в качестве параметра функции getUsers в модуле usersController . Таким образом, высокоуровневый модуль index.js не зависит напрямую от низкоуровневого модуля database , но оба зависят от абстракции функции getUsers . Применяя инверсию зависимостей, мы можем добиться большей модульности, гибкости и тестируемости кода. Например, если мы хотим переключиться на другую реализацию базы данных, мы можем создать новый модуль, реализующий функцию getUsers , и передать ее в качестве getUsers в модуле usersController без изменения существующего кода. Точно так же мы можем легко имитировать модуль database для целей модульного тестирования.
Принцип единой ответственности (Single Responsibility Principle, SRP)
Каждый модуль или функция должны нести единую ответственность. Это помогает в создании кодовой базы, которую легко понять и поддерживать. Вот пример в Node.js который иллюстрирует принцип SRP:
// user.js - Плохой код! class User < constructor(name, email, password) < this.name = name; this.email = email; this.password = password; >saveToDatabase() < await database.saveUser(user) .then(() =>< res.status(200).send() >) .catch(err => < res.status(400).send('Email Error') >); > sendWelcomeEmail() < await mailService.send(user, 'Welcome') .then(() =>< res.status(200).send() >) .catch(err => < res.status(400).send('Email Error') >); > >
- Сохранение пользовательских данных в базе данных
- Отправка приветственного электронного письма пользователю
Чтобы следовать принципу SRP, мы можем разделить эти обязанности на отдельные классы:
class User < constructor(name, email, password) < this.name = name; this.email = email; this.password = password; >> class UserRepository < saveToDatabase(user) < await database.saveUser(user) .then(() =>< res.status(200).send() >) .catch(err => < res.status(400).send('Email Error') >); > > class EmailService < sendWelcomeEmail(user) < await mailService.send(user, 'Welcome') .then(() =>< res.status(200).send() >) .catch(err => < res.status(400).send('Email Error') >); > > >
- User : отвечает за представление пользователя.
- UserRepository : отвечает за сохранение пользовательских данных в базе данных.
- EmailService : отвечает за отправку приветственного электронного письма пользователю.
Концепции и детальное проектирование
Чистая архитектура в Node.js основана на принципах чистой архитектуры, введенных Робертом С. Мартином (он же дядя Боб). В нем подчеркивается разделение ответственности, разъединение зависимостей и модульность. Цель состоит в том, чтобы создать кодовую базу, которую легко понять, протестировать и поддерживать. Основная идея Clean Architecture заключается в разделении приложения на разные слои, где каждый уровень несет определенную ответственность. Слои взаимодействуют друг с другом через четко определенные интерфейсы. Это позволяет легко модифицировать и тестировать приложение, не затрагивая другие части кодовой базы. Node.js — это популярная среда выполнения для создания веб-приложений. Он имеет обширную экосистему библиотек и фреймворков, которые можно использовать для реализации Clean Architecture. Вот некоторые из ключевых концепций чистой архитектуры в Node.js:
Уровень инфраструктуры (Infrastructure Layer)
Уровень инфраструктуры отвечает за обработку внешних зависимостей, таких как базы данных, API или файловая система. Он должен быть отделен от уровня домена, чтобы обеспечить легкое тестирование и модификацию. Уровень инфраструктуры должен реализовывать интерфейсы, определенные уровнем домена. В Node.js уровень инфраструктуры может быть реализован с помощью пакетов или модулей. Например, можно использовать популярный пакет Knex.js для обработки запросов к базе данных. Уровень инфраструктуры должен быть спроектирован так, чтобы быть подключаемым, что позволяет легко заменять внешние зависимости. Вот пример модуля инфраструктуры, реализующего адаптер базы данных:
const knex = require('knex'); class UserDatabase < constructor(config) < this.db = knex(config); >async getById(id) < const data = await this.db('users').where(< id >).first(); return data ? User.create(data) : null; > async save(user) < const data = User.toData(user); const < id >= user; if (id) < await this.db('users').where(< id >).update(data); > else < const [newId] = await this.db('users').insert(data); user.id = newId; >> >Этот модуль предоставляет методы для получения пользователя по идентификатору и сохранения пользователя в базе данных с помощью библиотеки Knex.js.
Уровень представления (Presentation Layer)
Уровень представления отвечает за отображение выходных данных приложения для пользователя и обработку вводимых пользователем данных. Он должен быть отделен от прикладного уровня и уровня инфраструктуры. Уровень представления может быть реализован с помощью веб-фреймворков, таких как Express.js или Hapi.js. В Node.js уровень представления может быть реализован с помощью веб-фреймворков или модулей. Веб-фреймворки предоставляют мощный и гибкий способ реализации уровня представления и размещения всей логики вместе. Вот пример модуля презентации, реализующего REST API с помощью веб-платформы Express.js:
const express = require('express'); const bodyParser = require('body-parser'); const UserService = require('./services/user-service'); const UserDatabase = require('./infra/user-database'); const app = express(); app.use(bodyParser.json()); const userDatabase = new UserDatabase(config); const userService = new UserService(userDatabase); app.get('/users/:id', async (req, res) => < const < id >= req.params; try < const user = await userService.getUserById(id); res.json(user); >catch (error) < res.status(404).json(< error: error.message >); > >); app.put('/users/:id', async (req, res) => < const < id >= req.params; const < userData >= req.body; try < let user = await userService.getUserById(id); user = User.create(< . user, . userData >); await userService.saveUser(user); res.json(user); > catch (error) < res.status(404).json(< error: error.message >); > >);Этот модуль создает приложение Express.js, настраивает пользовательскую службу и базу данных, а также предоставляет конечные точки REST API для получения и обновления пользовательских данных.
Прикладной уровень (Application Layer)
Прикладной уровень отвечает за оркестрацию взаимодействия между доменным уровнем и уровнем инфраструктуры. Он содержит варианты использования приложения, которые представляют взаимодействия между пользователем и системой. Прикладной уровень должен быть отделен от уровня домена и уровня инфраструктуры. В Node.js прикладной уровень может быть реализован с помощью классов или модулей. Классы обеспечивают четкое разделение ответственности и инкапсуляцию. Модули обеспечивают более простой подход к реализации прикладного уровня. Вот пример класса приложения, представляющего пользовательскую службу:
class UserService < constructor(userDatabase) < this.userDatabase = userDatabase; >async getUserById(id) < const user = await this.userDatabase.getById(id); if (!user) < throw new Error(`User not found with ID $`); > return user; > async saveUser(user) < await this.userDatabase.save(user); >>Этот класс предоставляет методы для получения пользователя по идентификатору и сохранения пользователя с помощью модуля инфраструктуры UserDatabase.
Уровень домена (Domain (Enterprise) Layer)
Доменный уровень является сердцем приложения, содержащим бизнес-логику и правила. Он должен быть независим от каких-либо внешних библиотек или фреймворков, что упрощает его тестирование и изменение. Доменный уровень является наиболее важной частью приложения, и любые изменения должны вноситься с большой осторожностью. В Node.js, уровень домена может быть реализован с помощью классов или модулей. Классы обеспечивают четкое разделение ответственности, инкапсуляцию и возможность повторного использования. Модули, с другой стороны, обеспечивают более простой подход к реализации доменного уровня. Вот пример класса домена, представляющего сущность пользователя:
class User < constructor(id, name, email) < this.id = id; this.name = name; this.email = email; >changeName(name) < this.name = name; >changeEmail(email) < this.email = email; >static create(data) < const < id, name, email >= data; return new User(id, name, email); > static toData(user) < const < id, name, email >= user; return < id, name, email >; > >Этот класс инкапсулирует пользовательские данные и предоставляет методы для изменения имени и адреса электронной почты пользователя.
Использование сторонних npm пакетов
Использование сторонних пакетов — отличный способ повысить чистоту кода. Существует множество пакетов, которые могут помочь вам организовать кодовую базу и сделать ее более модульной. Например, можно использовать пакет, например Express.js для обработки маршрутизации и промежуточного ПО. Вы также можете использовать пакет, такой как Knex.js для обработки запросов к базе данных. Использование этих пакетов может помочь уменьшить дублирование кода и сделать базу кода более организованной. Вот еще несколько примеров использования сторонних пакетов:
Lodash
Lodash — это служебная библиотека, предоставляющая широкий спектр функций для работы с массивами, объектами и другими структурами данных. Используя Lodash, можно избежать написания повторяющегося кода для таких задач, как фильтрация, сортировка и преобразование данных. Например, рассмотрим следующий код для фильтрации массива пользователей по возрасту:
const users = [ < name: 'Alice', age: 25 >, < name: 'Bob', age: 30 >, < name: 'Charlie', age: 35 >, ]; const filteredUsers = users.filter(user => user.age >= 30);Используя Lodash, вы можете вместо этого написать следующий код:
const _ = require('lodash'); const users = [ < name: 'Alice', age: 25 >, < name: 'Bob', age: 30 >, < name: 'Charlie', age: 35 >, ]; const filteredUsers = _.filter(users, user => user.age >= 30);Этот код более лаконичный и легкий для чтения, а использование функции filter от Lodash дает понять, что делает код.
Moment.js или date-fns
Moment.js — это библиотека для синтаксического анализа, манипулирования и форматирования даты и времени. Используя Moment.js, можно избежать написания сложного кода манипуляции с датой и снизить вероятность ошибок. Например, рассмотрим следующий код для форматирования даты в виде строки:
const date = new Date(); const formattedDate = `$-$-$`;Используя Moment.js, вы можете вместо этого написать следующий код:
const moment = require('moment'); const date = new Date(); const formattedDate = moment(date).format('YYYY-MM-DD');Этот код легче читать и поддерживать, а использование функции format от Moment.js дает понять, что делает код.
Winston
Winston — это библиотека логирования (журналирования), которая предоставляет множество функций для ведения журнала сообщений, включая различные уровни логов и настраиваемое форматирование. Используя Winston, вы можете избежать написания пользовательского кода ведения журнала и убедиться, что ваши журналы согласованы и легко читаются. Например, рассмотрим следующий код для регистрации сообщения:
console.log(`[$] INFO: User logged in`);Используя Winston, вы можете вместо этого написать следующий код
const winston = require('winston'); const logger = winston.createLogger( < level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.printf(info =>`[$] $: $`) ), transports: [ new winston.transports.Console(), new winston.transports.File(< filename: 'app.log' >), ], >); logger.info('User logged in');Этот код более настраиваемый и легко читаемый, а использование Winston дает понять, что делает код.
Итоги
Чистая архитектура — это мощная концепция, которая может помочь вам создать модульный и поддерживаемый код в проектах Node.js. Следуя принципам чистой архитектуры, можно создать кодовую базу, которую легко понять, протестировать и расширить. Можно также использовать сторонние пакеты и различные структуры папок для повышения чистоты кода. Реализация Clean Architecture в проектах Node.js может помочь вам избежать распространенных ошибок, таких как дублирование кода, неорганизованная кодовая база и тесная связь.





