32 KiB
Разрабатываем первое Web API на Node.js
Для секции "Веб-разработка: пример Fullstack"
Введение
Урок предназачен для аудитории, имеющей базовые навыки работы с HTML/CSS, Git и SSH. Цель: освоить основы разработки Web API с использованием Node.js и фреймворка Express за ограниченное время (3 часа).
Рекомендую очень хороший ресурс с теорией и практикой по Node.js
📘 Урок 1: Создание репозитория и организации структуры проекта
Цель урока: Создать GitHub-репозиторий, организовать структуру проекта и научиться работать с ветками в Git.
Краткая теория:
- GitHub — платформа для хостинга и совместной разработки IT-проектов с использованием системы контроля версий Git.
- Репозиторий — хранилище кода и истории его изменений.
- Ветка (branch) — изолированная копия проекта для разработки новой функциональности без влияния на основной код (часто называется
mainилиmaster).
Практические шаги:
-
Создайте репозиторий на GitHub:
- Авторизуйтесь на GitHub.
- Нажмите
+в правом верхнем углу и выберитеNew repository. - В поле
Repository nameукажите название, отражающее тему вашего API (например,my-first-api). - Оставьте репозиторий публичным (
Public). - Поставьте галочку
Add a README.md. Это создаст начальный файл описания проекта. - Нажмите
Create repository.
-
Клонируйте репозиторий и создайте учебную ветку:
# Склонируйте репозиторий на компьютер 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
Цель урока: Создать структуру папок для backend и инициализировать проект Node.js.
Краткая теория:
- Node.js — это среда выполнения JavaScript вне браузера, которая позволяет создавать серверные приложения.
- package.json — это файл манифеста проекта Node.js, который содержит его описание, версию, список зависимостей и скриптов.
Практические шаги:
- Создайте папку
backendвнутри проекта и перейдите в неё. Можно сделать это в Git Bash:mkdir backend cd backend - Инициализируйте проект Node.js. Используйте флаг
-y, чтобы принять значения по умолчанию.npm init -y # Нужно для поддержи современного синтаксиса ES Module npm pkg set type="module";
Проверка результата:
Убедитесь, что в папке backend появился файл package.json. В его первой строке должно быть указано "type": "module".
🚀 Урок 3: Установка фреймворка Express
Цель урока: Установить библиотеку Express — популярный фреймворк для создания веб-приложений и API на Node.js.
Краткая теория:
- Express — это минималистичный и гибкий фреймворк, который значительно упрощает создание серверных приложений и маршрутов.
- Зависимости (dependencies) — это сторонние библиотеки (пакеты), которые использует ваш проект. Они управляются с помощью
npm(Node Package Manager).
Практические шаги:
В папке backend выполните команду установки:
npm install express
Что должно получиться:
- В файле
package.jsonв раздел"dependencies"добавится"express"с номером версии. - В проекте появится папка
node_modules, где хранятся все установленные библиотеки.
🗑️ Урок 4: Работа с .gitignore и первый коммит
Atlassian: Файл .gitignore — игнорирование файлов в Git
Цель урока: Научиться игнорировать ненужные для репозитория файлы и зафиксировать изменения.
Краткая теория:
node_modules— это папка со всеми зависимостями проекта. Её нельзя добавлять в Git, так как она очень большая и её можно восстановить командойnpm install..gitignore— специальный файл, в котором перечисляются шаблоны файлов и папок, которые Git должен игнорировать.
Практические шаги:
- Создайте файл
.gitignoreв основной папке вашего проекта (не внутриbackend). - Добавьте в него шаблоны. Вы можете создать базовое содержимое на сайте gitignore.io. Введите
Node, Windows, VisualStudioCodeи скопируйте сгенерированный текст в ваш файл. - Убедитесь, что в
.gitignoreесть строки:node_modules/ .env - Выполните коммит:
# Вернитесь в корень проекта (если вы в папке backend) cd .. # Добавьте все новые файлы в staging area git add . # Убедитесь, что папка node_modules не добавилась и зафиксируйте изменения с комментарием git commit -m "feat: инициализирован проект Node.js с Express, добавлен .gitignore"
📄 Урок 5: Создание базовой структуры сервера
Цель урока: Создать основные файлы приложения и понять их роль.
Краткая теория:
Серверное приложение на Express обычно разделяют на несколько файлов для лучшей организации:
server.js— точка входа. Запускает сервер, подключает основные настройки.app.js— ядро приложения. Содержит настройки Express и все маршруты (routes).
При этом также создаются отдельные папки и файлы под разные части проекта. Например, для:
- базы данных;
- утилит;
- объектов;
- маршрутов;
и так далее. В нашем примере мы ограничимся работой в двух файлах:
server.jsиapp.js, а более мощную файловую структуру изучим позже.
Практические шаги:
- В папке
backendсоздайте папкуsrc. - Внутри
srcсоздайте два файла:app.jsиserver.js. - В файл
app.jsпока добавьте только создание экземпляра приложения:// backend/src/app.js import express from 'express'; // Создание экземпляра приложения Express const app = express(); export default app; // Экспортируем app для использования в server.js - В файл
server.jsдобавьте код запуска сервера:// 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: Первый запуск сервера
Цель урока: Запустить сервер и убедиться, что он работает, возвращая ожидаемую ошибку.
Практические шаги:
- Из папки
backendвыполните команду:node src/server.js - В консоли должно появиться сообщение:
Сервер запущен и слушает порт 3000. - Откройте браузер и перейдите по адресу
http://localhost:3000. - Вы должны увидеть стандартную страницу ошибки
Cannot GET /. Это ожидаемо, так как мы не создали обработчик для корневого маршрута (/). - Остановите сервер, нажав
Ctrl + Cв терминале. - Выполните коммит:
git add . git commit -m "feat: создана базовая структура сервера, успешный запуск" git push origin study # Или study-фамилия
🖐️ Урок 7: Создание первого маршрута (Hello World)
Цель урока: Создать простейший обработчик GET-запроса и получить ответ от сервера.
Краткая теория:
- Маршрут (route) — это правило, которое определяет, как сервер будет реагировать на клиентский запрос к определенному URL (конечной точке) и HTTP-методу (GET, POST и т.д.).
- Обработчик (handler) — функция, которая принимает запрос (
req) и формирует ответ (res).
Практические шаги:
- Откройте файл
backend/src/app.js. - Добавьте обработчик для корневого маршрута (
/) перед строкойexport default app;:// Обработчик GET-запроса на корневой URL '/' app.get('/', (req, res) => { // Устанавливаем заголовок ответа res.setHeader('Content-Type', 'text/plain'); // Отправляем текстовый ответ res.send('Hello World from my API!'); }); - Запустите сервер снова (
node src/server.js) и обновите страницуhttp://localhost:3000в браузере. - Вы должны увидеть надпись
Hello World from my API!. - Выполните коммит.
🔢 Урок 8: Работа с параметрами пути (Path Parameters)
- Expressjs: Маршрутизация в Express
- Nodejsdev: Как построить REST API с помощью JS, Node.js и Express.js
Цель урока: Научиться создавать динамические маршруты и извлекать данные из URL.
Краткая теория:
- Статические маршруты — это фиксированные адреса, например,
/aboutили/contacts. Они всегда одинаковы. - Динамические маршруты — это адреса, которые могут меняться. Например, страница профиля с разными именами пользователей:
/user/anna,/user/max. - Параметры пути (Path Parameters) — специальные части URL, которые меняются. В Express они обозначаются двоеточием (
:), например:/user/:username. - Когда кто-то заходит на
/user/anna, Express видит:usernameв маршруте и понимает: "вместо:usernameподставленоanna". Это значение сохраняется вreq.params.
Примеры разных параметров:
- Один параметр:
// Когда заходят на /hello/Максим
app.get('/hello/:name', (req, res) => {
const name = req.params.name; // = "Максим"
res.send(`Привет, ${name}!`);
});
- Несколько параметров:
// Когда заходят на /book/сказки/5
app.get('/book/:genre/:page', (req, res) => {
const genre = req.params.genre; // = "сказки"
const page = req.params.page; // = "5"
res.send(`Жанр: ${genre}, Страница: ${page}`);
});
- Параметр в середине пути:
// Когда заходят на /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}`);
});
Практические шаги:
- В файле
app.jsдобавьте маршрут:
// Пример запроса: 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');
}
});
- Протестируйте:
http://localhost:3000/double/7→ работаетhttp://localhost:3000/double/abc→ ошибка
➕➖✖️➗ Урок 9: Практическое задание — Текстовый API-калькулятор
Цель задания: Создать простой калькулятор, который принимает числа в URL и возвращает текстовый результат.
Задание:
Создайте 4 маршрута для основных математических операций. Формат ответа — обычный текст.
Маршруты и примеры:
-
Сложение:
GET /api/calc/sum/:a/:b- Пример:
GET /api/calc/sum/5/3 - Ответ:
5 + 3 = 8
- Пример:
-
Вычитание:
GET /api/calc/subtract/:a/:b- Пример:
GET /api/calc/subtract/10/4 - Ответ:
10 - 4 = 6
- Пример:
-
Умножение:
GET /api/calc/multiply/:a/:b- Пример:
GET /api/calc/multiply/7/2 - Ответ:
7 * 2 = 14
- Пример:
-
Деление:
GET /api/calc/divide/:a/:b- Пример:
GET /api/calc/divide/15/3 - Ответ:
15 / 3 = 5
- Пример:
Как это сделать:
- Для каждого маршрута получите параметры из
req.params.aиreq.params.b - Преобразуйте их в числа:
const num1 = parseFloat(req.params.a) - Выполните операцию
- Верните ответ в формате:
res.send(`${num1} + ${num2} = ${result}`)
Важно:
- Пока не нужно проверять, корректные ли данные прислал пользователь
- Используйте шаблонные строки (обратные кавычки) для формирования ответа
⚠️ Урок 10: Базовые проверки в калькуляторе
Цель урока: Добавить минимальную проверку входных данных.
Задание:
Доработайте калькулятор из урока 9, добавив две простые проверки:
- Проверка, что оба параметра — числа
- Для деления — проверка, что второй параметр не ноль
Как это сделать:
Шаг 1: Проверка на число В начале каждого обработчика добавьте:
const a = parseFloat(req.params.a);
const b = parseFloat(req.params.b);
// Проверяем, являются ли оба параметра числами
if (isNaN(a) || isNaN(b)) {
return res.status(400).send('Ошибка: оба параметра должны быть числами');
}
Шаг 2: Проверка деления на ноль Только в обработчике деления добавьте после проверки на числа:
// Проверка деления на ноль
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
Цель урока: Понять, что такое JSON и научиться отправлять данные в этом формате.
Краткая теория:
- JSON (JavaScript Object Notation) — популярный формат для обмена данными;
- у JSON есть строгий набор правил:
- Все ключи в двойных кавычках
- Значения: строки (в кавычках), числа, true/false, null, массивы [], объекты {}
- Пример JSON:
{
"имя": "Иван",
"возраст": 16,
"хобби": ["программирование", "шахматы"],
"студент": true
}
Практическое задание:
- Создайте маршрут
GET /api/user/:id - Если
:id— число, верните JSON с информацией о пользователе:
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"
});
}
});
- Протестируйте в браузере:
http://localhost:3000/api/user/123→ увидите красиво оформленный JSONhttp://localhost:3000/api/user/abc→ JSON с ошибкой
Обратите внимание
В примере мы возвращаем фальшивые данные. С настоящими динамическими данными (списки/объекты БД) мы поработаем позже.
🔄 Урок 12: Калькулятор с JSON-ответами
Цель урока: Переделать текстовый калькулятор для возврата JSON.
Задание:
Измените все 4 операции калькулятора так, чтобы они возвращали данные в JSON-формате.
Формат успешного ответа:
{
"success": true,
"operation": "sum",
"arguments": {
"a": 5,
"b": 3
},
"result": 8
}
Формат ответа с ошибкой:
{
"success": false,
"error": {
"code": "DIVISION_BY_ZERO",
"message": "Делитель не может быть равен нулю"
}
}
Как изменить код:
- Заменяем
res.send()наres.json() - Для успешного ответа формируем объект:
const response = {
success: true,
operation: "sum", // или "subtract", "multiply", "divide"
arguments: {
a: a,
b: b
},
result: a + b // здесь ваша операция
};
res.json(response);
- Для ошибок:
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 для управления объявлением на сайте с тремя методами:
-
Просмотр объявления — GET
/api/adВозвращает текущее объявление или сообщение о его отсутствии. -
Установка объявления — POST
/api/adУстанавливает новое объявление, отправленное в теле запроса. -
Очистка объявления — 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 для управления заметками с четырьмя операциями:
- Получить все заметки — GET
/api/notes - Добавить заметку — POST
/api/notes - Получить одну заметку — GET
/api/notes/:id - Удалить заметку — DELETE
/api/notes/:id
Как реализовать
Создайте массив для хранения заметок и счетчик для ID:
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) и верните подтверждение удаления.
Важные моменты
Заметки будут храниться только в памяти сервера и исчезнут при его перезапуске. Для постоянного хранения данных потребуется база данных или файловая система, но это тема для следующих занятий.
Вопросы для самопроверки
- Чем отличается
notes[0]отnotes.find(n => n.id === 1)? - Почему лучше использовать отдельный счетчик
nextId, а не просто длину массива? - Какие HTTP-методы соответствуют операциям CRUD?

