【React×Go】フロント（React）とバック（Go）で連携する


## はじめに
よく「フロントエンドは○○で、バックエンドは××で」みたいな記述を見かけますが、フロントエンドとバックエンドの連携というのが調べてもパッと出てこなく、学習初期によく疑問に思っていたのでまとめました。

## 前提知識

ReactとGoの基本知識があることが前提。

#### Go
- APIサーバーを立ててjsonデータを返すことができる

#### React
- useState, useEffect
- axios

## 構成

フロントエンド：React
バックエンド：Go

フロントとバックの連携という1点に集中したいので、DBは使わず、テストデータを直書きしています。

## 1. Goでjsonデータを返す

#### 1-1. 適当な作業ディレクトリを作成します。

```
~/Desktop$ mkdir dev
~/Desktop$ cd dev
~/Desktop/dev$ mkdir go-app // goアプリ用
~/Desktop/dev$ cd go-app
~/Desktop/dev/go-app$ code . // vs codeで開く場合
```

以下の作業はgo-app内で実施します。

#### 1-2. main.goファイルの作成

go-app内にmain.goファイルを作成し、パッケージ名を宣言します。

```
~/Desktop/dev/go-app$ touch main.go
```

```main.go
package main
```

#### 1-3. テストデータの作成

jsonで返すテストデータを作成します。
（本来はここはDBから取得するものとなります。）

```main.go
package main

type fruit struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Icon string `json:"icon"`
}

var fruits = []fruit{
	{ID: 1, Name: "apple", Icon: "🍎"},
	{ID: 2, Name: "banana", Icon: "🍌"},
	{ID: 3, Name: "grape", Icon: "🍇"},
}
```

#### 1-4. jsonデータを返す関数の作成

作成したテストデータを返す関数（ここではgetFruitsとします）を作成します。
あとでCORSエラーに対応するため、コードを１行追加します。


```main.go
package main

import (
	"encoding/json"
)

type fruit struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Icon string `json:"icon"`
}

var fruits = []fruit{
	{ID: 1, Name: "apple", Icon: "🍎"},
	{ID: 2, Name: "banana", Icon: "🍌"},
	{ID: 3, Name: "grape", Icon: "🍇"},
}

func getFruits(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
    // ここにCORS対応コードを追加します。
	json.NewEncoder(w).Encode(fruits)
}
```

#### 1-5. 作成した関数の呼び出し

作成したgetFruits関数をmain関数内で呼び出します。

```main.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type fruit struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Icon string `json:"icon"`
}

var fruits = []fruit{
	{ID: 1, Name: "apple", Icon: "🍎"},
	{ID: 2, Name: "banana", Icon: "🍌"},
	{ID: 3, Name: "grape", Icon: "🍇"},
}

// ※Goではコードの記述順序は関係ないので、上に書いても下に書いても構いません。
func main() {
	http.HandleFunc("/", getFruits)
	fmt.Println("Starting server at port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func getFruits(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
    // ここにCORS対応コードを追加します。
	json.NewEncoder(w).Encode(fruits)
}
```

#### 1-6. サーバー起動＆jsonデータ取得

サーバーを起動します。

```
~/Desktop/dev/go-app$ go run main.go
Starting server at port 8080
```

この状態で`http://localhost:8080`にリクエストを送ると、jsonデータが返ってきます。

・curlの場合
```
~/Desktop& curl http://localhost:8080
[{"id":1,"name":"apple","icon":"🍎"},{"id":2,"name":"banana","icon":"🍌"},{"id":3,"name":"grape","icon":"🍇"}]
```

・Webブラウザの場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2907902/4c4d1419-63c8-c082-aa64-c712fe32d223.png)

以上で一旦完了です。
一旦Ctrl + Cでサーバーを止めておきます。

## 2.Reactでリクエストを送信する

#### 2-1. Reactプロジェクトの作成&起動

作業ディレクトリでReactアプリを作成します。

```
~/Desktop/dev$ npx create-react-app react-app --template typescript
```

作成したReactアプリのディレクトリに移動してアプリを起動します。

```
~/Desktop/dev$ cd react-app
~/Desktop/dev/react-app$ npm start
```

`http://localhost:3000`にアクセスし、Reactアプリが起動していることを確認します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2907902/2f1da9f0-6747-8207-78c8-76460248630e.png" >

#### 2-2. データ型の作成&表示

App.tsxを開きます。
return文の中身を外側のdivを残して一旦削除します。
一番上のimport logo, import cssも使わないので削除します。

```App.tsx
import React from 'react';
import './App.css';

function App() {
  return (
    <div>

    </div>
  );
}

export default App;
```

今回受け取るデータの型を定義します。
合わせてテストデータを作成し、ブラウザで正しく表示できることを確認します。

