Разработка Go(lang) API с echo и MySQL

Разработка /
Разработка: Разработка Go(lang) API с echo и MySQL
В этой статье мы рассмотрим как создать API на базе MySQL с Go и echo. Ей мы начинаем цикл статей по разработке сайта photographerexcuses.com (название сайта можно перевести как «оправдания фотографов»).

Сайт представляет собой одностраничное веб-приложение на базе Vue.js, получающее данные (оправдания) от API на Go. Мы храним эти данные в базе MySQL.

Выбор веб-фреймворка на Golang

Многие вам скажут, что фреймворк не нужен и будут абсолютно правы. Однако без фреймворка многие вещи делать сложнее. В общем, выбирайте что хотите, а я выбрал echo для этого проекта.

Начнём с создания файла main.go, настройки в нём echo и регистрации необходимых роутов. Обратите внимание, что мы импортируем database/sql с _ перед ним. Это сделано для того, чтобы компилятор пропустил этот неиспользуемый импорт сейчас.

package main

import (
  _ "database/sql"
  "fmt"
  "net/http"

  _ "github.com/go-sql-driver/mysql"
  "github.com/labstack/echo"
  "github.com/labstack/echo/middleware"
  "github.com/JonathanMH/goClacks/echo"
)

func main() {
  // Echo instance
  e := echo.New()
  e.Use(goClacks.Terrify) // optional ;)

  // Middleware
  e.Use(middleware.Logger())
  e.Use(middleware.Recover())

  e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"*"},
    AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
  }))

  // Route => handler
  e.GET("/", func(c echo.Context) error {

    return c.JSON(http.StatusOK, "Hi!")
  })

  e.GET("/id/:id", func(c echo.Context) error {
    requested_id := c.Param("id")
    fmt.Println(requested_id);
    return c.JSON(http.StatusOK, requested_id)
  })

  e.Logger.Fatal(e.Start(":4000"))
}

Также, возможно, вам не понадобится middleware.CORSWithConfig, тут всё зависит от того, как вы настроите фронтенд. Я рекомендую оставить это хотя бы на время разработки.

Открыв localhost:4000 вы должны увидеть строку “Hi!”, а открыв localhost:4000/id/42 вы должны увидеть “42”. Всё работает как надо, ведь мы хотели как получать случайный результат, так и определённое оправдание по его ID.

Ответ в формате JSON

Для того, чтобы наш API всегда отвечал определённым образом, создадим специальный тип, которым и будем передавать ответ:

type (
  Excuse struct {
    Error string `json:"error"`
    Id string `json:"id"`
    Quote string `json:"quote"`
  }
)

Обработка ошибок очень важна в любом проекте, поэтому мы задали специальное поле Error для этого. В коде фронтенда (это будет один из следующих постов) мы сделаем проверку этого поля, чтобы знать, что все SQL-запросы прошли успешно.

Выборка случайного поля в SQL на Go

Разработка: Разработка Go(lang) API с echo и MySQL

var quote string;
var id string;
err = db.QueryRow("SELECT id, quote FROM excuses ORDER BY RAND() LIMIT 1").Scan(&id, "e)

В этом кусочке кода показана выборка из базы данных из таблицы excuses, а методом .Scan() мы записываем её в инициированные выше переменные.

Затем мы воспользуемся структурой Excuse с передачей в неё значений наших переменных, вернём c как контекст echo, и JSON() со статусом ответа 200 и готовым ответом:

response := Excuse{Id: id, Error: "false", Quote: quote}
return c.JSON(http.StatusOK, response)

В результате мы получим следующий ответ в httpie:

(master)⚡ % http localhost:3131

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Length: 78
Content-Type: application/json; charset=utf-8
Date: Sat, 01 Apr 2017 12:51:01 GMT
Vary: Origin
X-Clacks-Overhead: GNU Terry Pratchett

{
    "error": "false",
    "id": "48",
    "quote": "This is just how the industry works now."
}

А X-Clacks-Overhead: GNU Terry Pratchett приходит из middleware goClacks, которое я написал в честь известного фэнтезийного писателя.

Собираем воедино

Нам понадобится SQL только для создания таблицы в базе данных:

CREATE TABLE `excuses` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `quote` text,
  `author` varchar(191) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

Немного доработанный, хоть и без обработки ошибок, наш код будет выглядеть так, как он описан в конце топика. И вы также можете повторно использовать MySQL-соединение вместо создания нового для каждого запроса.

Также можно будет сделать некоторые интересные вещи, вроде таких как

  • Кэширование последних запросов к базе данных и проверка ответа в памяти перед созданием очередного SQL запроса
  • Ограничения скорости по IP адресу

Ниже полная версия кода приложения:

package main

import (
  "database/sql"
  "fmt"
  "net/http"

  _ "github.com/go-sql-driver/mysql"
  "github.com/labstack/echo"
  "github.com/labstack/echo/middleware"
  "github.com/JonathanMH/goClacks/echo"
)

type (
  Excuse struct {
    Error string `json:"error"`
    Id string `json:"id"`
    Quote string `json:"quote"`
  }
)

func main() {
  // Echo instance
  e := echo.New()
  e.Use(goClacks.Terrify)

  // Middleware
  e.Use(middleware.Logger())
  e.Use(middleware.Recover())

  e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"*"},
    AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
  }))

  // Route => handler
  e.GET("/", func(c echo.Context) error {
    db, err := sql.Open("mysql", "db_user:db_password@tcp(SERVER_IP:PORT)/database_name")

    if err != nil {
      fmt.Println(err.Error())
      response := Excuse{Id: "", Error: "true", Quote: ""}
      return c.JSON(http.StatusInternalServerError, response)
    }
    defer db.Close()

    var quote string;
    var id string;
    err = db.QueryRow("SELECT id, quote FROM excuses ORDER BY RAND() LIMIT 1").Scan(&id, "e)

    if err != nil {
      fmt.Println(err)
    }

    fmt.Println(quote);
    response := Excuse{Id: id, Error: "false", Quote: quote}
    return c.JSON(http.StatusOK, response)
  })

  e.GET("/id/:id", func(c echo.Context) error {
    requested_id := c.Param("id")
    fmt.Println(requested_id);
      db, err := sql.Open("mysql", "db_user:db_password@tcp(SERVER_IP:PORT)/database_name")

    if err != nil {
      fmt.Println(err.Error())
      response := Excuse{Id: "", Error: "true", Quote: ""}
      return c.JSON(http.StatusInternalServerError, response)
    }
    defer db.Close()

    var quote string;
    var id string;
    err = db.QueryRow("SELECT id, quote FROM excuses WHERE id = ?", requested_id).Scan(&id, "e)

    if err != nil {
      fmt.Println(err)
    }

    response := Excuse{Id: id, Error: "false", Quote: quote}
    return c.JSON(http.StatusOK, response)
  })

  e.Logger.Fatal(e.Start(":4000"))
}


Источник: «Building a Go(lang) API with echo and MySQL»

Продолжение: tehnojam.pro/category/development/deploj-golang-prilozhenija-bez-docker.html
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.