В этой статье я расскажу, как писал самый простой сервер для общения со старой базой данных Firebird.

Суть следующая: имеется старая база данных Firebird 3.0, которая крутится на сервере. Нужно написать backend, который будет общаться с данной базой.

Не судите строго, так как это мой первый опыт написания backend в принципе.

Итак, всё что изначально имелось у меня, это база данных. Для простоты я буду обозначать ее DB. Пропустим шаги установки NodeJS. Создаем папку проекта, инициализируем проект npm init. Данные указываем любые. После инициализации нужно добавить строку "type": "module" в файле package.json.

{
  "type": "module",
  "name": "nodejs-server-firebird",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Устанавливаем в эту же папку node-firebird командой:

npm install node-firebird 

С помощью данного пакета мы сможем подключиться к базе.

В корне папки проекта создаем папку src. Должно получиться что-то похожее:

Структура проекта

Далее в папке src создаем две папки: config и db. А в папке src создаем файл index.js

Далее устанавливаем пакеты: express и cors

npm install express 
npm install cors

Должна появиться папка node_modules и файл package-lock.json

Заодно, чтобы постоянно не перезапускать сервер вручную, а при любых изменениях он смог сам перезапускаться, поставим пакет nodemon

npm install nodemon

Создаю новый файл database.js в папке config. Он будет отвечать за соединение с базой. Здесь указываю все данные базы. А также пишу функцию, которая соединяется с базой данных.

import firebird from "node-firebird";

const dbOptions = {
    host: 'localhost',
    port: 3050,
    database: 'C:\\Users\\user\\Documents\\nodejs-server-firebird\\DB.DB',    
    user: 'SYSDBA',
    password: 'masterkey',
    lowercase_keys: true, 
    role: null,    
    pageSize: 4096    
};

function executeQuery(ssql, params, callback){

    firebird.attach(dbOptions, function(err, db) {
            
        if (err) {
            return callback(err, []); 
        } 

        db.query(ssql, params, function(err, result) {
            
            db.detach();

            if (err) {
                return callback(err, []);
            } else {
                return callback(undefined, result);
            }
        });

    });
}

export {executeQuery};

Все запросы буду писать в ранее созданном файле index.js. Импортирую необходимые модули.

import express from "express";
import cors from "cors";
import { executeQuery } from "./config/database.js";

const app = express();

app.use(express.json());

app.use(cors());

Далее пишу первый метод GET, который будет получать все данные из таблицы. Заодно добавлю условие на фильтр определенных id.

app.get("/products", (req, res) => {
  try {
    const { id_prod } = req.query; // Получаем параметр id_prod из запроса

    let ssql = "SELECT * FROM PRODUCTS WHERE ID_PROD > 0"; // Формируем SQL-запрос
    const filter = [];

    if (id_prod) {
      ssql += " AND ID_PROD LIKE ?"; // Добавляем условие поиска по id_prod
      filter.push(`%${id_prod}%`);
    }

    // Выполняем SQL-запрос с использованием функции executeQuery
    executeQuery(ssql, filter, (err, result) => {
      if (err) {
        res.status(500).json({ error: err.message }); // Обрабатываем ошибку и отправляем клиенту сообщение об ошибке
      } else if (result.length === 0) {
        res.status(404).json({ error: "Ничего не найдено" }); // Добавляем статус 404, если результат пустой
      } else {
        res.status(200).json(result); // Отправляем клиенту результат запроса
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message }); // Обрабатываем исключение и отправляем клиенту сообщение об ошибке
  }
});

Получилось вот так:

Методом GET получаю все данные

При использовании фильтра, получаем определенный объект:

Использую фильтр в методе GET

Следующий метод будет тоже GET, но им буду получать определенный "продукт". Так называемую определенную карточку.

Объявляю id и получаем его из параметров запроса. А потом выбираю продукт с заданным id.

// Получение одного продукта по id
app.get("/products/:id", (req, res) => {
  const id = req.params.id; // Получаем id из параметров запроса
  const ssql = "SELECT * FROM PRODUCTS WHERE ID_PROD = ?"; // Используем подготовленный запрос

  // Выполнение SQL-запроса с использованием функции executeQuery
  executeQuery(ssql, [id], (err, result) => {
    if (err) {
      console.error("Ошибка при выполнении запроса:", err);
      res.status(500).send("Internal Server Error"); // Отправляем ошибку сервера в случае ошибки SQL-запроса
    } else if (result.length === 0) {
      res.status(404).send("Product not found"); // Отправляем сообщение о том, что продукт не найден, если результат пустой
    } else {
      res.send(result[0]); // Отправляем первый найденный продукт в ответ на успешный запрос
    }
  });
});

Теперь нужно явно указать в ссылке id объекта, например /products/9. Отправляю запрос в Postman, и получаю вот так. Отлично.


Следующий метод POST, которым будет создавать новые объекты в базе.

// Добавление нового продукта
app.post("/products/new", (req, res) => {
  const ssql = "INSERT INTO PRODUCTS(NAME, ABOUT) VALUES (?, ?) RETURNING ID_PROD"; // SQL-запрос для вставки нового продукта и получения его ID

  // Выполнение SQL-запроса с использованием функции executeQuery и данными из тела запроса (req.body)
  executeQuery(ssql, [req.body.NAME, req.body.ABOUT], (err, result) => {
    if (err) {
      if (err.code === "ER_DUP_ENTRY") {
        res.status(409).json({ error: "Запись уже существует" }); // Обработка ошибки дублирования записи
      } else if (err.code === "ECONNREFUSED") {
        res.status(502).json({ error: "Сервер не может установить соединение с базой данных" }); // Обработка ошибки соединения с базой данных
      } else {
        console.error("Ошибка при выполнении запроса:", err);
        res.status(500).json({ error: "Произошла ошибка при добавлении продукта" }); // Отправка общей ошибки сервера
      }
    } else {
      res.status(201).json("Новый продукт добавлен, его id " + result.id_prod); // Отправка успешного ответа с ID нового продукта
    }
  });
});
Отправляю запрос в POSTMAN
Смотрю, появилась строка в базе

Далее метод PUT, которым буду обновлять определенную запись.

// Обновление информации о продукте по его ID
app.put("/products/:id_prod/update", function (req, res) {
  const id_prod = req.params.id_prod; // Получаем ID продукта из параметров запроса
  const { name, about } = req.body; // Получаем данные для обновления из тела запроса

  let ssql = 'UPDATE PRODUCTS SET name = ?, about = ? WHERE ID_PROD = ?'; // SQL-запрос для обновления информации о продукте

  // Выполняем SQL-запрос с использованием функции executeQuery
  executeQuery(ssql, [name, about, id_prod], function (err, result) {
    if (err) {
      console.error(err);
      return res.status(500).json({ error: 'Произошла ошибка при обновлении записи.' }); // Отправляем ошибку сервера при возникновении ошибки SQL-запроса
    } else {
      return res.status(200).json({ message: `Запись с id_prod: ${id_prod} успешно обновлена.` }); // Отправляем успешный ответ после успешного обновления
    }
  });
});
Отправляю запрос на обновление 21 строки

Последний метод, который я написал, это DELETE. Изначально отправляется запрос по id_prod, который проверяет, есть ли строка в базе, если ее нет, тогда падает ошибка что ее нет. Если строка все таки есть, тогда она удаляется.

// Обработчик HTTP DELETE-запроса для удаления записи по ID_PROD
app.delete("/products/:id_prod", function (req, res) {
  const id_prod = req.params.id_prod;
  const selectQuery = "SELECT ID_PROD FROM PRODUCTS WHERE ID_PROD = ?";

  // Выполняем запрос на выборку записи перед удалением
  executeQuery(selectQuery, [id_prod], function (err, result) {
    if (err) {
      return res.status(500).send('Ошибка выполнения запроса на выборку: ' + err.message);
    }

    // Если запись с указанным ID_PROD найдена
    if (result.length > 0) {
      const deleteQuery = "DELETE FROM PRODUCTS WHERE ID_PROD = ?";

      // Выполняем запрос на удаление записи
      executeQuery(deleteQuery, [id_prod], function (err, result) {
        if (err) {
          return res.status(500).send('Ошибка удаления: ' + err.message);
        }

        // Возвращаем успешный статус и сообщение об успешном удалении
        return res.send(`Удаление записи с ID_PROD=${id_prod} выполнено успешно`);
      });
    } else {
      // Если запись не найдена, возвращаем статус 404 и сообщение об отсутствии записи
      return res.status(404).send(`Запись с указанным ID_PROD ${id_prod} не существует. Возможно, она уже была удалена`);
    }
  });
});

Выглядит это вот так:

Первый раз отправляем запрос, строка существует, значит удаление выполнено успешно.
Повторно отправляем этот же запрос. Приходит 404 статус.

В самом конце файла index.js нужно добавить прослушивание порта 3000 для сервера.

app.listen(3000, function () {
  console.log("Server is running on port 3000");
});

Теперь остается запустить сервер в консоли командой:

nodemon src/index.js

Видим лог что сервер запущен:

$ nodemon src/index.js
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node src/index.js`
Server is running on port 3000

БОНУС: В папку db еще можно положить скрипт для БД Firebird, который создает тестовую базу данных, которая используется в данном примере. Просто для себя.

CREATE TABLE PRODUCTS (
  ID_PROD INTEGER GENERATED BY DEFAULT AS IDENTITY,
  NAME VARCHAR(100),
  ABOUT VARCHAR(100),
  CONSTRAINT PK_PRODUCTS PRIMARY KEY (ID_PROD)
);

Чтобы создать таблицу, нужно просто выполнить данный скрипт в БД.