⚡ JavaScript

Работа с сетью и HTTP API в JavaScript (Node.js): подробное руководство

Что это такое и зачем? (аналогия из реальной жизни)

Представь, что ты сидишь в кафе и хочешь заказать пиццу. Ты не идёшь на кухню и не готовишь её сам — ты просто зовёшь официанта, говоришь ему свой заказ, а через некоторое время он приносит тебе готовую пиццу. В этом простом действии скрыта целая философия взаимодействия между программами.

Работа с сетью и HTTP API — это ровно то же самое, только вместо официанта у нас HTTP-протокол, вместо кухни — удалённый сервер, а вместо пиццы — данные (например, список фильмов, прогноз погоды или информация о пользователе).

В реальной жизни мы постоянно пользуемся такими "официантами": когда открываешь приложение погоды, оно отправляет запрос на сервер и получает данные. Когда ищешь товар на маркетплейсе — браузер просит сервер показать результаты. Даже когда ты читаешь эту статью, твой браузер уже выполнил десятки HTTP-запросов, чтобы загрузить текст, картинки и шрифты.

Зачем это нужно программисту? Потому что современные приложения редко работают в одиночку. Они общаются с другими программами через интернет. Твой сайт может попросить сервер Google Maps показать карту, сервер погоды — дать температуру, а сервер банка — проверить баланс. Всё это происходит через HTTP API.

💡 Важное замечание: HTTP API — это не какой-то один язык или технология. Это просто набор правил (протокол), по которому программы общаются через интернет. JavaScript, Python, Java — все они умеют работать с HTTP API, просто синтаксис разный.

Теория: как это работает изнутри

Давай заглянем "под капот" и разберёмся, что на самом деле происходит, когда твоя программа делает HTTP-запрос.

Клиент-серверная архитектура

В любой сетевой коммуникации есть две стороны:

  • Клиент — тот, кто просит (твоя программа на Node.js)
  • Сервер — тот, кто отвечает (удалённый компьютер с данными)

HTTP-запрос (Request)

