Goで超簡単API

# 概要

Goを使用してCRUDを行える簡単なAPIを作成します。(DBは使用しません。)

# 前提条件・設定
1. Goの開発環境が整っている事を前提としています。
2. ルートを設定する為に[gorilla/mux](https://github.com/gorilla/mux)を使用するのでインストールを行います。

```
go get -u github.com/gorilla/mux
```

# 手順
1. 開発準備
2. ルート(エンドポイント)の設定
3. モデルの作成
4. モックデータの作成
5. CRUDの設定
6. postmanを使用して動作確認

# 開発準備

API用のディレクトリを作成し、`main.go`ファイルの作成を行います。

```
$ mkdir goApi
$ cd goApi
$ touch main.go
```


# ルート(エンドポイント)の設定

先ほどインストールした`gorilla/mux`を使用して以下のようにルート(エンドポイント)を設定します。

```go
package main

import (
	"github.com/gorilla/mux"
    "log"
)

func main() {
	// ルーターのイニシャライズ
	r := mux.NewRouter()

	// ルート(エンドポイント)
	r.HandleFunc("/api/books", getBooks).Methods("GET")
	r.HandleFunc("/api/books/{id}", getBook).Methods("GET")
	r.HandleFunc("/api/books", createBook).Methods("POST")
	r.HandleFunc("/api/books/{id}", updateBook).Methods("PUT")
	r.HandleFunc("/api/books/{id}", deleteBook).Methods("DELETE")

	log.Fatal(http.ListenAndServe(":8000", r))
}
```

# モデルの作成

データの型となるモデル(struct)を`func main()`の上に作成します。
`title`と`author`の情報を持つ`Book`と`firstname`と`lastname`を持つ`Author`を作成します。

```go
package main

import (
  /*省略*/
)

type Book struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Author *Author `json:"author"`
}

type Author struct {
	FirstName string `json:"firstname"`
	LastName  string `json:"lastname"`
}

func main() {
  /*省略*/
}
```

# モックデータの作成

データの型となるモデルをもとにAPIの動作確認用のモックデータを作成します。

```go
package main

import (
  /*省略*/
)
type Book struct {
 /*省略*/
}

type Author struct {
 /*省略*/
}

// Bookのデータを保持するスライスの作成
var books []Book

func main() {
	// ルーターのイニシャライズ
	 /*省略*/
   
	// モックデータの作成
	books = append(books, Book{ID: "1", Title: "Book one", Author: &Author{FirstName: "Philip", LastName: "Williams"}})
	books = append(books, Book{ID: "2", Title: "Book Two", Author: &Author{FirstName: "John", LastName: "Johnson"}})

	// ルート(エンドポイント)
	/*省略*/
}
```

# CRUDの設定
以下の関数を設定していきます。

- `getBooks()`: 全ての本を取得する。
- `getBook()`: 特定の本を取得する。
- `createBook()`: 新しい本を作成する。
- `updateBook()`: 特定の本の情報を更新する。
- `deleteBook()`: 特定の本を削除する。

```go
// Get All Books
func getBooks(w http.ResponseWriter, r *http.Request) {
}

// Get Single Book
func getBook(w http.ResponseWriter, r *http.Request) {
}

// Create a Book
func createBook(w http.ResponseWriter, r *http.Request) {
}

// Update a Book
func updateBook(w http.ResponseWriter, r *http.Request) {
}

// Delete a Book
func deleteBook(w http.ResponseWriter, r *http.Request) {\
}
```

## packageのimport

関数を完成させる為に必要ないくつかの`package`を`import`します。

- jsonデータを作成、取得する為に`encoding/json`
- ランダムなidを作成する為に`math/rand`
- 作成したidを`string`型に変更する為に`strconv`
- http通信を可能にする為に`net/http`

```go

package main

import (
	"encoding/json"
	"github.com/gorilla/mux"
	"log"
	"math/rand"
	"net/http"
	"strconv"
)

/*省略*/
```

### 関数の作成

ここでの詳細の説明は省略しますが、他言語でAPIを作成する際に用いるCRUDの関数の構造と特に大き違いはないかと思います。

```go
package main

import (
	"encoding/json"
	"github.com/gorilla/mux"
	"log"
	"math/rand"
	"net/http"
	"strconv"
)

// Book Struct (Model)

type Book struct {
 /*省略*/
}

type Author struct {
 /*省略*/
}

// Init Books var as a slice Book struct
var books []Book


// Get All Books
func getBooks(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(books)
}

// Get Single Book
func getBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)

	// Loop through books and find with id
	for _, item := range books {
		if item.ID == params["id"] {
			json.NewEncoder(w).Encode(item)
			return
		}
	}
	json.NewEncoder(w).Encode(&Book{})
}

// Create a Book
func createBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var book Book
	_ = json.NewDecoder(r.Body).Decode(&book)
	book.ID = strconv.Itoa(rand.Intn(10000)) // Mock ID - not safe in production
	books = append(books, book)
	json.NewEncoder(w).Encode(book)
}

// Update a Book
func updateBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	params := mux.Vars(r)

	for index, item := range books {
		if item.ID == params["id"] {
			books = append(books[:index], books[index+1:]...)
			var book Book
			_ = json.NewDecoder(r.Body).Decode(&book)
			book.ID = params["id"]
			books = append(books, book)
			json.NewEncoder(w).Encode(book)
			return
		}
	}
	json.NewEncoder(w).Encode(books)
}

// Delete a Book
func deleteBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	params := mux.Vars(r)

	for index, item := range books {
		if item.ID == params["id"] {
			books = append(books[:index], books[index+1:]...)
			break
		}
	}
	json.NewEncoder(w).Encode(books)
}

func main() {
 /*省略*/
}
```

最後の動作確認に移る前にここまでの`main.go` のコードは以下です。

```go
package main

import (
	"encoding/json"
	"github.com/gorilla/mux"
	"log"
	"math/rand"
	"net/http"
	"strconv"
)

// Book Struct (Model)

type Book struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Author *Author `json:"author"`
}

type Author struct {
	FirstName string `json:"firstname"`
	LastName  string `json:"lastname"`
}

// Init Books var as a slice Book struct
var books []Book

// Get All Books
func getBooks(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(books)
}

// Get Single Book
func getBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)

	// Loop through books and find with id
	for _, item := range books {
		if item.ID == params["id"] {
			json.NewEncoder(w).Encode(item)
			return
		}
	}
	json.NewEncoder(w).Encode(&Book{})
}

// Create a Book
func createBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var book Book
	_ = json.NewDecoder(r.Body).Decode(&book)
	book.ID = strconv.Itoa(rand.Intn(10000)) // Mock ID - not safe in production
	books = append(books, book)
	json.NewEncoder(w).Encode(book)
}

// Update a Book
func updateBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	params := mux.Vars(r)

	for index, item := range books {
		if item.ID == params["id"] {
			books = append(books[:index], books[index+1:]...)
			var book Book
			_ = json.NewDecoder(r.Body).Decode(&book)
			book.ID = params["id"]
			books = append(books, book)
			json.NewEncoder(w).Encode(book)
			return
		}
	}
	json.NewEncoder(w).Encode(books)
}

// Delete a Book
func deleteBook(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	params := mux.Vars(r)

	for index, item := range books {
		if item.ID == params["id"] {
			books = append(books[:index], books[index+1:]...)
			break
		}
	}
	json.NewEncoder(w).Encode(books)
}

func main() {
	// Initiate Router
	r := mux.NewRouter()

	// Mock Data
	books = append(books, Book{ID: "1", Title: "Book one", Author: &Author{FirstName: "Philip", LastName: "Williams"}})
	books = append(books, Book{ID: "2", Title: "Book Two", Author: &Author{FirstName: "John", LastName: "Johnson"}})

	// Route Hnadlers / Endpoints
	r.HandleFunc("/api/books", getBooks).Methods("GET")
	r.HandleFunc("/api/books/{id}", getBook).Methods("GET")
	r.HandleFunc("/api/books", createBook).Methods("POST")
	r.HandleFunc("/api/books/{id}", updateBook).Methods("PUT")
	r.HandleFunc("/api/books/{id}", deleteBook).Methods("DELETE")

	log.Fatal(http.ListenAndServe(":8000", r))
}
```

# postmanを使用して動作確認

動作を確認する為に`main.go`を実行し、[postman](https://www.getpostman.com/)を開きます。

```
$ go run main.go
```

### Get(http://localhost:8000/api/books)
モックデータとして作成した２つのデータを取得する事ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/1cce33c3-a835-88cd-f2cc-f110fea612dd.png)


### Get(http://localhost:8000/api/books/:id)

今度は`id`で指定して必要なデータのみを取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/ca789d1b-f3a1-4e9e-dd99-ace0282a1bb0.png)

### POST (http://localhost:8000/api/books)
次にデータを作成してみましょう。
`Post`リクエストなのでURLを入力する右側の選択肢を`Get`から`Post`に変更し、json形式のデータを渡します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/4fdd68eb-21e4-101b-5154-02bda68bdba3.png)

### PUT(http://localhost:8000/api/books/:id)
次は一度登録したデータを`Update(更新)`してみましょう。
URL右の選択肢を`PUT`に変更。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/f43eab19-919c-ee61-c288-0cece1275c3e.png)

### DELETE(http://localhost:8000/api/books/:id)
最後に一度登録したデータを`Destroy(削除)`してみましょう。
URL右の選択肢を`DELETE`に変更。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/704b3383-3b53-3939-91b2-ae37d42b5a6b.png)