В этой статье мы рассмотрим как создать 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, тут всё зависит от того, как вы настроите фронтенд. Я рекомендую оставить это хотя бы на время разработки.
Открыв http://localhost:4000 вы должны увидеть строку “Hi!”, а открыв http://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
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)⚡ {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} 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»
Продолжение: http://tehnojam.ru/category/development/deploj-golang-prilozhenija-bez-docker.html
Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.
Пишите: @ighar. Buy me a coffee, please :).