Хранение и получение данных из MySQL в API-интерфейсах Go

Мы будем использовать реальную базу данных, например MySQL, для хранения и получения данных для своих таблиц.

Предварительные требования


Установка MySQL в системе и базовые знания о работе с ним.

КОД

Итак, давайте сначала настроим таблицы, которые мы будем использовать для хранения данных. Сначала создадим базу данных Practice, затем внутри этой базы создадим таблицу Todos.

CREATE DATABASE IF NOT EXISTS `Practice`;

CREATE TABLE IF NOT EXISTS `Practice`.`Todos` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `task` VARCHAR(255) NOT NULL,
    `completed` INT NOT NULL DEFAULT 0,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

Обратите внимание, что мы добавили в таблицу Todos поля: id, created_at, updated_at. Мы рассмотрим, как использовать их при выполнении операций над этой таблицей.

Теперь создадим файл окружения, добавим в него ваши MySQl , и .
.env

PORT=:5000
DATABASE_URL=<user>:<password>@tcp(localhost)/<database>?parseTime=true
DB_DRIVER=mysql

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

go get github.com/joho/godotenv
go get github.com/go-sql-driver/mysql

Теперь начнем писать код для подключения к базе данных

main.go

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/go-chi/chi/v5"
    _ "github.com/go-sql-driver/mysql"
    "github.com/joho/godotenv"
)

var (
    DATABASE_URL, DB_DRIVER, PORT string
)

func init() {
    err := godotenv.Load()
    if err != nil {
        log.Fatalln("Couldn't load env on startup!!")
    }
    DATABASE_URL = os.Getenv("DATABASE_URL")
    DB_DRIVER = os.Getenv("DB_DRIVER")
    PORT = os.Getenv("PORT")
}

func DBClient() (*sql.DB, error) {
    db, err := sql.Open(DB_DRIVER, DATABASE_URL)
    if err != nil {
        return nil, err
    }
    if err := db.Ping(); err != nil {
        return nil, err
    }
    fmt.Println("Connected to DB")
    return db, nil
}

Здесь мы сначала загрузили переменные окружения в функции init, затем создали функцию DBClient, в которой использовали встроенную в Go библиотеку database/sql для создания соединения с базой данных, эта функция возвращает клиента, с помощью которого мы взаимодействуем/выполняем операции над таблицами.

Теперь внесем изменения в нашу структуру Server, чтобы она содержала функцию D

type Server struct {
    Router *chi.Mux
    DB     *sql.DB
}

func CreateServer(db *sql.DB) *Server {
    server := &Server{
        Router: chi.NewRouter(),
        DB:     db,
    }
    return server
}

func main() {
    db, err := DBClient()
    if err != nil {
        log.Fatalln("Couldn't connect to DB")
    }
    server := CreateServer(db)
    server.MountHandlers()

    fmt.Println("server running on port:5000")
    http.ListenAndServe(PORT, server.Router)
}

Теперь с этими изменениями можно запустить и проверить, удалось ли подключиться к базе данных или нет.

➜  go-api git:(main) ✗ make run
Connected to DB
server running on port:5000

ПРИМЕЧАНИЕ: При возникновении ошибок проверьте, импортированы ли все необходимые библиотеки и присутствуют ли учетные данные для создания в файле .env.

Теперь давайте снова напишем функции GetTodos и AddTodo с использованием клиента DB.
Поскольку наш клиент БД находится в структуре Server, то для доступа к нему в функциях GetTodos и AddTodo необходимо сделать их методами структуры Server, а также внести изменения в функцию MountHandlers.

func (server *Server) MountHandlers() {
    server.Router.Get("/greet", Greet)

    todosRouter := chi.NewRouter()
    todosRouter.Group(func(r chi.Router) {
        // make these as a methods of Server struct as to access DB client
        r.Get("/", server.GetTodos)
        r.Post("/", server.AddTodo)
    })

    server.Router.Mount("/todos", todosRouter)
}

type Todo struct {
    Id        int       `json:"id"`
    Task      string    `json:"task"`
    Completed bool      `json:"completed"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

type TodoRequestBody struct {
    Task      string `json:"task"`
    Completed bool   `json:"completed"`
}

func scanRow(rows *sql.Rows) (*Todo, error) {
    todo := new(Todo)
    err := rows.Scan(&todo.Id,
        &todo.Task,
        &todo.Completed,
        &todo.CreatedAt,
        &todo.UpdatedAt,
    )
    if err != nil {
        return nil, err
    }
    return todo, nil
}

func (server *Server) AddTodo(w http.ResponseWriter, r *http.Request) {
    todo := new(TodoRequestBody)
    if err := json.NewDecoder(r.Body).Decode(todo); err != nil {
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte("Please enter a correct Todo!!"))
        return
    }

    query := `INSERT INTO Todos (task, completed) VALUES (?, ?)`
    _, err := server.DB.Exec(query, todo.Task, todo.Completed)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("Something bad happened on the server :("))
        return
    }

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Todo added!!"))
}

func (server *Server) GetTodos(w http.ResponseWriter, r *http.Request) {
    query := `SELECT * FROM Todos ORDER BY created_at DESC`

    rows, err := server.DB.Query(query)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("Something bad happened on the server :("))
        return
    }

    var todos []*Todo

    for rows.Next() {
        todo, err := scanRow(rows)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte("Something bad happened on the server :("))
            return
        }
        todos = append(todos, todo)
    }

    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(todos)
}

В функции GetTodos мы создали запрос для получения всех Todos из таблицы Todos, отсортированных в порядке убывания по времени создания. Клиент DB предоставляет нам метод Query, с помощью которого мы можем выполнить наш запрос, который возвращает строки, затем мы деструктурируем их и возвращаем массив todos в виде json.

В функции AddTodo мы сначала берем тело запроса и отображаем его в TodoRequestBody, затем создаем запрос на вставку, выполняем этот запрос с помощью метода Exec, предоставленного клиентом БД.

Обратимся к указанным ниже конечным точкам с помощью curl

➜  go-api git:(main) ✗ curl -X POST 'http://localhost:5000/todos' -d '{"task": "Learn Go", "completed": false}'
Todo added!!

➜  go-api git:(main) ✗ curl -X GET 'http://localhost:5000/todos'
[{"id":1,"task":"Learn Go","completed":false,"created_at":"2023-09-03T15:18:54Z","updated_at":"2023-09-03T15:18:54Z"}]

Вы также можете просматривать записи непосредственно в таблице MySQL

mysql> SELECT * FROM Todos ORDER BY created_at DESC;
+----+----------+-----------+---------------------+---------------------+
| id | task     | completed | created_at          | updated_at          |
+----+----------+-----------+---------------------+---------------------+
|  1 | Learn Go |         0 | 2023-09-03 15:18:54 | 2023-09-03 15:18:54 |
+----+----------+-----------+---------------------+---------------------+
1 row in set (0.01 sec)

Я рекомендую вам самостоятельно написать конечные точки PUT /todos, DELETE /todos.

Заключение

На этом все, в этой статье мы рассказали о том, как можно подключать и использовать базы данных MySQL, выполнять операции над таблицами и возвращать ответ с помощью наших API.

+1
0
+1
1
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *