こんにちはMinatoです。
今回は人気のWebフレームワークGinを利用して、RESTfulAPIを開発する方法を解説します。
Ginとは
ginはGo(Golang)で書かれたWebフレームワークです。 httprouterのおかげで、最大40倍高速なパフォーマンスを備えたmartiniのようなAPIを備えています。パフォーマンスと優れた生産性が必要な場合は、Ginを好きになるでしょう。
https://gin-gonic.com/ja/docs/introduction/
Ginの人気について
GinはGoのWebフレームワークの中でも、特に人気があります。以下はGithubのStarの数を示しています。
Project Name | Stars | Forks | Open Issues | Description | Last Commit |
---|---|---|---|---|---|
gin | 51894 | 5889 | 443 | Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance — up to 40 times faster. If you need smashing performance, get yourself some Gin. | 2021-09-30 02:04:28 |
beego | 27032 | 5316 | 27 | beego is an open-source, high-performance web framework for the Go programming language. | 2021-09-18 15:08:26 |
kit | 21360 | 2192 | 47 | A standard library for microservices. | 2021-09-28 15:01:29 |
echo | 20797 | 1841 | 65 | High performance, minimalist Go web framework | 2021-09-26 15:56:43 |
概要
今回は簡単なTODOアプリを想定して、TODOを操作するAPIの開発を行います。
話を簡略化するために今回はデータをメモリ上に保存します。通常のアプリケーション開発ではデータはデータベースなどで管理することになります。
エンドポイント
メソッド | エンドポイント | 説明 |
---|---|---|
GET | /todos | TODOの一覧を取得します |
POST | /todos | 新規にTODOを作成します |
GET | /todos/:id | 指定されたTODO1件を取得します |
PATCH | /todos/:id | 指定されたTODOを更新します |
DELETE | /todos/:id | 指定されたTODOを削除します |
実装
コード用のディレクトリの作成
まずはコードを格納するディレクトリを作成します。
mkdir todo-api
cd todo-api
依存関係を管理できるモジュールを作成
go mod init example/todo-api
ライブラリのインストール
go get github.com/gin-gonic/gin
Structとデータの作成
まずは、現在のディレクトリにmain.goを作成し、以下の内容を記述します。
まずStruct todoを作成します。todoはIDとTitle、Authorのフィールドを持ちます。
json:"id"
の箇所ではStructをJsonに変換した際のフィールド名を明示的に指定しています。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
type todo struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var todos = []todo{
{ID: 1, Title: "タイトルA", Author: "Taro"},
{ID: 2, Title: "タイトルB", Author: "Jiro"},
{ID: 3, Title: "タイトルC", Author: "Takuya"},
}
func main() {
}
全てのtodo返すハンドラの作成
main()の上に以下の関数を定義します。
以下のコードでは、todo StructのスライスからJSONを作成し、JSONをレスポンスに書き込んでいます。
func getTodos(c *gin.Context) {
c.IndentedJSON(http.StatusOK, todos)
}
次にmain()に以下のコードを追加します。
gin.Default()
で デフォルトルーターを初期化します。
router.GET("/todos", getTodos)
では、GETメソッドでの/todosへのリクエストとgetTodos()ハンドラを紐づけています。
router.Run("localhost:8080")
でrouterをhttp.Serverに紐付け、サーバーを起動しています。
func main() {
router := gin.Default()
router.GET("/todos", getTodos)
router.Run("localhost:8080")
}
実装が完了したので、一度プログラムを実行して、動作の確認を行います。
http://localhost:8080/todos へGETでアクセスすると返却されます。
# 実行
go run .
curl http://localhost:8080/todos
[
{
"id": 1,
"title": "タイトルA",
"author": "Taro"
},
{
"id": 2,
"title": "タイトルB",
"author": "Jiro"
},
{
"id": 3,
"title": "タイトルC",
"author": "Takuya"
}
]
TODOを新規作成するハンドラの作成
main()の上に以下の関数を定義します。
c.BindJSON(&newTodo)
でリクエストボディをnewTodoにバインドしています。
append(todos, newTodo)
でtodosスライスに作成したnewTodoを追加しています。
最後に、newTodoをJSONに変換し、レスポンスに書き込んでいます。
func postTodo(c *gin.Context) {
var newTodo todo
if err := c.BindJSON(&newTodo); err != nil {
return
}
todos = append(todos, newTodo)
c.IndentedJSON(http.StatusCreated, newTodo)
}
main()を以下のように変更します。
router.POST("/todos", postTodo)
では、POSTメソッドでの/todosへのリクエストとpostTodoハンドラを紐づけています。
func main() {
router := gin.Default()
router.GET("/todos", getTodos)
router.POST("/todos", postTodo)
router.Run("localhost:8080")
}
サーバーを再起動して、動作を確認します。
POSTメソッドでhttp://localhost:8080/todosへアクセスしてください。
curl http://localhost:8080/todos \
--include \
--header "Content-Type: application/json" \
--request "POST" \
--data '{"id": 4,"title": "タイトルD","author": "Goro"}'
以下のレスポンスが返却されます。
{
"id": 4,
"title": "タイトルD",
"author": "Goro"
}
もう一度todoの一覧を取得すると先ほどPOSTしたデータが登録されています。
curl http://localhost:8080/todos
[
{
"id": 1,
"title": "タイトルA",
"author": "Taro"
},
{
"id": 2,
"title": "タイトルB",
"author": "Jiro"
},
{
"id": 3,
"title": "タイトルC",
"author": "Takuya"
},
{
"id": 4,
"title": "タイトルD",
"author": "Goro"
}
]
指定したTODO1件を取得するハンドラ
main()の上に以下の関数を定義します。
まずはc.Param("id")
を利用して、URLからidを取得します。また、strconv.Atoi()を利用して、idを文字列から数値に変換しています。
for文でtodosから指定したIDのデータを検索し、存在すれば該当のtodoをJSON形式で返却します。
func getTodoById(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return
}
for _, t := range todos {
if t.ID == id {
c.IndentedJSON(http.StatusOK, t)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
main()を以下のように変更します。
router.GET("/todos/:id", getTodoById)
では、GETメソッドでの/todos/:idへのリクエストとgetTodoByIdハンドラを紐づけています。
サーバーを再起動して、動作を確認します。
GETメソッドでhttp://localhost:8080/todos/:idへアクセスしてください。指定したIDのtodo1件が表示されます。
func main() {
router := gin.Default()
router.GET("/todos", getTodos)
router.GET("/todos/:id", getTodoById)
router.POST("/todos", postTodo)
router.Run("localhost:8080")
}
TODOを更新するハンドラ
main()の上に以下の関数を定義します。
まずはc.Param("id")
を利用して、URLからidを取得します。また、strconv.Atoi()を利用して、idを文字列から数値に変換しています。
次にc.BindJSON(&todo)
でtodoにJSONのリクエストボディをバインドしています。
そして、todosスライスの中の指定されたIDのデータをtodoで置き換えた後にtodoをJSONに変換して返しています。
func patchTodo(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return
}
var todo todo
todo.ID = id
if err = c.BindJSON(&todo); err != nil {
return
}
for i, t := range todos {
if t.ID == id {
todos[i] = todo
c.IndentedJSON(http.StatusOK, todo)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "todo not found"})
}
main()を以下のように変更します。
router.PATCH("/todos/:id", patchTodo)
では、PATCHメソッドでの/todos/:idへのリクエストとpatchTodoハンドラを紐づけています。
func main() {
router := gin.Default()
router.GET("/todos", getTodos)
router.GET("/todos/:id", getTodoById)
router.POST("/todos", postTodo)
router.PATCH("/todos/:id", patchTodo)
router.Run("localhost:8080")
}
サーバーを再起動して、動作を確認します。
PATCHメソッドでhttp://localhost:8080/todos/:idへアクセスしてください。
curl http://localhost:8080/todos/2 \
--include \
--header "Content-Type: application/json" \
--request "PATCH" \
--data '{"title": "XXX","author": "YYY"}'
以下のレスポンスが返却されます。
{
"id": 2,
"title": "XXX",
"author": "YYY"
}
もう一度todoの一覧を取得するとデータが更新されていることを確認できます。
TODOを削除するハンドラ
main()の上に以下の関数を定義します。
まずはc.Param("id")
を利用して、URLからidを取得します。また、strconv.Atoi()を利用して、idを文字列から数値に変換しています。
todos = append(todos[:i], todos[i+1:]...)
で指定されたIDのtodoをtodosから削除してます。
func deleteTodo(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return
}
for i, t := range todos {
if t.ID == id {
todos = append(todos[:i], todos[i+1:]...)
c.IndentedJSON(http.StatusOK, gin.H{"message": "todo(" + strconv.Itoa(id) + ") is deleted"})
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "todo not found"})
}
main()を以下のように変更します。
router.DELETE("/todos
では、DELETEメソッドでの/todos/:idへのリクエストとdeleteTodoハンドラを紐づけています。/:id
", deleteTodo)
func main() {
router := gin.Default()
router.GET("/todos", getTodos)
router.GET("/todos/:id", getTodoById)
router.POST("/todos", postTodo)
router.PATCH("/todos/:id", patchTodo)
router.DELETE("/todos/:id", deleteTodo)
router.Run("localhost:8080")
}
サーバーを再起動して、動作を確認します。
DELETEメソッドでhttp://localhost:8080/todos/:idへアクセスしてください。
指定したIDのtodoがtodosから削除されます。
curl http://localhost:8080/todos/2 \
--request "DELETE"
まとめ
以上でRESTfulAPIの実装は完了です。
Ginを利用することで簡単にRESTfulAPIを開発することができます。
本記事を読んでGinに興味を持たれた方は公式のドキュメントを確認してください。
最後まで読んでいただき、ありがとうございます。
この記事が、「面白いな」、「勉強になったな」という方は、SNSでシェアしていただけると嬉しいです。