![backdrop](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`). ### **Практические шаги**: 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, который содержит его описание, версию, список зависимостей и скриптов. ### **Практические шаги**: 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). ### **Практические шаги**: В папке `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 должен игнорировать. ### **Практические шаги**: 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). При этом также создаются отдельные папки и файлы под разные части проекта. Например, для: - базы данных; - утилит; - объектов; - маршрутов; и так далее. В нашем примере мы ограничимся работой в двух файлах: **`server.js`** и **`app.js`**, а более мощную файловую структуру изучим позже. ### **Практические шаги**: 1. В папке `backend` создайте папку `src`. 2. Внутри `src` создайте два файла: `app.js` и `server.js`. 3. **В файл `app.js`** пока добавьте только создание экземпляра приложения: ```javascript // backend/src/app.js import express from 'express'; const app = express(); export default app; // Экспортируем app для использования в server.js ``` 4. **В файл `server.js`** добавьте код запуска сервера: ```javascript // backend/src/server.js import app from './app.js'; const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Сервер запущен и слушает порт ${PORT}`); }); ``` ### **Что должно получиться**: Создана модульная структура приложения. Пока при запуске сервер будет работать, но на любой запрос возвращать ошибку `404`, так как маршруты не определены. --- ## ▶️ **Урок 6: Первый запуск сервера** ### **Цель урока**: Запустить сервер и убедиться, что он работает, возвращая ожидаемую ошибку. ### **Практические шаги**: 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 (конечной точке) и HTTP-методу (GET, POST и т.д.). - **Обработчик (handler)** — функция, которая принимает запрос (`req`) и формирует ответ (`res`). ### **Практические шаги**: 1. Откройте файл `backend/src/app.js`. 2. **Добавьте обработчик для корневого маршрута (`/`) перед строкой `export default app;`**: ```javascript // Обработчик GET-запроса на корневой URL '/' app.get('/', (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`. ### **Примеры разных параметров**: 1. **Один параметр**: ```javascript // Когда заходят на /hello/Максим app.get('/hello/:name', (req, res) => { const name = req.params.name; // = "Максим" res.send(`Привет, ${name}!`); }); ``` 2. **Несколько параметров**: ```javascript // Когда заходят на /book/сказки/5 app.get('/book/:genre/:page', (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', (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', (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 и возвращает текстовый результат. ### **Задание**: Создайте 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: Базовые проверки в калькуляторе** ### **Цель урока**: Добавить минимальную проверку входных данных. ### **Задание**: Доработайте калькулятор из урока 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 } ``` ### **Практическое задание**: 1. Создайте маршрут `GET /api/user/:id` 2. Если `:id` — число, верните JSON с информацией о пользователе: ```javascript app.get('/api/user/:id', (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. ### **Задание**: Измените все 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(n => n.id === id);`. Если заметка найдена — верните её, если нет — верните ошибку 404. Для DELETE `/api/notes/:id` найдите индекс заметки по ID: `const index = notes.findIndex(n => n.id === id);`, затем удалите её с помощью `splice(index, 1)` и верните подтверждение удаления. ### Важные моменты Заметки будут храниться только в памяти сервера и исчезнут при его перезапуске. Для постоянного хранения данных потребуется база данных или файловая система, но это тема для следующих занятий. ### Вопросы для самопроверки 1. Чем отличается `notes[0]` от `notes.find(n => n.id === 1)`? 2. Почему лучше использовать отдельный счетчик `nextId`, а не просто длину массива? 3. Какие HTTP-методы соответствуют операциям CRUD? --- ![](img2.jpg)