Files
lesson_2/README.md
2025-12-12 21:06:28 +03:00

591 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
![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';
// Создание экземпляра приложения 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, () => {
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 (конечной точке/endpoint) и 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)