```App.tsx
import React from 'react';

type Fruit = {
  id: number;
  name: string;
  icon: string;
}

var fruits = [
  { id: 1, name: "apple", icon: "🍎" },
  { id: 2, name: "banana", icon: "🍌" },
  { id: 3, name: "grape", icon: "🍇" },
]

function App() {
  return (
    <div>
      {fruits.map(fruit => (
        <p key={fruit.id}>
          <span>{fruit.name}</span>
          <span>{fruit.icon}</span>
        </p>
      )
      )
      }
    </div>
  );
}

export default App;

```

ブラウザに正しく表示できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2907902/6647527c-a41c-5a10-6853-7c9ad3a26d12.png)

正しく表示できることが確認できたので、テストデータは消しておきます。
（一時的にエラーとなります）

```App.tsx
import React from 'react';

type Fruit = {
  id: number;
  name: string;
  icon: string;
}

function App() {
  return (
    <div>
      {fruits.map(fruit => (
        <p key={fruit.id}>
          <span>{fruit.name}</span>
          <span>{fruit.icon}</span>
        </p>
      )
      )
      }
    </div>
  );
}

export default App;

```

#### 2-3. データを保持するstateの作成

useStateで非同期で取得したデータを保持するstateを作成します。

```App.tsx
import React, { useState } from 'react';

type Fruit = {
  id: number;
  name: string;
  icon: string;
}

function App() {
  const [fruits, setFruits] = useState<Fruit[]>([{ id: 0, name: "", icon: "" }])

  return (
    <div>
      {fruits.map(fruit => (
        <p key={fruit.id}>
          <span>{fruit.name}</span>
          <span>{fruit.icon}</span>
        </p>
      )
      )
      }
    </div>
  );
}

export default App;
```

#### 2-4. axiosで非同期リクエストを送り、jsonデータを取得する

まずはaxiosをインストールします。

```
~/Desktop/dev/react-app$ npm install axios
```

package.jsonにaxiosが追加されていることが確認できます。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2907902/812f0f33-5cb6-a6d7-f905-39a728788999.png">

次にuseEffect, axiosを使用してサーバーにリクエストを送信します。

```App.tsx
import React, { useState, useEffect } from 'react';
import axios from "axios";

type Fruit = {
  id: number;
  name: string;
  icon: string;
}

function App() {
  const [fruits, setFruits] = useState<Fruit[]>([{ id: 0, name: "", icon: "" }])

  useEffect(() => {
    (
      async () => {
        const data = await axios.get("http://localhost:8080")
        console.log(data.data)
        console.log(data.data[0])
        setFruits(data.data)
      }
    )()
  }, [])

  return (
    <div>
      {fruits.map(fruit => (
        <p key={fruit.id}>
          <span>{fruit.name}</span>
          <span>{fruit.icon}</span>
        </p>
      )
      )
      }
    </div>
  );
}

export default App;

```

1で構築したサーバーにリクエストを送ります。
サーバーを起動します。

```
~/Desktop/dev/go-app$ go run main.go
Starting server at port 8080
```

ブラウザで`http://localhost:3000`にアクセスすると真っ白い画面が表示され何も表示されていません。
開発者ツールを開きます。
するとCORSエラーが発生しているのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2907902/869754da-2f85-4169-1a94-0a8e976e67a9.png)

これはサーバー（`http://localhost:8080`）に異なるorigin（`http://localhost:3000`）からアクセスしていることが原因です。

CORSに関してはこちらの動画がわかりやすいので、こちらでご確認ください。

[【CORS入門】もうCORSエラーに苦しむことはありません。Webエンジニア必見です。](https://www.youtube.com/watch?v=8fE2TmbPqlU)

#### 2-5. CORSの解消

Ctrl+ C　で一度goのサーバーを停止します。

CORS解消のため、レスポンスヘッダーのAccess-Control-Allow-Origiにリクエスト元の `http://localhost:3000`を指定します。


```main.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type fruit struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Icon string `json:"icon"`
}

var fruits = []fruit{
	{ID: 1, Name: "apple", Icon: "🍎"},
	{ID: 2, Name: "banana", Icon: "🍌"},
	{ID: 3, Name: "grape", Icon: "🍇"},
}

func main() {
	http.HandleFunc("/", getFruits)
	fmt.Println("Starting server at port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func getFruits(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000") // 追加
	json.NewEncoder(w).Encode(fruits)
}

```

再度Goのサーバーを起動します。

```
~/Desktop/dev/go-app$ go run main.go
Starting server at port 8080
```

`http://localhost:3000`にアクセスし、ブラウザをリフレッシュすると、データが取得できていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2907902/dac556bf-8c94-fb33-7d19-2bfe1e4b5798.png)

以上でフロントエンドとバックエンドの通信が実現できました。
なお、Goにはフロント作成用のテンプレートがありますが、実務ではあまり使用されず、ReactやVueといったフロントエンドフレームワークと組み合わせて使用されるのが一般的なようです。

以上です。