Команда Go for Devs подготовила перевод статьи по созданию RESTful API на Go с использованием фреймворка Gin. Всего за несколько шагов вы напишете простой веб-сервис, который умеет возвращать список джазовых альбомов, добавлять новые и находить альбом по ID. Отличный старт для знакомства с Gin.
В этой статье рассматриваются основы создания RESTful веб-сервисов на Go с использованием веб-фреймворка Gin.
Gin упрощает многие задачи при создании веб-приложений, включая веб-сервисы. В этом учебнике вы будете использовать Gin для маршрутизации запросов, извлечения их параметров и формирования JSON-ответов.
В ходе работы вы создадите RESTful API-сервер с двумя эндпоинтами. Примером послужит проект — хранилище данных о винтажных джазовых пластинках.
Предварительные требования
Установленный Go версии 1.16 или новее. Инструкции по установке см. в разделе Installing Go.
Инструмент для редактирования кода. Подойдёт любой текстовый редактор.
CLI. Go отлично работает в любом терминале Linux и Mac, а также в PowerShell или cmd на Windows.
Утилита curl. На Linux и Mac она обычно уже установлена. В Windows она включена начиная с Windows 10 Insider build 17063. Для более ранних версий Windows её может потребоваться установить вручную.
Проектирование API-эндпоинтов
Вы будете создавать API, который предоставляет доступ к магазину, торгующему винтажными виниловыми пластинками. Поэтому необходимо предусмотреть эндпоинты, через которые клиент сможет получать список альбомов и добавлять новые.
Разработка API обычно начинается с проектирования эндпоинтов. Пользователям вашего API будет гораздо проще работать с ним, если эндпоинты будут интуитивно понятными.
Вот эндпоинты, которые мы сегодня создадим:
/albums
GET
— получить список всех альбомов в формате JSON.POST
— добавить новый альбом, переданный в формате JSON.
/albums/:id
GET
— получить данные об альбоме по его ID в формате JSON.
Далее вы создадите папку для кода.
Создание директории для кода
Для начала создайте проект, в котором будет располагаться ваш код.
Откройте командную строку и перейдите в домашний каталог.
На Linux или Mac:
$ cd
В Windows:
C:\> cd %HOMEPATH%
Через командную строку создайте директорию для кода с именем web-service-gin:
$ mkdir web-service-gin
$ cd web-service-gin
Создайте модуль, в котором вы сможете управлять зависимостями.
Выполните команду go mod init
, указав путь для модуля, в котором будет находиться ваш код:
$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin
Эта команда создаст файл go.mod
, в котором будут перечисляться все добавленные зависимости для их отслеживания. Подробнее о выборе имени модуля и путях см. в разделе Managing dependencies.
Далее вы спроектируете структуры данных для работы с информацией.
Подготовка данных
Чтобы упростить учебный пример, данные будут храниться в памяти. В реальном API обычно используется база данных.
Имейте в виду: при хранении в памяти набор альбомов будет теряться каждый раз при остановке сервера и создаваться заново при запуске.
Напишем код
В текстовом редакторе создайте файл main.go
в каталоге web-service. В этот файл вы будете писать код на Go.
В начало main.go
вставьте объявление пакета:
package main
Самостоятельная программа (в отличие от библиотеки) всегда находится в пакете main
.
Ниже объявления пакета вставьте определение структуры album
. Она понадобится для хранения данных об альбомах в памяти.
Теги структуры, например json:"artist"
, задают имена полей при сериализации структуры в JSON. Без тегов в JSON попали бы имена с заглавной буквы — стиль, менее привычный для JSON.
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
Под определением структуры вставьте срез album
с начальными данными:
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
Далее вы напишете код для первого эндпоинта.
Написание обработчика для возврата всех элементов
Когда клиент делает запрос GET /albums
, нужно вернуть все альбомы в формате JSON.
Для этого необходимо:
реализовать логику формирования ответа;
сопоставить путь запроса с этой логикой.
Обратите внимание: порядок добавления кода обратный тому, как он выполняется во время работы программы — сначала вы добавляете зависимости, затем код, который их использует.
Напишем код
Под определением структуры из предыдущего шага вставьте следующий код, который будет возвращать список альбомов:
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
В этом коде:
Создаётся функция
getAlbums
, принимающая параметрgin.Context
. Имя функции можно выбрать любое — Gin и Go не требуют определённого формата.gin.Context — ключевая часть фреймворка Gin. Она содержит данные запроса, умеет валидировать и сериализовать JSON и многое другое. (Несмотря на схожее название, это другой пакет, не связанный со встроенным в Go
context
.)Вызов Context.IndentedJSON сериализует структуру в JSON и добавляет её в ответ.
Первый аргумент функции — HTTP-код состояния, который вы хотите вернуть клиенту. Здесь используется константа StatusOK из пакета
net/http
, означающая 200 OK.
Обратите внимание: вместо Context.IndentedJSON
можно вызвать Context.JSON
, чтобы отправить более компактный JSON. Но в отладке форматированный вариант удобнее, а разница в размере обычно несущественна.
В начале файла main.go
, сразу под объявлением среза albums
, вставьте следующий код, чтобы привязать обработчик к пути эндпоинта:
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
Этот код устанавливает связь между функцией getAlbums
и запросами к эндпоинту /albums
.
В нём выполняются такие действия:
Инициализируется роутер Gin с помощью
Default()
.С помощью метода GET связываются HTTP-метод GET и путь
/albums
с функцией-обработчиком.
Обратите внимание: вы передаёте имя функцииgetAlbums
, а не результат её вызова. Если бы вы написалиgetAlbums()
, функция выполнилась бы сразу.Вызов Run привязывает роутер к
http.Server
и запускает сервер.
Вверху файла main.go
, сразу под объявлением пакета, импортируйте необходимые пакеты для поддержки этого кода:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
Сохраните файл main.go
.
Запуск кода
Теперь нужно подключить модуль Gin как зависимость.
В командной строке выполните команду go get
, чтобы добавить модуль github.com/gin-gonic/gin в зависимости вашего проекта. Аргумент .
означает «получить зависимости для кода в текущем каталоге».
$ go get .
go get: added github.com/gin-gonic/gin v1.7.2
Go найдёт и загрузит зависимость в соответствии с импортом, который вы добавили.
Теперь в каталоге с файлом main.go
выполните команду для запуска кода. Аргумент .
снова означает «запустить код из текущего каталога»:
$ go run .
После этого у вас будет работающий HTTP-сервер, готовый принимать запросы.
В новом окне командной строки выполните запрос к сервису с помощью curl:
$ curl http://localhost:8080/albums
Команда должна вывести данные, которые вы заранее добавили в сервис:
[
{
"id": "1",
"title": "Blue Train",
"artist": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artist": "Sarah Vaughan",
"price": 39.99
}
]
Поздравляем, вы запустили API! В следующем разделе вы создадите ещё один эндпоинт для обработки POST-запроса и добавления нового элемента.
Написание обработчика для добавления нового элемента
Когда клиент отправляет POST
-запрос на /albums
, нужно добавить альбом, переданный в теле запроса, к существующим данным.
Для этого потребуется:
реализовать логику добавления нового альбома в список;
добавить код для маршрутизации POST-запроса к этой логике.
Напишем код
Сразу после блока import
вставьте следующий код (чаще всего такие функции размещают в конце файла, но Go не требует определённого порядка объявления):
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
В этом коде:
используется Context.BindJSON, чтобы преобразовать тело запроса в структуру
newAlbum
;новый альбом добавляется в срез
albums
;в ответ возвращается статус
201 Created
и JSON с данными добавленного альбома.
Теперь измените функцию main
, чтобы добавить обработку POST
-запросов:
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
В этом коде:
метод
POST
по пути/albums
связывается с функциейpostAlbums
.
В Gin можно привязывать обработчики к комбинациям «HTTP-метод + путь». Таким образом, один и тот же путь может иметь разные обработчики для GET
, POST
и других методов.
Запуск кода
Если сервер из предыдущего шага всё ещё запущен, остановите его.
В командной строке в каталоге, где находится main.go
, снова выполните запуск:
$ go run .
В другом окне командной строки отправьте запрос к работающему веб-сервису с помощью curl:
$ curl http://localhost:8080/albums \
--include \
--header "Content-Type: application/json" \
--request "POST" \
--data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'
Команда должна вывести заголовки ответа и JSON с добавленным альбомом:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Wed, 02 Jun 2021 00:34:12 GMT
Content-Length: 116
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artist": "Betty Carter",
"price": 49.99
}
Как и в предыдущем разделе, используйте curl, чтобы получить полный список альбомов и убедиться, что новый альбом был добавлен:
$ curl http://localhost:8080/albums \
--header "Content-Type: application/json" \
--request "GET"
Команда должна вывести список альбомов:
[
{
"id": "1",
"title": "Blue Train",
"artist": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artist": "Sarah Vaughan",
"price": 39.99
},
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artist": "Betty Carter",
"price": 49.99
}
]
В следующем разделе вы добавите код для обработки GET-запроса к конкретному элементу.
Написание обработчика для возврата конкретного элемента
Когда клиент делает запрос GET /albums/[id]
, нужно вернуть альбом, чей идентификатор совпадает с параметром id
в пути.
Для этого потребуется:
реализовать логику поиска нужного альбома;
сопоставить путь запроса с этой логикой.
Напишем код
Под функцией postAlbums
, добавленной ранее, вставьте следующий код для поиска конкретного альбома:
// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Loop over the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
В этом коде:
используется
Context.Param
, чтобы получить параметрid
из URL. Когда вы будете привязывать обработчик к пути, в маршруте появится плейсхолдер для параметра;выполняется перебор среза
albums
в поисках альбома, у которого полеID
совпадает с переданным параметром. Если альбом найден, он сериализуется в JSON и возвращается клиенту со статусом200 OK
;если альбом не найден, возвращается ошибка со статусом
404 Not Found
и сообщением"album not found"
.
В реальном сервисе поиск, скорее всего, выполнялся бы запросом к базе данных.
Теперь измените функцию main
, чтобы добавить новый маршрут:
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
Здесь путь /albums/:id
сопоставляется с функцией getAlbumByID
. В Gin двоеточие перед элементом пути означает, что это параметр пути.
Запуск кода
Если сервер ещё работает, остановите его. Затем в каталоге с файлом main.go
снова выполните:
$ go run .
В новом окне командной строки отправьте запрос:
$ curl http://localhost:8080/albums/2
Команда должна вывести JSON с альбомом по указанному ID:
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
}
Если альбом не найден, вернётся JSON с сообщением об ошибке.
Русскоязычное Go сообщество

Друзья! Эту статью перевела команда «Go for Devs» — сообщества, где мы делимся практическими кейсами, инструментами для разработчиков и свежими новостями из мира Go. Подписывайтесь, чтобы быть в курсе и ничего не упустить!
Заключение
Поздравляем! Вы только что создали простой RESTful веб-сервис на Go и Gin.
Ниже приведён полный код приложения, который мы написали:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Loop through the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}