Когда клиент хочет что-то получить, он отправляет запрос, который состоит из:

  • Метода — что именно мы хотим сделать (GET — прочитать, POST — создать, PUT — обновить, DELETE — удалить)
  • URL — адрес, по которому находится ресурс (например, https://api.weather.com/v1/current)
  • Заголовков (Headers) — дополнительная информация (тип данных, авторизация, язык)
  • Тела (Body) — данные, которые мы отправляем (обычно в POST или PUT запросах)

HTTP-ответ (Response)

Сервер обрабатывает запрос и возвращает ответ, который содержит:

  • Статус-код — трёхзначное число, которое говорит, всё ли хорошо:
    • 200 — OK (успех)
    • 201 — Created (создано)
    • 400 — Bad Request (неправильный запрос)
    • 404 — Not Found (не найдено)
    • 500 — Internal Server Error (ошибка на сервере)
  • Заголовки — мета-информация об ответе
  • Тело — сами данные (чаще всего в формате JSON)

Формат данных: JSON

Большинство современных API используют JSON (JavaScript Object Notation). Выглядит он так:

{
    "name": "Иван",
    "age": 25,
    "hobbies": ["программирование", "шахматы"],
    "address": {
        "city": "Москва",
        "street": "Тверская"
    }
}

JSON очень похож на объекты JavaScript, поэтому в Node.js с ним работать особенно удобно — можно превратить JSON в объект одной командой.

Асинхронность

Самое важное, что нужно понять: сетевые запросы — асинхронные. Когда твоя программа отправляет запрос, она не "засыпает" в ожидании ответа. Вместо этого она продолжает работать дальше, а когда ответ приходит — специальная функция (колбэк) его обрабатывает. В современном JavaScript для этого используют async/await, что делает код похожим на обычный синхронный.

⚠️ Критически важно: Если ты попробуешь сделать HTTP-запрос без асинхронности, программа либо зависнет, либо вернёт undefined. Это одна из самых частых ошибок новичков.

Синтаксис в JavaScript (Node.js): все формы и варианты

В Node.js есть несколько способов делать HTTP-запросы. Давай рассмотрим все основные.

1. Встроенный модуль http/https (низкоуровневый)

Node.js имеет встроенные модули http и https. Они не требуют установки, но код получается громоздким. Используются редко, в основном для написания собственных библиотек.

2. Библиотека axios (самая популярная)

Это стандарт де-факто для HTTP-запросов в JavaScript. Удобная, простая, поддерживает промисы и async/await.

3. Библиотека node-fetch (современный стандарт)

Реализация браузерного fetch для Node.js. Легковесная, современная, но требует установки.

4. Библиотека got (мощная и быстрая)

Более новая библиотека с большим количеством возможностей "из коробки".

Сравнение популярных библиотек

Библиотека Установка Популярность Сложность Особенности
axios npm install axios ⭐⭐⭐⭐⭐ Низкая Автоматический JSON, перехватчики
node-fetch npm install node-fetch ⭐⭐⭐⭐ Низкая Как в браузере, легковесный
got npm install got ⭐⭐⭐ Средняя Мощный, много опций
http/https Встроенный ⭐⭐ Высокая Не требует установки

Основные методы HTTP-запросов

Метод Что делает Есть ли тело Пример использования
GET Получить данные Нет Получить список пользователей
POST Создать новый ресурс Да Зарегистрировать нового пользователя
PUT Полностью обновить ресурс Да Обновить профиль пользователя
PATCH Частично обновить ресурс Да Изменить только email пользователя
DELETE Удалить ресурс Нет Удалить пользователя

Примеры кода — от простого к сложному

Теперь перейдём к самому интересному — практике. Все примеры используют библиотеку axios, так как она самая популярная и простая. Установи её командой npm install axios.

Пример 1: Простой GET-запрос (получаем данные)

// Подключаем библиотеку axios
const axios = require('axios');

// Асинхронная функция для выполнения запроса
async function getUsers() {
    try {
        // Отправляем GET-запрос на тестовый API
        // JSONPlaceholder — это бесплатный сервис с тестовыми данными
        const response = await axios.get('https://jsonplaceholder.typicode.com/users');
        
        // response.data — это тело ответа, уже преобразованное в объект JavaScript
        console.log('Полученные данные:', response.data);
        
        // response.status — код ответа (200, 404 и т.д.)
        console.log('Статус ответа:', response.status);
        
        // response.headers — заголовки ответа
        console.log('Заголовки:', response.headers);
    } catch (error) {
        // Если произошла ошибка (нет интернета, сервер не отвечает)
        console.error('Ошибка при запросе:', error.message);
    }
}

// Вызываем функцию
getUsers();

Пример 2: POST-запрос (отправляем данные)

const axios = require('axios');

async function createUser() {
    try {
        // Создаём объект с данными нового пользователя
        const newUser = {
            name: 'Мария Петрова',
            username: 'maria_p',
            email: 'maria@example.com'
        };
        
        // Отправляем POST-запрос с телом запроса (данными)
        const response = await axios.post(
            'https://jsonplaceholder.typicode.com/users',
            newUser  // Второй аргумент — тело запроса
        );
        
        // Сервер вернёт созданный объект с присвоенным ID
        console.log('Созданный пользователь:', response.data);
        console.log('Статус:', response.status); // Должен быть 201 (Created)
    } catch (error) {
        console.error('Ошибка создания:', error.message);
    }
}

createUser();

Пример 3: Обработка ошибок и разных статус-кодов

const axios = require('axios');

async function fetchWithErrorHandling() {
    try {
        // Пробуем получить несуществующий ресурс (должен вернуть 404)
        const response = await axios.get('https://jsonplaceholder.typicode.com/nonexistent');
        console.log('Успех:', response.data);
    } catch (error) {
        // Проверяем, есть ли ответ от сервера (статус-код)
        if (error.response) {
            // Сервер ответил, но с ошибкой
            console.log('Статус ошибки:', error.response.status); // 404
            console.log('Сообщение сервера:', error.response.data);
        } else if (error.request) {
            // Запрос был отправлен, но ответа нет (нет интернета)
            console.log('Сервер не отвечает. Проверьте интернет.');
        } else {
            // Ошибка на этапе настройки запроса
            console.log('Ошибка в запросе:', error.message);
        }
    }
}

fetchWithErrorHandling();

Пример 4: Работа с заголовками (авторизация)

const axios = require('axios');

async function getWithAuth() {
    try {
        // Создаём конфигурацию запроса
        const config = {
            headers: {
                // Заголовок авторизации (обычно токен)
                'Authorization': 'Bearer my-secret-token-123',
                // Указываем, что ожидаем JSON
                'Accept': 'application/json',
                // Указываем, что отправляем JSON
                'Content-Type': 'application/json'
            },
            // Параметры запроса (добавляются к URL)
            params: {
                limit: 10,  // Получить только 10 записей
                page: 1     // Первая страница
            }
        };
        
        const response = await axios.get(
            'https://jsonplaceholder.typicode.com/posts',
            config  // Передаём конфигурацию третьим аргументом
        );
        
        console.log('Посты:', response.data);
        console.log('Количество:', response.data.length);
    } catch (error) {
        console.error('Ошибка авторизации:', error.message);
    }
}

getWithAuth();

Пример 5: Параллельные запросы (Promise.all)

const axios = require('axios');

async function getMultipleResources() {
    try {
        // Создаём массив промисов (запросов)
        const requests = [
            axios.get('https://jsonplaceholder.typicode.com/posts'),
            axios.get('https://jsonplaceholder.typicode.com/users'),
            axios.get('https://jsonplaceholder.typicode.com/comments')
        ];
        
        // Ждём выполнения ВСЕХ запросов одновременно
        // Это намного быстрее, чем делать их по очереди
        const [posts, users, comments] = await Promise.all(requests);
        
        console.log('Постов:', posts.data.length);    // 100
        console.log('Пользователей:', users.data.length); // 10
        console.log('Комментариев:', comments.data.length); // 500
        
        // Теперь можно объединить данные
        console.log('Первый пост написал:', users.data[0].name);
    } catch (error) {
        // Если хотя бы один запрос упадёт — упадут все
        console.error('Ошибка в одном из запросов:', error.message);
    }
}

getMultipleResources();

Пример 6: Создание своего экземпляра axios (instance)

const axios = require('axios');

// Создаём свой экземпляр с базовыми настройками
// Это удобно, когда работаешь с одним API много раз
const apiClient = axios.create({
    baseURL: 'https://jsonplaceholder.typicode.com',
    timeout: 5000,  // Таймаут 5 секунд
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
});

// Теперь можно использовать apiClient как обычный axios
// Но URL будут относительными
async function workWithApi() {
    try {
        // Этот запрос пойдёт на https://jsonplaceholder.typicode.com/users
        const users = await apiClient.get('/users');
        console.log('Пользователи:', users.data);
        
        // Этот запрос пойдёт на https://jsonplaceholder.typicode.com/posts
        const posts = await apiClient.get('/posts');
        console.log('Посты:', posts.data);
        
        // POST-запрос на https://jsonplaceholder.typicode.com/posts
        const newPost = await apiClient.post('/posts', {
            title: 'Мой новый пост',
            body: 'Текст поста...',
            userId: 1
        });
        console.log('Создан:', newPost.data);
    } catch (error) {
        console.error('Ошибка:', error.message);
    }
}

workWithApi();

Пример 7: Перехватчики (interceptors) — продвинутая техника

const axios = require('axios');

// Создаём экземпляр
const api = axios.create({
    baseURL: 'https://jsonplaceholder.typicode.com'
});

// Перехватчик запроса — выполняется перед каждым запросом
api.interceptors.request.use(
    (config) => {
        // Можно добавить логирование
        console.log(`[${new Date().toISOString()}] ${config.method.toUpperCase()} ${config.url}`);
        
        // Можно автоматически добавить токен авторизации
        config.headers.Authorization = 'Bearer ' + getTokenFromStorage();
        
        // Обязательно вернуть config, иначе запрос не уйдёт
        return config;
    },
    (error) => {
        // Ошибка при настройке запроса
        return Promise.reject(error);
    }
);

// Перехватчик ответа — выполняется после каждого ответа
api.interceptors.response.use(
    (response) => {
        // Можно обработать данные перед тем, как они попадут в код
        console.log(`Ответ получен: ${response.status}`);
        return response;
    },
    (error) => {
        // Можно обработать ошибки глобально
        if (error.response && error.response.status === 401) {
            // Если получили 401 (не авторизован) — перенаправить на страницу входа
            console.log('Требуется авторизация!');
            redirectToLogin();
        }
        return Promise.reject(error);
    }
);

// Вспомогательные функции (для примера)
function getTokenFromStorage() {
    return 'fake-token-123';
}

function redirectToLogin() {
    console.log('Перенаправление на страницу входа...');
}

// Теперь все запросы через api будут проходить через перехватчики
async function testInterceptors() {
    const response = await api.get('/users');
    console.log('Данные:', response.data);
}

testInterceptors();

Пример 8: Загрузка файла (stream)

const axios = require('axios');
const fs = require('fs');  // Модуль для работы с файлами

async function downloadFile() {
    try {
        // Создаём поток записи в файл
        const writer = fs.createWriteStream('image.jpg');
        
        // Скачиваем файл с сервера
        const response = await axios({
            method: 'GET',
            url: 'https://example.com/image.jpg',
            responseType: 'stream'  // Важно! Получаем поток, а не строку
        });
        
        // Направляем поток данных из ответа в файл
        response.data.pipe(writer);
        
        // Ждём, пока файл запишется полностью
        await new Promise((resolve, reject) => {
            writer.on('finish', resolve);
            writer.on('error', reject);
        });
        
        console.log('Файл успешно скачан!');
    } catch (error) {
        console.error('Ошибка скачивания:', error.message);
    }
}

downloadFile();

Сравнение с Python: чем похоже и чем отличается

Если ты уже знаком с Python или планируешь его изучать, полезно знать, как работа с HTTP API выглядит на обоих языках.

Сходства

  • Асинхронность: В обоих языках сетевые запросы асинхронные. В Python используется asyncio и aiohttp, в JavaScript — async/await.
  • Библиотеки: В Python самая популярная библиотека — requests (синхронная) и aiohttp (асинхронная). В JavaScript — axios и node-fetch.
  • JSON: Оба языка отлично работают с JSON. В Python есть json.loads() и json.dumps(), в JavaScript — JSON.parse() и JSON.stringify().
  • Статус-коды: Обработка статус-кодов (200, 404, 500) идентична.

Различия

Аспект JavaScript (Node.js) Python
Основная библиотека axios (асинхронная) requests (синхронная)
Асинхронность По умолчанию асинхронный По умолчанию синхронный (нужен aiohttp для асинхронности)
JSON автоматически axios сам парсит JSON requests сам парсит JSON
Синтаксис await axios.get(url) requests.get(url)
Обработка ошибок try/catch с axios try/catch с requests
Типизация Динамическая, нет типов Динамическая, но можно добавить типы

Пример на Python для сравнения

// JavaScript (Node.js)
const axios = require('axios');

async function getData() {
    const response = await axios.get('https://api.example.com/data');
    console.log(response.data);
}
# Python (с библиотекой requests)
import requests

def get_data():
    response = requests.get('https://api.example.com/data')
    print(response.json())
💡 Совет: Если ты знаешь Python, переход на JavaScript будет лёгким. Главное отличие — не забывай про await и async в JavaScript. В Python ты можешь писать синхронный код, а в Node.js — нет.

Типичные ошибки и подводные камни

Даже опытные разработчики иногда допускают эти ошибки. Вот список самых частых проблем и как их избежать.

1. Забыл про асинхронность

// ❌ НЕПРАВИЛЬНО: функция не async, нет await
function getUsers() {
    const response = axios.get('https://api.example.com/users');
    console.log(response.data); // undefined — запрос ещё не выполнен!
}

// ✅ ПРАВИЛЬНО: используем async/await
async function getUsers() {
    const response = await axios.get('https://api.example.com/users');
    console.log(response.data); // Теперь данные есть
}

2. Не обрабатываешь ошибки

// ❌ НЕПРАВИЛЬНО: нет try/catch
async function getData() {
    const response = await axios.get('https://nonexistent-url.com');
    // Если сервер не отвечает — программа упадёт с ошибкой
}

// ✅ ПРАВИЛЬНО: всегда оборачивай в try/catch
async function getData() {
    try {
        const response = await axios.get('https://nonexistent-url.com');
        console.log(response.data);
    } catch (error) {
        console.error('Ошибка:', error.message);
    }
}

3. Путаешь методы GET и POST

// ❌ НЕПРАВИЛЬНО: пытаешься отправить данные через GET
await axios.get('/users', { name: 'Иван' }); // Данные не отправятся!

// ✅ ПРАВИЛЬНО: для отправки данных используй POST
await axios.post('/users', { name: 'Иван' });

4. Забываешь про заголовки

// ❌ НЕПРАВИЛЬНО: не указал Content-Type
await axios.post('/api/data', jsonData);
// Сервер может не понять, что ты отправляешь JSON

// ✅ ПРАВИЛЬНО: всегда указывай заголовки
await axios.post('/api/data', jsonData, {
    headers: {
        'Content-Type': 'application/json'
    }
});

5. Игнорируешь таймауты

// ❌ НЕПРАВИЛЬНО: без таймаута программа может зависнуть навсегда
const response = await axios.get('https://slow-server.com');

// ✅ ПРАВИЛЬНО: устанавливай разумный таймаут
const response = await axios.get('https://slow-server.com', {
    timeout: 5000 // 5 секунд — и ошибка
});

6. Не проверяешь статус-код

// ❌ НЕПРАВИЛЬНО: доверяешь, что всё всегда 200
const response = await axios.get('/api/data');
// Если сервер вернул 404 или 500 — данные могут быть пустыми

// ✅ ПРАВИЛЬНО: проверяй статус
const response = await axios.get('/api/data');
if (response.status === 200) {
    console.log('Данные:', response.data);
} else {
    console.log('Неожиданный статус:', response.status);
}

7. Делаешь запросы в цикле последовательно

// ❌ НЕПРАВИЛЬНО: медленно — каждый запрос ждёт предыдущий
for (const id of [1, 2, 3, 4, 5]) {
    const response = await axios.get(`/items/${id}`);
    console.log(response.data);
}

// ✅ ПРАВИЛЬНО: используй Promise.all для параллельных запросов
const requests = [1, 2, 3, 4, 5].map(id => axios.get(`/items/${id}`));
const responses = await Promise.all(requests);
responses.forEach(r => console.log(r.data));

Лучшие практики и советы профессионалов

Эти советы помогут тебе писать не просто работающий, а качественный и надёжный код.

1. Всегда используй try/catch

Сетевые запросы — это самое ненадёжное место в программе. Интернет может пропасть, сервер может упасть, API может измениться. Оборачивай каждый запрос в try/catch.

2. Создавай отдельный модуль для API

Не разбрасывай запросы по всему коду. Создай один файл (например, api.js), где будут все функции для работы с внешними сервисами.

// api.js — отдельный модуль для работы с API
const axios = require('axios');

const client = axios.create({
    baseURL: 'https://api.example.com/v1',
    timeout: 10000,
    headers: { 'Content-Type': 'application/json' }
});

module.exports = {
    getUsers: () => client.get('/users'),
    getUserById: (id) => client.get(`/users/${id}`),
    createUser: (data) => client.post('/users', data),
    updateUser: (id, data) => client.put(`/users/${id}`, data),
    deleteUser: (id) => client.delete(`/users/${id}`)
};

3. Используй переменные окружения для секретов

Никогда не храни API-ключи и токены в коде. Используй файл .env и библиотеку dotenv.

// .env файл
API_KEY=my-secret-key-123
API_URL=https://api.example.com

// В коде
require('dotenv').config();
const apiKey = process.env.API_KEY;
const apiUrl = process.env.API_URL;

4. Логируй запросы и ответы

Когда что-то идёт не так, логи помогут быстро найти проблему. Используй перехватчики (interceptors) для автоматического логирования.

5. Ограничивай количество запросов (rate limiting)

Многие API ограничивают количество запросов в минуту. Уважай эти ограничения — используй задержки между запросами.

// Функция для задержки
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Делаем запросы с задержкой в 1 секунду
for (let i = 0; i < 10; i++) {
    await axios.get('/api/data');
    await delay(1000); // Ждём 1 секунду
}

6. Используй кэширование

Если данные меняются редко, не запрашивай их каждый раз. Сохраняй результат в переменную или файл.

let cachedUsers = null;
let cacheTime = null;

async function getUsers() {
    // Если данные были получены меньше минуты назад — возвращаем кэш
    if (cachedUsers && Date.now() - cacheTime < 60000) {
        return cachedUsers;
    }
    
    const response = await axios.get('/users');
    cachedUsers = response.data;
    cacheTime = Date.now();
    return cachedUsers;
}

7. Обрабатывай частичные ошибки

Если ты делаешь 10 запросов и один упал — не обязательно ронять всю программу. Используй Promise.allSettled вместо Promise.all.

const results = await Promise.allSettled(requests);
results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
        console.log(`Запрос ${index} успешен:`, result.value.data);
    } else {
        console.log(`Запрос ${index} упал:`, result.reason.message);
    }
});

Вопросы для самопроверки

Проверь, насколько хорошо ты усвоил материал. Попробуй ответить на вопросы, не подглядывая в ответы.

  1. Вопрос: Что такое HTTP API простыми словами?

    Ответ

    Это набор правил, по которым одна программа может общаться с другой через интернет. Как официант в