Node.js + MySQL: REST API для первых данных

Что мы соберем

В этом уроке сделаем маленький REST API на Node.js, Express и MySQL. Сценарий приземленный: у нас есть форма заявки на сайте, и мы хотим сохранять имя, email и статус в таблицу

Это хороший первый проект, потому что в нем сразу видны важные backend-кусочки:

  • HTTP-сервер;
  • JSON-запросы;
  • подключение к базе;
  • SQL-таблица;
  • параметризованные запросы;
  • простая проверка входных данных.

Что нужно заранее

Нужны:

  • установленный Node.js LTS;
  • npm;
  • MySQL-сервер локально или в Docker;
  • базовое понимание Express;
  • любой клиент для проверки запросов: curl, Postman, Insomnia или REST Client в VS Code.

Если MySQL еще не стоит, можно сначала прочитать SQL-блок и вернуться сюда позже. Смысл урока не в установке MySQL, а в связке Node.js + база

Схема проекта

node-mysql-api/
  package.json
  server.js
  db.js
  .env

В server.js будет Express. В db.js — подключение к MySQL. В .env — настройки подключения

Создаем проект

mkdir node-mysql-api
cd node-mysql-api
npm init -y
npm install express mysql2 dotenv

Пакеты:

  • express — сервер и маршруты;
  • mysql2 — драйвер для MySQL;
  • dotenv — чтение переменных окружения из .env.

Готовим таблицу

Создадим базу и таблицу:

CREATE DATABASE solo_lessons;

USE solo_lessons;

CREATE TABLE leads (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(120) NOT NULL,
  email VARCHAR(180) NOT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Для первого урока этого достаточно. В реальном проекте можно добавить индексы, уникальность email, источник заявки, телефон, комментарий, UTM-метки

Настройки подключения

Создайте .env:

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=solo_lessons
DB_PORT=3306
PORT=3000

Пароль не пишите прямо в server.js. Сегодня это учебный проект, завтра код случайно окажется в репозитории

Подключение к MySQL

Создайте db.js:

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  port: Number(process.env.DB_PORT || 3306),
  waitForConnections: true,
  connectionLimit: 10
});

module.exports = pool;

Мы используем pool, а не одно вечное соединение. Для API это нормальнее: сервер может обрабатывать несколько запросов, а pool управляет подключениями

Первый server.js

require('dotenv').config();

const express = require('express');
const pool = require('./db');

const app = express();
const port = Number(process.env.PORT || 3000);

app.use(express.json());

app.get('/api/status', async (req, res) => {
  const [rows] = await pool.query('SELECT 1 AS ok');

  res.json({
    ok: true,
    database: rows[0].ok === 1
  });
});

app.listen(port, () => {
  console.log(`API запущен: http://localhost:${port}`);
});

Запуск:

node server.js

Проверка:

curl http://localhost:3000/api/status

Если база подключена, получите JSON со статусом

Получаем список заявок

Добавим маршрут:

app.get('/api/leads', async (req, res, next) => {
  try {
    const [rows] = await pool.query(
      'SELECT id, name, email, status, created_at FROM leads ORDER BY id DESC'
    );

    res.json({
      ok: true,
      leads: rows
    });
  } catch (error) {
    next(error);
  }
});

Пока таблица пустая, это нормально. API должен вернуть пустой массив, а не падать

Добавляем заявку

Теперь POST /api/leads:

app.post('/api/leads', async (req, res, next) => {
  try {
    const { name, email } = req.body;

    if (!name || !email) {
      return res.status(400).json({
        ok: false,
        message: 'Нужны name и email'
      });
    }

    const [result] = await pool.execute(
      'INSERT INTO leads (name, email, status) VALUES (?, ?, ?)',
      [name, email, 'new']
    );

    res.status(201).json({
      ok: true,
      lead: {
        id: result.insertId,
        name,
        email,
        status: 'new'
      }
    });
  } catch (error) {
    next(error);
  }
});

Обратите внимание на ? в SQL-запросе. Это параметризованный запрос. Мы не склеиваем SQL строкой из пользовательского ввода, потому что это прямой путь к SQL-инъекциям

Общая обработка ошибок

В конце server.js, перед app.listen, добавьте:

app.use((error, req, res, next) => {
  console.error(error);

  res.status(500).json({
    ok: false,
    message: 'Внутренняя ошибка сервера'
  });
});

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

Полный server.js

require('dotenv').config();

const express = require('express');
const pool = require('./db');

const app = express();
const port = Number(process.env.PORT || 3000);

app.use(express.json());

app.get('/api/status', async (req, res, next) => {
  try {
    const [rows] = await pool.query('SELECT 1 AS ok');

    res.json({
      ok: true,
      database: rows[0].ok === 1
    });
  } catch (error) {
    next(error);
  }
});

app.get('/api/leads', async (req, res, next) => {
  try {
    const [rows] = await pool.query(
      'SELECT id, name, email, status, created_at FROM leads ORDER BY id DESC'
    );

    res.json({
      ok: true,
      leads: rows
    });
  } catch (error) {
    next(error);
  }
});

app.post('/api/leads', async (req, res, next) => {
  try {
    const { name, email } = req.body;

    if (!name || !email) {
      return res.status(400).json({
        ok: false,
        message: 'Нужны name и email'
      });
    }

    const [result] = await pool.execute(
      'INSERT INTO leads (name, email, status) VALUES (?, ?, ?)',
      [name, email, 'new']
    );

    res.status(201).json({
      ok: true,
      lead: {
        id: result.insertId,
        name,
        email,
        status: 'new'
      }
    });
  } catch (error) {
    next(error);
  }
});

app.use((error, req, res, next) => {
  console.error(error);

  res.status(500).json({
    ok: false,
    message: 'Внутренняя ошибка сервера'
  });
});

app.listen(port, () => {
  console.log(`API запущен: http://localhost:${port}`);
});

Проверка через curl

Добавить заявку:

curl -X POST http://localhost:3000/api/leads \
  -H "Content-Type: application/json" \
  -d '{"name":"Анна","email":"anna@example.com"}'

Получить список:

curl http://localhost:3000/api/leads

Если заявка появилась в списке, связка Node.js + MySQL работает

Частые ошибки

Access denied for user

Неверный пользователь или пароль в .env. Проверьте DB_USER и DB_PASSWORD

Unknown database

База не создана или указано другое имя. Выполните CREATE DATABASE или поправьте DB_NAME

ECONNREFUSED

MySQL-сервер не запущен или порт другой. Проверьте службу MySQL и DB_PORT

Данные вставляются, но русский текст ломается

Проверьте кодировку базы и таблицы. Обычно для новых проектов стоит использовать utf8mb4

SQL собирается через шаблонную строку

Не делайте так:

`SELECT * FROM leads WHERE email = '${email}'`

Используйте параметры:

pool.execute('SELECT * FROM leads WHERE email = ?', [email]);

Что улучшить дальше

После первого результата можно добавить:

  • валидацию email;
  • PUT /api/leads/:id для изменения статуса;
  • DELETE /api/leads/:id;
  • пагинацию;
  • логирование запросов;
  • Docker Compose для MySQL;
  • миграции вместо ручного SQL.

Ответы на эти вопросы могут быть для вас полезными

Можно ли использовать MySQL с Node.js?

Да. Для этого нужен драйвер, например mysql2, и обычные SQL-запросы из Node.js-кода

Что лучше для первого проекта: MySQL или MongoDB?

Если данные табличные и важны связи, MySQL понятен. Если данные похожи на гибкие документы, можно взять MongoDB. Для обучения полезны оба подхода

Почему нельзя хранить пароль базы в коде?

Код легко попадает в Git, архив, переписку или скриншот. Настройки подключения лучше держать в переменных окружения

Зачем нужен pool подключений?

API обрабатывает много запросов. Pool помогает переиспользовать подключения к базе и не открывать новое соединение на каждый запрос вручную

Это уже полноценный backend?

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

Что почитать дальше по Node.js

Если вы собираете тему по шагам, рядом лучше открыть:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x