![](img.jpg) # Разрабатываем первое Web API на Node.js Для секции "Веб-разработка: пример Fullstack" ## Введение Урок предназначен для аудитории, имеющей базовые навыки работы с HTML/CSS, Git и SSH. Цель: освоить основы разработки Web API с использованием Node.js и фреймворка Express за ограниченное время (3 часа). Рекомендую [очень хороший ресурс с теорией и практикой по Node.js](https://metanit.com/web/nodejs/1.1.php) --- ## 📘 **Урок 1: Создание репозитория и организации структуры проекта** ### **Цель урока**: Создать GitHub-репозиторий, организовать структуру проекта и научиться работать с ветками в Git. ### **Краткая теория**: - **GitHub** — платформа для хостинга и совместной разработки IT-проектов с использованием системы контроля версий Git. - **Репозиторий** — хранилище кода и истории его изменений. - **Ветка (branch)** — изолированная копия проекта для разработки новой функциональности без влияния на основной код (часто называется `main` или `master`). ### **Теория: Для чего создавать репозиторий и ветки** Создание репозитория — это первый шаг к организации вашего кода в профессиональной среде. Репозиторий хранит не только текущую версию проекта, но и всю историю изменений, что позволяет отслеживать, кто, когда и какие правки вносил. Это критически важно для командной работы, так как позволяет нескольким разработчикам работать над одним проектом одновременно, не мешая друг другу. Ветки в Git создаются для изоляции новой функциональности или экспериментов от основного кода. Вы работаете в своей ветке, и пока вы не объедините её с основной, ваши изменения не повлияют на стабильную версию проекта. Это позволяет безопасно тестировать новые идеи и реализовывать фичи без риска сломать уже работающую систему. ### **Практические шаги**: 1. **Создайте репозиторий на GitHub**: * Авторизуйтесь на [GitHub](https://github.com). * Нажмите `+` в правом верхнем углу и выберите `New repository`. * В поле `Repository name` укажите название, отражающее тему вашего API (например, `my-first-api`). * Оставьте репозиторий публичным (`Public`). * **Поставьте галочку** `Add a README.md`. Это создаст начальный файл описания проекта. * Нажмите `Create repository`. 2. **Клонируйте репозиторий и создайте учебную ветку**: ```bash # Склонируйте репозиторий на компьютер git clone git@github.com:<ваш-логин>/<название-репозитория>.git cd <название-репозитория> # Создайте и переключитесь на новую ветку 'study' git checkout -b study # Если в команде двое, каждый создает свою ветку (например, study-ivanov) # git checkout -b study-фамилия ``` ### **Что должно получиться**: * Создан удаленный репозиторий на GitHub с файлом `README.md`. * На вашем компьютере есть локальная копия проекта. * Вы находитесь в новой ветке (`study` или `study-фамилия`), а не в основной (`main`). --- ## 📦 **Урок 2: Инициализация проекта Node.js** - [Metanit: Файл package.json и конфигурация проекта](https://metanit.com/web/nodejs/2.7.php) ### **Цель урока**: Создать структуру папок для backend и инициализировать проект Node.js. ### **Краткая теория**: - **Node.js** — это среда выполнения JavaScript вне браузера, которая позволяет создавать серверные приложения. - **package.json** — это файл манифеста проекта Node.js, который содержит его описание, версию, список зависимостей и скриптов. ### **Теория: Почему нужны Node.js и package.json** Node.js преобразует JavaScript из языка, работающего только в браузере, в полноценный инструмент для создания серверной логики. Это означает, что вы можете писать код, который будет выполняться на сервере, обрабатывать запросы от клиентов (например, браузеров или мобильных приложений), работать с базами данных и возвращать результаты. Файл `package.json` является сердцем любого проекта на Node.js. В нём описывается проект: его название, версия, автор. Но главное — он хранит список всех внешних библиотек (зависимостей), которые использует ваш проект. Это позволяет любому другому разработчику склонировать ваш проект и одной командой установить всё необходимое для его работы. Установка `"type": "module"` говорит Node.js использовать современный синтаксис модулей (import/export) вместо устаревшего CommonJS, что делает код более стандартизированным и удобным для чтения. ### **Практические шаги**: 1. **Создайте папку `backend`** внутри проекта и перейдите в неё. Можно сделать это в Git Bash: ```bash mkdir backend cd backend ``` 2. **Инициализируйте проект Node.js**. Используйте флаг `-y`, чтобы принять значения по умолчанию. ```bash npm init -y # Нужно для поддержи современного синтаксиса ES Module npm pkg set type="module"; ``` ### **Проверка результата**: Убедитесь, что в папке `backend` появился файл `package.json`. В его первой строке должно быть указано `"type": "module"`. --- ## 🚀 **Урок 3: Установка фреймворка Express** - [Metanit: Начало работы с Express](https://metanit.com/web/nodejs/4.1.php) ### **Цель урока**: Установить библиотеку Express — популярный фреймворк для создания веб-приложений и API на Node.js. ### **Краткая теория**: - **Express** — это минималистичный и гибкий фреймворк, который значительно упрощает создание серверных приложений и маршрутов. - **Зависимости (dependencies)** — это сторонние библиотеки (пакеты), которые использует ваш проект. Они управляются с помощью `npm` (Node Package Manager). ### **Теория: Роль Express и менеджера пакетов npm** Node.js сам по себе предоставляет базовые инструменты для работы с сетью, но писать на чистом Node.js сложно и долго. Express — это фреймворк, который добавляет над Node.js слой абстракции. Он предоставляет простые и понятные методы для описания маршрутов (URL-адресов, на которые реагирует сервер), обработки HTTP-запросов (GET, POST и других) и формирования ответов. Без Express пришлось бы вручную разбирать заголовки запросов и собирать ответы, что является рутинной и ошибкоопасной работой. npm (Node Package Manager) — это инструмент для управления зависимостями. Когда вы устанавливаете Express командой `npm install express`, npm скачивает библиотеку и все её зависимости из центрального репозитория в папку `node_modules` и фиксирует точную версию в `package.json`. Это гарантирует, что у всех разработчиков проекта будут одинаковые версии библиотек, что предотвращает проблемы с совместимостью. ### **Практические шаги**: В папке `backend` выполните команду установки: ```bash npm install express ``` ### **Что должно получиться**: * В файле `package.json` в раздел `"dependencies"` добавится `"express"` с номером версии. * В проекте появится папка `node_modules`, где хранятся все установленные библиотеки. --- ## 🗑️ **Урок 4: Работа с `.gitignore` и первый коммит** [Atlassian: Файл .gitignore — игнорирование файлов в Git](https://www.atlassian.com/ru/git/tutorials/saving-changes/gitignore) ### **Цель урока**: Научиться игнорировать ненужные для репозитория файлы и зафиксировать изменения. ### **Краткая теория**: - **`node_modules`** — это папка со всеми зависимостями проекта. Её **нельзя** добавлять в Git, так как она очень большая и её можно восстановить командой `npm install`. - **`.gitignore`** — специальный файл, в котором перечисляются шаблоны файлов и папок, которые Git должен игнорировать. ### **Теория: Контроль версий и игнорирование файлов** Система контроля версий Git предназначена для отслеживания изменений в исходном коде, а не в сгенерированных файлах или конфиденциальных данных. Папка `node_modules` содержит тысячи файлов установленных библиотек. Её размер может составлять сотни мегабайт. Хранить её в Git неэффективно, потому что её всегда можно восстановить по информации из `package.json` командой `npm install`. Файл `.gitignore` — это набор правил для Git. Каждая строка в этом файле — это паттерн (например, `node_modules/` или `*.log`), который указывает Git, какие файлы или папки следует полностью игнорировать и не включать в репозиторий. Это помогает сохранять репозиторий чистым, небольшим и безопасным (например, игнорируя файлы с паролями `.env`). Создание коммита — это операция фиксации текущего состояния вашего кода в истории Git. Каждый коммит имеет уникальный идентификатор и сообщение, описывающее изменения. Это позволяет в любой момент вернуться к любой ранее сохранённой версии проекта. ### **Практические шаги**: 1. **Создайте файл `.gitignore`** в основной папке вашего проекта (не внутри `backend`). 2. **Добавьте в него шаблоны**. Вы можете создать базовое содержимое на сайте [gitignore.io](https://www.toptal.com/developers/gitignore). Введите `Node, Windows, VisualStudioCode` и скопируйте сгенерированный текст в ваш файл. 3. **Убедитесь, что в `.gitignore` есть строки**: ``` node_modules/ .env ``` 4. **Выполните коммит**: ```bash # Вернитесь в корень проекта (если вы в папке backend) cd .. # Добавьте все новые файлы в staging area git add . # Убедитесь, что папка node_modules не добавилась и зафиксируйте изменения с комментарием git commit -m "feat: инициализирован проект Node.js с Express, добавлен .gitignore" ``` --- ## 📄 **Урок 5: Создание базовой структуры сервера** - [MDN: Введение в Express/Node](https://developer.mozilla.org/ru/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction) - [Habr: Лучшие практики Node.js — советы по структуре проектов](https://habr.com/ru/articles/454476/) ### **Цель урока**: Создать основные файлы приложения и понять их роль. ### **Краткая теория**: Серверное приложение на Express обычно разделяют на несколько файлов для лучшей организации: - **`server.js`** — точка входа. Запускает сервер, подключает основные настройки. - **`app.js`** — ядро приложения. Содержит настройки Express и все маршруты (routes). ### **Теория: Архитектура серверного приложения** Разделение кода на разные файлы (`app.js` и `server.js`) следует принципу разделения ответственности. `app.js` отвечает за логику приложения: настройку middleware (промежуточного ПО), определение всех маршрутов и обработчиков. `server.js` отвечает за инфраструктуру: запуск HTTP-сервера на определённом порту и обработку сетевых соединений. Такое разделение делает код модульным и тестируемым. Вы можете импортировать объект `app` из `app.js` в тесты, не запуская реальный сервер. Переменная окружения `process.env.PORT` используется для настройки порта. На локальной машине вы используете порт 3000, но когда приложение размещается на хостинге (например, Heroku или Render), платформа сама назначает порт через эту переменную. Это стандартный подход для облачных сервисов. В нашем примере мы используем максимально базовую файловую структуру. Настоящие приложения, как правило, очень сложные. Для того, чтобы их было удобно поддерживать, используют более сложную, но при этом изящную и удобную файлову структуру. Пример того, как могло бы выглядеть приложение в продакшене: ``` my-first-api/ ├── backend/ │ ├── src/ │ │ ├── app.js # Ядро приложения: настройки Express, middleware │ │ ├── server.js # Точка входа: запуск сервера │ │ ├── config/ # Конфигурационные файлы │ │ │ ├── database.js # Настройки подключения к БД │ │ │ └── constants.js # Константы приложения │ │ ├── routes/ # Папка для всех маршрутов │ │ │ ├── api/ # API маршруты │ │ │ │ ├── index.js # Объединяет все API-маршруты │ │ │ │ ├── users.js # Маршруты для работы с пользователями │ │ │ │ ├── posts.js # Маршруты для работы с постами │ │ │ │ └── auth.js # Маршруты для аутентификации │ │ │ └── web.js # Веб-маршруты (если есть HTML-страницы) │ │ ├── controllers/ # Контроллеры (обработчики маршрутов) │ │ │ ├── userController.js │ │ │ ├── postController.js │ │ │ └── authController.js │ │ ├── models/ # Модели данных (структура БД) │ │ │ ├── User.js │ │ │ ├── Post.js │ │ │ └── index.js # Инициализация всех моделей │ │ ├── middleware/ # Промежуточное ПО (middleware) │ │ │ ├── auth.js # Проверка авторизации │ │ │ ├── validation.js # Валидация данных │ │ │ └── errorHandler.js # Обработка ошибок │ │ ├── services/ # Бизнес-логика (сервисный слой) │ │ │ ├── userService.js │ │ │ └── postService.js │ │ ├── utils/ # Вспомогательные функции │ │ │ ├── helpers.js │ │ │ ├── validators.js │ │ │ └── logger.js # Логирование │ │ └── public/ # Статические файлы (CSS, JS, изображения) │ │ ├── css/ │ │ ├── js/ │ │ └── images/ │ ├── tests/ # Тесты │ │ ├── unit/ # Модульные тесты │ │ └── integration/ # Интеграционные тесты │ ├── .env # Переменные окружения (НЕ коммитить в Git!) │ ├── .env.example # Пример .env файла (без реальных значений) │ ├── package.json │ └── package-lock.json ├── frontend/ # Если это fullstack-проект │ ├── src/ │ └── public/ ├── .gitignore ├── README.md └── docker-compose.yml # Если используется Docker ``` ### **Практические шаги**: 1. В папке `backend` создайте папку `src`. 2. Внутри `src` создайте два файла: `app.js` и `server.js`. 3. **В файл `app.js`** пока добавьте только создание экземпляра приложения: ```javascript // backend/src/app.js import express from 'express'; // Создание экземпляра приложения Express const app = express(); export default app; // Экспортируем app для использования в server.js ``` 4. **В файл `server.js`** добавьте код запуска сервера: ```javascript // backend/src/server.js import app from './app.js'; // Выбираем порт из файла конфигурации .env, либо используем порт 3000 по умолчанию const PORT = process.env.PORT || 3000; app.listen(PORT, function() { console.log(`Сервер запущен и слушает порт ${PORT}`); }); ``` ### **Что должно получиться**: Создана модульная структура приложения. Сейчас при запуске сервер (технически) будет работать, но на все запросы будет отвечать ошибкой `404`, так мы ещё не научили сервер обрабатывать маршруты. --- ## ▶️ **Урок 6: Первый запуск сервера** ### **Цель урока**: Запустить сервер и убедиться, что он работает, возвращая ожидаемую ошибку. ### **Теория: Жизненный цикл HTTP-запроса и обработка ошибок** Когда вы вводите адрес в браузере, браузер отправляет HTTP-запрос на указанный сервер и порт. Запущенное приложение Node.js с Express прослушивает этот порт и получает запрос. Express проверяет, есть ли зарегистрированный маршрут (обработчик), который соответствует URL и методу запроса. Если маршрут не найден, Express автоматически генерирует и отправляет обратно ответ с HTTP-статусом `404 (Not Found)` и стандартной HTML-страницей ошибки. Сообщение "Cannot GET /" прямо указывает, что для метода `GET` по пути `/` обработчик не найден. Успешный запуск сервера и получение этой ошибки — важный этап. Он подтверждает, что серверная часть работает, сетевые подключения устанавливаются, и фреймворк Express корректно обрабатывает входящие запросы. Теперь ваша задача — научить сервер правильно на них отвечать. ### **Практические шаги**: 1. Из папки `backend` выполните команду: ```bash node src/server.js ``` 2. В консоли должно появиться сообщение: `Сервер запущен и слушает порт 3000`. 3. Откройте браузер и перейдите по адресу `http://localhost:3000`. 4. **Вы должны увидеть стандартную страницу ошибки `Cannot GET /`**. Это ожидаемо, так как мы не создали обработчик для корневого маршрута (`/`). 5. Остановите сервер, нажав `Ctrl + C` в терминале. 6. **Выполните коммит**: ```bash git add . git commit -m "feat: создана базовая структура сервера, успешный запуск" git push origin study # Или study-фамилия ``` --- ## 🖐️ **Урок 7: Создание первого маршрута (Hello World)** ### **Цель урока**: Создать простейший обработчик GET-запроса и получить ответ от сервера. ### **Краткая теория**: - **Маршрут (route)** — это правило, которое определяет, как сервер будет реагировать на клиентский запрос к определенному URL (конечной точке/endpoint) и HTTP-методу (GET, POST и т.д.). - **Обработчик (handler)** — функция, которая принимает запрос (`req`) и формирует ответ (`res`). ### **Теория: Механизм маршрутизации в Express** Метод `app.get()` регистрирует обработчик для GET-запросов по указанному пути. Первый аргумент — это путь (`'/'`), второй — функция-обработчик. Когда приходит запрос, Express сравнивает его метод и URL с зарегистрированными маршрутами и выполняет функцию первого подходящего. Функция-обработчик получает два объекта: `req` (запрос) и `res` (ответ). `req` содержит информацию о входящем запросе: заголовки, параметры, тело. `res` используется для формирования и отправки ответа клиенту. `res.setHeader()` устанавливает HTTP-заголовки ответа. Заголовок `Content-Type: text/plain` сообщает клиенту, что тело ответа — простой текст, а не HTML или JSON. `res.send()` завершает запрос, отправляет указанные данные и закрывает соединение. Это базовый строительный блок любого Web API — связь URL с конкретной функцией, выполняющей полезную работу. ### **Практические шаги**: 1. Откройте файл `backend/src/app.js`. 2. **Добавьте обработчик для корневого маршрута (`/`) перед строкой `export default app;`**: ```javascript // Обработчик GET-запроса на корневой URL '/' app.get('/', function(req, res) { // Устанавливаем заголовок ответа res.setHeader('Content-Type', 'text/plain'); // Отправляем текстовый ответ res.send('Hello World from my API!'); }); ``` 3. Запустите сервер снова (`node src/server.js`) и обновите страницу `http://localhost:3000` в браузере. 4. **Вы должны увидеть надпись `Hello World from my API!`**. 5. **Выполните коммит**. --- ## 🔢 **Урок 8: Работа с параметрами пути (Path Parameters)** - [Expressjs: Маршрутизация в Express](https://www.expressjs.com.cn/ru/guide/routing.html) - [Nodejsdev: Как построить REST API с помощью JS, Node.js и Express.js](https://nodejsdev.ru/guides/rest-api-design/) ### **Цель урока**: Научиться создавать динамические маршруты и извлекать данные из URL. ### **Краткая теория**: - **Статические маршруты** — это фиксированные адреса, например, `/about` или `/contacts`. Они всегда одинаковы. - **Динамические маршруты** — это адреса, которые могут меняться. Например, страница профиля с разными именами пользователей: `/user/anna`, `/user/max`. - **Параметры пути (Path Parameters)** — специальные части URL, которые меняются. В Express они обозначаются двоеточием (`:`), например: `/user/:username`. - Когда кто-то заходит на `/user/anna`, Express видит `:username` в маршруте и понимает: "вместо `:username` подставлено `anna`". Это значение сохраняется в `req.params`. ### **Теория: Динамическая маршрутизация и извлечение данных** Параметры пути превращают статический маршрут в шаблон, способный обрабатывать множество разных запросов. Синтаксис `:parameterName` указывает Express, что на этой позиции в URL может находиться любое значение. Express автоматически извлекает это значение из URL и помещает его в объект `req.params` под соответствующим ключом. Это основной механизм для создания RESTful API, где URL идентифицирует ресурс (например, `/users/123` идентифицирует пользователя с ID 123). Проверка `isNaN(input)` необходима, потому что параметры из URL всегда приходят в виде строк. Даже если в URL было `/double/7`, в `req.params.number` попадёт строка `"7"`. `parseFloat()` преобразует строку в число, а `isNaN()` проверяет, удалось ли это преобразование. Это защищает ваш API от некорректных данных и предотвращает ошибки выполнения. ### **Примеры разных параметров**: 1. **Один параметр**: ```javascript // Когда заходят на /hello/Максим app.get('/hello/:name', function(req, res) { const name = req.params.name; // = "Максим" res.send(`Привет, ${name}!`); }); ``` 2. **Несколько параметров**: ```javascript // Когда заходят на /book/сказки/5 app.get('/book/:genre/:page', function(req, res) { const genre = req.params.genre; // = "сказки" const page = req.params.page; // = "5" res.send(`Жанр: ${genre}, Страница: ${page}`); }); ``` 3. **Параметр в середине пути**: ```javascript // Когда заходят на /category/электроника/product/123 app.get('/category/:categoryName/product/:productId', function(req, res) { const category = req.params.categoryName; // = "электроника" const productId = req.params.productId; // = "123" res.send(`Категория: ${category}, Товар: ${productId}`); }); ``` ### **Практические шаги**: 1. В файле `app.js` добавьте маршрут: ```javascript // Пример запроса: GET http://localhost:3000/double/7 app.get('/double/:number', function(req, res) { const input = req.params.number; // Получаем параметр из URL // Проверяем, является ли параметр числом if (!isNaN(input)) { const number = parseFloat(input); const result = number * 2; res.send(`Если удвоить ${number}, получится ${result}`); } else { res.status(400).send('Пожалуйста, укажите число. Например: /double/10'); } }); ``` 2. Протестируйте: - `http://localhost:3000/double/7` → работает - `http://localhost:3000/double/abc` → ошибка --- ## ➕➖✖️➗ **Урок 9: Практическое задание — Текстовый API-калькулятор** ### **Цель задания**: Создать простой калькулятор, который принимает числа в URL и возвращает текстовый результат. ### **Теория: Построение функциональных API-эндпоинтов** Это задание демонстрирует, как проектировать специализированные конечные точки (endpoints) API. Префикс `/api/calc/` создаёт пространство имён для всех операций калькулятора, что соответствует хорошей практике организации API. Каждая математическая операция получает собственный маршрут с чётко определёнными входными параметрами (`:a` и `:b`). Это делает API предсказуемым и удобным для использования. Возврат результата в виде простой текстовой строки с полным выражением (`5 + 3 = 8`) даёт пользователю API не только ответ, но и контекст, подтверждая, какая операция была выполнена и с какими числами. Такой подход облегчает отладку и интеграцию. Преобразование параметров из строк в числа с помощью `parseFloat()` необходимо для выполнения математических операций, так как все данные из URL изначально являются строками. ### **Задание**: Создайте 4 маршрута для основных математических операций. Формат ответа — обычный текст. **Маршруты и примеры**: 1. **Сложение**: `GET /api/calc/sum/:a/:b` - Пример: `GET /api/calc/sum/5/3` - Ответ: `5 + 3 = 8` 2. **Вычитание**: `GET /api/calc/subtract/:a/:b` - Пример: `GET /api/calc/subtract/10/4` - Ответ: `10 - 4 = 6` 3. **Умножение**: `GET /api/calc/multiply/:a/:b` - Пример: `GET /api/calc/multiply/7/2` - Ответ: `7 * 2 = 14` 4. **Деление**: `GET /api/calc/divide/:a/:b` - Пример: `GET /api/calc/divide/15/3` - Ответ: `15 / 3 = 5` ### **Как это сделать**: 1. Для каждого маршрута получите параметры из `req.params.a` и `req.params.b` 2. Преобразуйте их в числа: `const num1 = parseFloat(req.params.a)` 3. Выполните операцию 4. Верните ответ в формате: ``res.send(`${num1} + ${num2} = ${result}`)`` ### **Важно**: - Пока не нужно проверять, корректные ли данные прислал пользователь - Используйте шаблонные строки (обратные кавычки) для формирования ответа --- ## ⚠️ **Урок 10: Базовые проверки в калькуляторе** ### **Цель урока**: Добавить минимальную проверку входных данных. ### **Теория: Валидация входных данных в API** Любой API, который принимает данные от пользователя, должен проверять их корректность перед обработкой. Это называется валидацией. Без валидации некорректные данные (например, текст вместо числа) могут вызвать ошибку в логике программы (`NaN` в математических операциях) или даже привести к падению сервера. Проверка `isNaN(a) || isNaN(b)` выявляет случаи, когда преобразование строки в число не удалось. Важно использовать `return res.status(400).send(...)` после отправки ошибки. Ключевое слово `return` гарантирует, что выполнение функции обработчика немедленно прекратится после отправки ответа с ошибкой. Без `return` код продолжил бы выполняться, пытаясь произвести вычисления с некорректными данными, что привело бы к внутренней ошибке сервера (500). Статус `400 (Bad Request)` — стандартный HTTP-код, указывающий клиенту, что проблема в присланных им данных, а не в работе сервера. Отдельная проверка деления на ноль защищает от математической ошибки, которая даст в результате `Infinity`. ### **Задание**: Доработайте калькулятор из урока 9, добавив две простые проверки: 1. **Проверка, что оба параметра — числа** 2. **Для деления — проверка, что второй параметр не ноль** ### **Как это сделать**: **Шаг 1: Проверка на число** В начале каждого обработчика добавьте: ```javascript const a = parseFloat(req.params.a); const b = parseFloat(req.params.b); // Проверяем, являются ли оба параметра числами if (isNaN(a) || isNaN(b)) { return res.status(400).send('Ошибка: оба параметра должны быть числами'); } ``` **Шаг 2: Проверка деления на ноль** Только в обработчике деления добавьте после проверки на числа: ```javascript // Проверка деления на ноль if (b === 0) { return res.status(400).send('Ошибка: на ноль делить нельзя'); } ``` ### **Что должно происходить**: - `/api/calc/sum/5/3` → `5 + 3 = 8` (работает) - `/api/calc/sum/5/abc` → `Ошибка: оба параметра должны быть числами` (ошибка 400) - `/api/calc/divide/10/0` → `Ошибка: на ноль делить нельзя` (ошибка 400) --- ## 📊 **Урок 11: Введение в формат JSON** - [MDN: Работа с JSON](https://developer.mozilla.org/ru/docs/Learn_web_development/Core/Scripting/JSON) - [Microsoft Learn: Создание API, получающего JSON](https://learn.microsoft.com/ru-ru/shows/beginners-series-to-nodejs/how-to-create-an-api-that-receive-json-with-nodejs-and-express-20-of-26) ### **Цель урока**: Понять, что такое JSON и научиться отправлять данные в этом формате. ### **Краткая теория**: - **JSON (JavaScript Object Notation)** — популярный формат для обмена данными; - у JSON есть строгий набор правил: - Все ключи в двойных кавычках - Значения: строки (в кавычках), числа, true/false, null, массивы [], объекты {} - **Пример JSON**: ```json { "имя": "Иван", "возраст": 16, "хобби": ["программирование", "шахматы"], "студент": true } ``` ### **Теория: JSON как стандарт обмена данными** JSON стал универсальным языком для взаимодействия между различными частями веб-приложений: сервером и клиентом (браузером, мобильным приложением), а также между разными сервисами. Его главные преимущества — читаемость для человека и простота разбора для машины. В отличие от простого текста, JSON позволяет структурировать сложные данные, объединяя их в объекты с ключами и значениями, а также во вложенные списки (массивы). Метод `res.json()` в Express выполняет две важные задачи: во-первых, он преобразует переданный ему JavaScript-объект или массив в корректную JSON-строку; во-вторых, он автоматически устанавливает HTTP-заголовок ответа `Content-Type: application/json`. Этот заголовок сообщает клиенту, что в ответе пришли структурированные данные в формате JSON, которые нужно соответствующим образом интерпретировать, а не отображать как простой текст. Возврат ошибок также в формате JSON (`res.status(400).json(...)`) делает API последовательным: клиент всегда ожидает получить JSON и может единообразно обрабатывать как успешные ответы, так и ошибки. ### **Практическое задание**: 1. Создайте маршрут `GET /api/user/:id` 2. Если `:id` — число, верните JSON с информацией о пользователе: ```javascript app.get('/api/user/:id', function(req, res) { const id = req.params.id; if (!isNaN(id)) { // Формируем вручную объект пользователя. // Пока что просто притворимся, что мы загрузили его из базы данных. const userData = { userId: Number(id), name: "Алексей Петров", email: "alexey@example.com", registered: true }; // Отправляем как JSON res.json(userData); } else { // Если id не число - ошибка в JSON формате res.status(400).json({ error: "ID должен быть числом", example: "/api/user/123" }); } }); ``` 3. Протестируйте в браузере: - `http://localhost:3000/api/user/123` → увидите красиво оформленный JSON - `http://localhost:3000/api/user/abc` → JSON с ошибкой ### Обратите внимание В примере мы возвращаем фальшивые данные. С настоящими динамическими данными (списки/объекты БД) мы поработаем позже. --- ## 🔄 **Урок 12: Калькулятор с JSON-ответами** ### **Цель урока**: Переделать текстовый калькулятор для возврата JSON. ### **Теория: Проектирование структуры JSON-ответов** Структурированный JSON-ответ должен быть информативным и машиночитаемым. Введение поля `success: true/false` позволяет клиенту быстро определить, был ли запрос успешным, без необходимости анализировать HTTP-статус код или содержимое ответа. Поле `operation` явно указывает, какая операция была выполнена, что полезно при логировании и отладке. Объект `arguments` дублирует исходные данные, предоставляя контекст. Поле `result` содержит итог вычислений. В случае ошибки вместо данных результата возвращается объект `error` с детализацией. Поле `code` (например, `"DIVISION_BY_ZERO"`) — это машинно-читаемый код ошибки, по которому клиентское приложение может выполнить конкретное действие (например, показать определённое сообщение). Поле `message` содержит человекочитаемое описание. Такой подход соответствует лучшим практикам проектирования REST API, делая его предсказуемым и удобным для интеграции. ### **Задание**: Измените все 4 операции калькулятора так, чтобы они возвращали данные в JSON-формате. ### **Формат успешного ответа**: ```json { "success": true, "operation": "sum", "arguments": { "a": 5, "b": 3 }, "result": 8 } ``` ### **Формат ответа с ошибкой**: ```json { "success": false, "error": { "code": "DIVISION_BY_ZERO", "message": "Делитель не может быть равен нулю" } } ``` ### **Как изменить код**: 1. **Заменяем `res.send()` на `res.json()`** 2. **Для успешного ответа** формируем объект: ```javascript const response = { success: true, operation: "sum", // или "subtract", "multiply", "divide" arguments: { a: a, b: b }, result: a + b // здесь ваша операция }; res.json(response); ``` 3. **Для ошибок**: ```javascript res.status(400).json({ success: false, error: { code: "INVALID_NUMBERS", // или "DIVISION_BY_ZERO" message: "Оба параметра должны быть числами" } }); ``` ### **Примеры кодов ошибок**: - `"INVALID_NUMBERS"` — когда параметры не числа - `"DIVISION_BY_ZERO"` — когда делим на ноль --- # 👷 **Дополнительные задания (повышенная сложность!)** ## 📝 Задание 1: Использование POST и DELETE методов для редактирования текста в объявлении сайта ### Цель задания Научиться использовать методы POST и DELETE для изменения данных на сервере, понять разницу между методами, которые только читают данные (GET) и теми, которые их изменяют. ### Теория: HTTP-методы и состояние сервера HTTP-методы определяют тип операции, которую мы хотим выполнить. GET используется для получения данных без изменений, POST — для создания или отправки новых данных, DELETE — для удаления данных. В этом задании вы будете использовать переменную на сервере для хранения состояния приложения между запросами. Это означает, что все пользователи, которые обращаются к серверу, будут видеть одно и то же объявление. ### Практическая задача Создайте API для управления объявлением на сайте с тремя методами: 1. **Просмотр объявления** — GET `/api/ad` Возвращает текущее объявление или сообщение о его отсутствии. 2. **Установка объявления** — POST `/api/ad` Устанавливает новое объявление, отправленное в теле запроса. 3. **Очистка объявления** — DELETE `/api/ad` Полностью удаляет текущее объявление. ### Как реализовать Начните с создания переменной для хранения объявления в начале файла `app.js`, после импортов. Например: `let currentAd = null;` Для GET-метода проверьте, есть ли значение в переменной `currentAd`. Если объявление существует — верните его, если нет — сообщите, что объявление не установлено. Для POST-метода потребуется middleware `express.json()` для чтения JSON из тела запроса. Добавьте строку `app.use(express.json())` в `app.js` после создания экземпляра приложения. В обработчике POST получите текст объявления из `req.body.text` и сохраните его в переменную `currentAd`. DELETE-метод должен просто устанавливать `currentAd = null` и возвращать подтверждение удаления. ### Важные вопросы для обсуждения - Почему для изменения данных лучше использовать POST, а не GET? - Как данные сохраняются между запросами разных пользователей? - Что произойдет с объявлением при перезапуске сервера? Для тестирования этих методов вам понадобится специальный инструмент вроде cURL или Postman, так как браузер по умолчанию отправляет только GET-запросы. --- ## 📒 Задание 2: "Ящик заметок" с использованием массива ### Цель задания Научиться работать с массивами в JavaScript и реализовать базовые CRUD-операции (Create, Read, Update, Delete). ### Теория: Массивы в JavaScript Массив — это упорядоченный список элементов. Создать массив можно так: `let notes = []` (пустой массив) или `let notes = ["Первая заметка", "Вторая"]` (массив с данными). Основные операции с массивами: - Добавление элемента: `notes.push("Новая заметка")` - Получение элемента: `notes[0]` (первый элемент, индексы начинаются с 0) - Удаление элемента: `notes.splice(индекс, 1)` - Длина массива: `notes.length` ### Теория: CRUD-операции CRUD — это основа большинства веб-приложений: - **Create** (Создать) — добавление новой заметки (POST) - **Read** (Чтение) — получение списка или одной заметки (GET) - **Update** (Обновление) — изменение существующей заметки (PUT/PATCH) - **Delete** (Удаление) — удаление заметки (DELETE) ### Практическая задача Создайте полноценное API для управления заметками с четырьмя операциями: 1. **Получить все заметки** — GET `/api/notes` 2. **Добавить заметку** — POST `/api/notes` 3. **Получить одну заметку** — GET `/api/notes/:id` 4. **Удалить заметку** — DELETE `/api/notes/:id` ### Как реализовать Создайте массив для хранения заметок и счетчик для ID: ```javascript let notes = []; let nextId = 1; ``` Для GET `/api/notes` просто верните весь массив `notes` в формате JSON. Для POST `/api/notes` получите текст заметки из `req.body.text`, создайте объект заметки с уникальным ID и текущей датой, добавьте его в массив с помощью `push()`, увеличьте `nextId` и верните созданную заметку. Для GET `/api/notes/:id` найдите заметку в массиве по ID. Используйте метод `find()`: `const note = notes.find(function(n) { return n.id === id; });`. Если заметка найдена — верните её, если нет — верните ошибку 404. Для DELETE `/api/notes/:id` найдите индекс заметки по ID: `const index = notes.findIndex(function(n) { return n.id === id; });`, затем удалите её с помощью `splice(index, 1)` и верните подтверждение удаления. ### Важные моменты Заметки будут храниться только в памяти сервера и исчезнут при его перезапуске. Для постоянного хранения данных потребуется база данных или файловая система, но это тема для следующих занятий. ### Вопросы для самопроверки 1. Чем отличается `notes[0]` от `notes.find(function(n) { return n.id === 1; })`? 2. Почему лучше использовать отдельный счетчик `nextId`, а не просто длину массива? 3. Какие HTTP-методы соответствуют операциям CRUD?