В этой статье я расскажу, как писал самый простой сервер для общения со старой базой данных 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
Использую фильтр в методе 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
Отправляю запрос в 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 строки
Отправляю запрос на обновление 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 статус.
Повторно отправляем этот же запрос. Приходит 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)
);

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

Комментарии (0)