Разработка API для бэкенда
Цель
Научиться использовать БД при разработке RESTful API с помощью фреймворка Gin и языка программирования Go.
Порядок выполнения
- Изучить полезную информацию для создания приложения.
- Реализовать API и протестировать его работу.
Полезная информация
Данный пример демонстрирует реализацию простейшего RESTful API, работающего с БД, на языке программирования Go (Golang) с использованием фреймворка Gin и стандартного пакета database/sql для работы с базами данных.
Подробнее вы можете посмотреть следующие ссылки:
- https://go.dev/doc/database/
- https://go.dev/doc/tutorial/database-access
- https://go.dev/doc/tutorial/web-service-gin
Пример включает в себя:
- Подготовка среды разработки.
- Проектирование API.
- Создание проекта.
- Инициализация БД.
- Подключение к БД.
- Получение всех альбомов
- Добавление альбома
Какие инструменты и технологии используются?
- Язык программирования: Go (Golang).
- Web фреймворк Gin.
- Пакет для работы с базой данных:
database/sql. - СУБД РЕД База Данных.
- Драйвер для РЕД Базы Данных.
- Операционная система РЕД ОС.
- Текстовый редактор для написания программы (Меню/Стандартные/Текстовый редактор)
Подготовка среды разработки
Установка GoLang
- Выполните в терминале команду для установки golang:
sudo dnf install golang
- Установите
golang tools:
sudo go install -v golang.org/x/tools/gopls@latest
- Установите отладчик для GoLang:
go install github.com/go-delve/delve/cmd/dlv@latest
Установка RedDatabase
Установка РЕД Базы Данных подробно рассмотрена в:
- Руководство администратора в документации
- Видео-курсе администрирования
Проектирование API
В примере мы разработаем RESTful API, которое предоставляет доступ к магазину по продаже винтажных записей на виниле. Так что нужно будет предоставить конечные точки, через которые клиент может получить и добавить альбомы для пользователей.
Разработка API обычно начинается с разработки конечных точек. Важно чтобы точки можно было легко понять.
В в этом уроке мы создадим следующие точки
/albums
GET– Получить список альбомов в виде JSON.POST– Добавить новый альбом из данных в виде JSON, полученных в запросе.
Создание проекта
- Для начала нужно создать папку нашего проекта (он будет находиться в домашней директории) и перейдем в нее для дальнейшей работы.
mkdir ~/rdb_go
cd ~/rdb_go
- Инициализируем модуль Go
go mod init rdb_go
- С помощью текстового редактора создадим файл
main.goс таким содержимым и сохраним его в папке проекта~/rdb_go:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
- Выполните программу с помощью команды
go run .. На экране выведетсяHello, Go!.
Инициализация БД
Теперь создадим папку, в которой будут храниться базы данных и выдадим ей права доступа, чтобы сервер СУБД имел доступ к файлам БД.
Для версии РЕД База Данных не выше 3 версии или Firebird:
mkdir /db
sudo chown firebird: /db
Для версии РЕД База Данных 5 и выше.
mkdir /db
sudo chown reddatabase: /db
Теперь, используя РЕД Эксперт, создайте в каталоге /db новую БД recordings.fdb. Полный путь к БД будет /db/recordings.fdb. В этой БД создайте таблицу ALBUM и добавьте в нее несколько записей, согласно скрипту.
RECREATE TABLE ALBUM (
ID INT GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY,
TITLE VARCHAR(128) NOT NULL,
ARTIST VARCHAR(255) NOT NULL,
PRICE DECIMAL(5,2) NOT NULL
);
INSERT INTO ALBUM (TITLE, ARTIST, PRICE) VALUES ('Лебединое озеро', 'Чайковский', 56.99);
INSERT INTO ALBUM (TITLE, ARTIST, PRICE) VALUES ('Чародейка', 'Чайковский', 63.99);
INSERT INTO ALBUM (TITLE, ARTIST, PRICE) VALUES ('Времена года', 'Вивальди', 17.99);
INSERT INTO ALBUM (TITLE, ARTIST, PRICE) VALUES ('Годы странствий', 'Ференц Лист', 34.98);
Подключение к БД
Добавьте импорт библиотеки для работы с РЕД База Данных в main.go. Теперь секция import должна выглядеть так.
import (
"database/sql"
"fmt"
"log"
_ "github.com/nakagami/firebirdsql"
)
Чтобы установить ее для сборки нашего проекта, в каталоге с нашей программой ~/rdb_go выполните
go get .
Эта команда автоматически загрузит все необходимые библиотеки и подключит все зависимости. Далее, при подключении новых зависимостей, эту команду нужно будет повторять. Сразу все зависимости прописать не получится, так как неиспользуемые библиотеки считаются ошибкой. Обратите внимание, что пакет github.com/nakagami/firebirdsql импортируется с подчеркиванием _, что заставляет компилятор вызвать инициализацию пакета, несмотря на то, что явно его содержимое нигде не используется. Оно будет использоваться объектами пакета database/sql.
Теперь напишем код для доступа к нашей БД. Для этого будем использовать глобальный указатель на структуру sql.DB.
В модуле main.go объявите глобальную переменную db и перепишите функцию main как указано ниже.
var db *sql.DB
func main() {
// Строка подключения
connStr := "sysdba:masterkey@localhost/db/recordings.fdb"
// Подключение к базе данных
var err error
db, err = sql.Open("firebirdsql", connStr)
if err != nil {
log.Fatalf("Ошибка подключения к базе данных: %v", err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatalf("Не удалось подключиться к базе данных: %v", err)
}
fmt.Print("Подключен")
}
db – это глобальная (для упрощения примера) переменная для работы с базой данных. В реальных проектах избегайте использование глобальных переменных, а передавайте необходимые значения как аргументы функции при вызовах.
connStr – строковая переменная для хранения строки подключения к БД. В строке подключения указан пользователь (sysdba), пароль (masterkey), адрес сервера (localhost) и путь к файлу БД на этом сервере (/db/recordings.fdb). Еще раз обратив внимание, что с файлом работает сервер СУБД и на том компьютере, где он запущен.
sql.Open – открывает БД и инициализирует переменную db. В случае возникновения ошибки, она будет записана в переменную err. Если ошибка возникла, для облегчения диагностики мы выводим ее с помощью log. При этом метод Fatalf выводит форматированное сообщение и немедленно завершает работу программы. В реальных проектах ошибку вероятнее всего необходимо будет обработать и продолжить работу программы.
sql.Ping – проверяет соединение. В зависимости от драйвера, sql.Open может не установить соединение с сервером сразу же, а лишь при первом обращении. sql.Ping используется чтобы выполнить простейший запрос и убедиться что соединение действительно установлено. Обработка ошибок осуществляется аналогичным образов.
В завершение функции, выводим сообщение об успешности подключения.
Получение всех альбомов
Теперь напишем функцию, которая будет запрашивать все записи таблицы ALBUM, записывать их в массив структур и возвращать в качестве результата.
В файле main.go опишем структуру для хранения записей об альбомах.
// Album представляет структуру записи в таблице ALBUM
type Album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
Тэги в этой структуре, такие как json:"artist" определяют имя полей, при сериализации или десериализации структуры JSON.
После функции main вставим описание новой функции.
func queryAlbums() ([]Album, error) {
var albums []Album
// SQL-запрос для поиска альбомов
query := "SELECT ID, TITLE, ARTIST, PRICE FROM ALBUM"
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("ошибка getAlbums: %v", err)
}
defer rows.Close()
// Обработка результата
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
return nil, fmt.Errorf("ошибка getAlbums: %v", err)
}
albums = append(albums, alb)
}
// Проверка ошибок при обработке строк
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("ошибка getAlbums: %v", err)
}
return albums, nil
}
Функция queryAlbums принимает параметр db *sql.DB – подключение к БД, которое используется для выполнения запроса.
Результатом функции является массив записей Album и код ошибки.
Для хранения результата функции объявляем переменную albums.
db.Query используется для выполнения запроса (первый параметр). Параметры запроса могут быть указаны через запятую как второй и последующие параметры.
defer rows.Close() гарантирует, что ресурсы, связанные с результатами запроса, будут освобождены после выхода из функции. Цикл для обработки строк rows.Next() перемещается по каждой строке результата. Для каждой строки создаётся новая переменная типа Album (alb), в которую записываются данные из столбцов.
rows.Scan считывает данные столбцов текущей строки и записывает их в поля структуры alb.
Проверить что код работает достаточно просто. Добавьте печать результата этой функции последней строкой функции main.
func main() {
...
fmt.Print("Подключен")
fmt.Print(queryAlbums())
}
И при выполнении будет выдан следующий результат
Подключен[{1 Лебединое озеро Чайковский 56.99} {2 Чародейка Чайковский 63.99} {3 Времена года Вивальди 17.99} {4 Годы странствий Ференц Лист 34.98}] <nil>
После объявления структуры Album добавьте функцию, которая будет записывать массив записей Album в JSON представление для ответа HTTP запроса GET.
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
albums, _ := queryAlbums()
c.IndentedJSON(http.StatusOK, albums)
}
Эта функция принимает в качестве параметра gin.Context. Это наиболее важная часть фреймворка Gin. Она хранит детали запроса, проверяет целостность и сериализует JSON и т.д.
IndentedJSON сериализует структуру в JSON и добавляет ее в ответ запроса. Первый параметр функции это код статуса HTTP, который будет отправлен клиенту. Здесь мы отправляем StatusOK, чтобы отправить статус 200 OK.
Теперь давайте изменим последний строки кода функции main, так, чтобы вместо печати результата вызова queryAlbums, запускать веб-сервер и регистрировать обработчик вызова GET /albums (getAlbums).
Теперь завершение функции main должно выглядеть следующий образом.
func main() {
...
fmt.Print("Подключен")
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
Здесь инициализируется маршрутизатор Gin и регистрируется обработчик HTTP GET для пути /albums с нашей функцией getAlbums.
В завершении запускается веб-сервер, слушающий сетевой порт 8080.
Не забудем импортировать вновь задействованные библиотеки.
import (
"database/sql"
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/nakagami/firebirdsql"
)
и установить их
go get .
Запустим наше приложение и обратимся к веб-серверу в командной строке.
curl http://localhost:8080/albums
Результатом работы команды будет:
[
{
"ID": 1,
"Title": "Лебединое озеро",
"Artist": "Чайковский",
"Price": 56.99
},
{
"ID": 2,
"Title": "Чародейка",
"Artist": "Чайковский",
"Price": 63.99
},
{
"ID": 3,
"Title": "Времена года",
"Artist": "Вивальди",
"Price": 17.99
},
{
"ID": 4,
"Title": "Годы странствий",
"Artist": "Ференц Лист",
"Price": 34.98
}
]
Для более красивого вывода можно использовать различные средства фронтенд разработки, но это выходит за рамки данной инструкции.
Добавление альбома
Теперь давайте добавим функцию для вставки записи в нашу БД.
Для выполнения запросов, не возвращающих данные, используется метод Exec. Сразу после функции queryAlbums добавим нашу новую функцию.
// addAlbum добавляет запись в БД
func addAlbum(alb Album) (error) {
_, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return fmt.Errorf("ошибка addAlbum: %v", err)
}
return nil
}
db.Exec – выполняет SQL-запрос на добавления записи в таблицу ALBUM. При этом ID мы не передаем, потому что это первичный ключ, и он сгенерируется автоматически.
Теперь давайте добавим вызов этой функции через метод POST /albums.
Для этого напишем обработчик HTTP-запроса.
// postAlbums добавляет в БД запись, на основе JSON запроса.
func postAlbums(c *gin.Context) {
var newAlbum Album
if err := c.BindJSON(&newAlbum); err != nil {
return
}
addAlbum(newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
BindJSON связывает запрос с полями переменной newAlbum. Далее вызывается наша функция addAlbum для сохранения полученных данных в БД и в HTTP-ответ добавляется статус 201 вместе с JSON только что вставленной записи.
Добавим регистрацию нового обработчика в функции main.
func main () {
...
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
Чтобы проверить, снова используем утилиту curl и выполним команду
curl http://localhost:8080/albums --include --header "Content-Type: application/json" --request "POST" --data '{"id": "0","title": "Кащей Бессмертный","artist": "Римский-Корсаков","price": 299.99}'
Ее вывод должен быть следующим.
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Sun, 27 Apr 2025 20:39:06 GMT
Content-Length: 116
{
"id": "0",
"title": "Кащей Бессмертный",
"artist": "Римский-Корсаков",
"price": 299.99
}
Последующий вызов
curl http://localhost:8080/albums
должен уже показать наличие только что вставленной записи
[
{
"id": "1",
"title": "Лебединое озеро",
"artist": "Чайковский",
"price": 56.99
},
{
"id": "2",
"title": "Чародейка",
"artist": "Чайковский",
"price": 63.99
},
{
"id": "3",
"title": "Времена года",
"artist": "Вивальди",
"price": 17.99
},
{
"id": "4",
"title": "Годы странствий",
"artist": "Ференц Лист",
"price": 34.98
},
{
"id": "5",
"title": "Кащей Бессмертный",
"artist": "Римский-Корсаков",
"price": 299.99
}
]
Заключение
Поздравляем! Вы завершили реализацию простого RESTful API на языке Go с использованием фреймворка Gin, работающего с базой данных РЕД База Данных.