GoとNuxtで飲食店検索ができるLINE BOTとLINEミニアプリ(LIFF)を作ってみた

![全体構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/d1be6d81-7699-e340-477f-13a7b9834c04.png)

# はじめに
Go / Nuxt.js / LINE Messaging API / Google Maps API / クリーンアーキテクチャ で、**飲食店検索やお気に入り登録できるLINE BOTとLIFFアプリ**を作ってみました！

LIFFアプリとはLINEやWeb上で動作するアプリのことです。
LINEミニアプリと呼ばれることもあります。
詳しくは後述します。

LINE BOTを含めバックエンドは元から作っていたのですが、ビジネスロジック部分をAPI化してLIFFと連携できるようにしてみました。

バックエンドに関する部分は以下の記事にまとめています。
こちらを読んでいなくても、この記事は読めるように書いています！
バックエンドやクリーンアーキテクチャに興味のある方はぜひ読んでみてください。

[Goとクリーンアーキテクチャで飲食店検索ができるLINE BOT作ってみた](https://qiita.com/yagi_eng/items/983a7cd36b63a2f0ffac)
⇒Qiitaデイリーで**最高5位**にランクインしました！

この記事では、全体構成やLIFFアプリに関する部分などをまとめようと思います。

## 読んだら幸せになれそうな人
- 個人開発に興味がある人
- モダンな技術に興味がある人
- Go, Nuxtに興味がある人
- LINE Messaging API (LINE BOT) やLIFFに興味がある人

##こんなのつくりました
### LINE BOT
デモ動画のロングバージョンは[こちら(Twitter)](https://twitter.com/i/status/1273208922682191875)にあります。
<img width="350" alt="mojikyo45_640-2.gif" src="https://p9.f1.n0.cdn.getcloudapp.com/items/NQuggG5P/VID_20200617_163635.gif">

### LIFF
デモ動画のロングバージョンは[こちら(Twitter)](https://twitter.com/i/status/1276721223808892929)にあります。
<img width="350" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/5fac11fe-33b0-5844-6831-16becdc15f5d.gif">
（くそ画質ですいません。。）

## お試しはこちら
### LINE BOT
以下のURLにアクセスするか、QRコードをLINEで読み取るとお試しできるので、良ければ試してみてください！

https://lin.ee/R5mD4Dt
![M.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/251e4c47-0a88-7f8d-d339-c5153a14ba86.png)

### LIFF
上記からアクセスするとLINE上で起動することができます。
Webブラウザ上では[こちら](https://resto-clip-liff.herokuapp.com/login)からお試し頂けます。
後述しますが、LINE上で起動する方が安定しています。。
ちなみにデモ動画はLINE上で起動したものです。

## つくった目的
以下を目的として作ってみました。ほぼ前の記事と同じです。

- Goとクリーンアーキテクチャ、Nuxtの習熟
- LINE関係のノウハウの蓄積
- 好きな飲食店を気軽にメモしたかった（でも食べログなどのアプリは入れたくなかった）

Goは人気急上昇中の言語なので、今のうちに勉強してGo案件にジョインしたいという下心強めです笑

クリーンアーキテクチャを採用した理由は、自分がMVCパターンにしか触れたことしかなく、そろそろ新しいフレームワークを習得したいと思ったからです。

また、LINE関係のノウハウを貯めておきたかったのでLINE BOTを題材にしました。
LINE上でミニアプリが動作できるようになる **LINEミニアプリ** が近いうちにリリースされるみたいなので波乗りしたいところです。

## ソースコード
- [バックエンド(Go&LINE BOT)](https://github.com/yagi-eng/place-search)
- [フロントエンド(Nuxt&LIFF)](https://github.com/yagi-eng/place-search-liff)

# LIFFってなに
## 説明
簡単に言うと、**LINEと簡単に連携できて、ブラウザやLINE上で動作するアプリを作れるやつ**です。

以下[公式HP](https://developers.line.biz/ja/docs/liff/overview/)からの抜粋です。
> LINE Front-end Framework（LIFF）は、LINEが提供するウェブアプリのプラットフォームです。このプラットフォームで動作するウェブアプリを、LIFFアプリと呼びます。

>LIFFアプリを使うと、LINEのユーザーIDなどをLINEプラットフォームから取得できます。LIFFアプリではこれらを利用して、ユーザー情報を活用した機能を提供したり、ユーザーの代わりにメッセージを送信したりできます。

Front-end Frameworkということで、**LIFFはフロントエンドで動作します**。

## 入門はこちら
以下にLIFF入門者向けの記事を書いていますので良かったら参考にしてください。
[【LINE/LIFF入門】LIFFでLINE公式アカウント（旧LINE＠）上に予約フォームを作成する（GitHub Pages使用）](https://qiita.com/yagi_eng/items/5dce5527dc5d6a6e9833)

ちなみに上記記事をもとにLTもしました！
以下にその時のスライドがあるのでこちらも参考にしてみてください。
[LIFFでLINE公式アカウント上に予約フォームを作成する](https://www.slideshare.net/ssuser11af3b1/liffline)
<img width="481" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/ba1e03ff-e798-8c6a-3c51-39e51205b8f2.png">

# 技術面全般
## 使用技術
- Go 1.14
- Nuxt.js 2.13
- LINE Messaging API
    - LINE BOTが作れるAPI
- Google Maps API
    - Google Mapの情報を取得できるAPI
- LIFF
- クリーンアーキテクチャ
- Heroku

Goの使用ライブラリは[こちら](https://qiita.com/yagi_eng/items/983a7cd36b63a2f0ffac#%E4%BD%BF%E7%94%A8%E6%8A%80%E8%A1%93)に記載しています。

## 全体構成
![全体構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/d1be6d81-7699-e340-477f-13a7b9834c04.png)

- GoサーバとNuxtサーバの2つを用意
    - サーバはHerokuを使用
- GoでLINE BOTとビジネスロジックを実装
    - ビジネスロジックにはLINE BOT用の口とAPI用の口の2つを持たせる
    - ビジネスロジックでLINEやGoogleMapのAPIをごちゃごちゃやっています
- NuxtでLIFFアプリを実装
    - LIFFアプリからGoで実装したAPIをたたく

## 今回の肝
LINEユーザは一意のユーザIDを持っています。
これを使うことで、LINE BOTからお気に入り登録しても、LIFFからお気に入り登録しても、どちらにも同じ結果が反映されるようにしました。

## Nuxt
Nuxtそのものは特筆すべき使い方はしませんでした。
Nuxtのディレクトリ構成や思想に従って、淡々とView部分やAPI通信部分を実装しました。

## CORS
今回はNuxtサーバから、別ドメインであるGoサーバのAPIを叩きに行っています。
これを実現するにはCORSの設定が必要であり、何も設定しないと別のドメインからはアクセスできない（はず）です。

自分はGoのechoというフレームワークを使っており、CORSはこんな感じ([github](https://github.com/yagi-eng/place-search/blob/master/server.go#L31))設定できます。

```go:server.go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
	AllowOrigins: []string{"Nuxtサーバのドメイン"},
}))
```

他ドメインからのアクセスを全て許容する場合は以下の通りです。

```go
e.Use(middleware.CORS())
```

# NuxtとLIFF
実装する上で2つのポイントがあります。
このポイントを抑えておけば淡々と実装していくだけです。

## LIFFの初期化＆LINEログイン
LIFFを使うにはまずLIFFの初期化が必要です。
また、LINEログインはLINE上でアプリを起動する場合には不要ですが、Webブラウザからアクセスする場合には必要です。

↓ LINEログイン
<img width="350" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/42687ac7-20df-cc1c-cb52-b90bfe689588.jpeg">

今回はstoreのactionにこんな感じ([github](https://github.com/yagi-eng/place-search-liff/blob/master/store/index.js#L14))で実装しました。
storeやactionは[Vuex](https://vuex.vuejs.org/ja/)の知識です。

```js:store/index.js
export const actions = {
    login({ commit, dispatch }) {
        liff
            .init({
                liffId: process.env.LIFF_ID
            })
            .then(() => {
                // Webブラウザからアクセスされた場合は、LINEにログインする
                if (!liff.isInClient() && !liff.isLoggedIn()) {
                    window.alert("LINEアカウントにログインしてください。")
                    liff.login({ redirectUri: location.href })
                }
                var token = liff.getIDToken()
                this.$cookies.set('jwt_token', token)
                commit('mutateLineIDToken', token)
            })
            .catch(err => {
                console.log("LIFF Initialization failed ", err)
            })
        this.app.router.push('/')
    }
}
```
ログイン画面遷移時にこのlogin関数を実行して、LIFF初期化＆LINEログインを行います。
LINEログインといっているのは、コード中の`liff.login({ redirectUri: location.href })`の部分です。
ログイン後には`liff.getIDToken()`でIDTokenを取得してstoreとcookieに保存します。

この実装だけだとIDTokenが失効した際にエラーが出てしまうのですが解消していません。
~~なんかめんどくさくなって諦めちゃいました (^q^)~~

## IDTokenからユーザIDを抽出する
LIFFでIDTokenを取得して、それをバックエンドに投げてユーザIDを抽出します。
LIFF単体でユーザIDを取得することもできますが、ユーザIDをそのままバックエンドに投げてしまうとセキュリティ的によろしくないためひと手間加えます。

ここの実装に結構時間がかかりましたが仕方ないですね。。

抽出方法については、[LINE Developers](https://developers.line.biz/ja/docs/liff/using-user-profile/#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E6%83%85%E5%A0%B1%E3%82%92%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%A7%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B)にまとまっていました。以下シーケンス図の抜粋です。
<img width="510" alt="id.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113263/95cc3a1e-3d8b-7dfa-5781-ff066f29a44f.png">

IDTokenをバックエンドに投げた後、さらに[LINE PlatformのAPI](https://developers.line.biz/ja/reference/social-api/#verify-id-token)を叩くことでユーザIDを抽出できます。
IDTokenが失効していると怒られます。

バックエンドはこんな感じ([github](https://github.com/yagi-eng/place-search/blob/master/interfaces/controllers/api_controller.go#L145))で実装しました。
GoでPOSTのHTTPリクエストを送る際は、`http.PostForm`を使います。

```go:api_controller.go
type verifyResp struct {
	Sub string `json:"sub"`
}

// getLineUserIDByToken tokenからLINEのuserIDを取得する
func getLineUserIDByToken(idToken string) string {
	values := url.Values{}
	values.Add("id_token", idToken)
	values.Add("client_id", os.Getenv("LIFF_CHANNEL_ID"))

	resp, err := http.PostForm(
		"https://api.line.me/oauth2/v2.1/verify",
		values,
	)

	if err != nil {
		logrus.Errorf("Error Parsing LINEIDToken: %v", err)
		return ""
	}

	body, _ := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()

	jsonBytes := ([]byte)(string(body))
	data := new(verifyResp)

	if err := json.Unmarshal(jsonBytes, data); err != nil {
		logrus.Errorf("Error JSON Unmarshal: %v", err)
		return ""
	}

	return data.Sub
}
```

# LINEBOTとLIFFを連携させる意義
実際のビジネスやサービス的なところにはあまり繋がらないかもしれませんね。
今回はあくまで技術習得をメインとして作っていたのですが、途中から「あれ、これ意味あるんか。。」となってモチベーションが下がった時もありました 笑

LINE BOTにもLIFFにもそれぞれ長所/短所があるので、互いに補い合える可能性はあるかもしれません。（浅い意見）

# 機能説明
クリーンアーキテクチャの理解に時間かけすぎて（言い訳）、大したもの作っていないのでさらっとだけ。

## キーワード検索
場所の名前や調べたい情報を入力すると、検索結果を返す。

## 位置情報検索（LINE BOTのみ）
トーク画面の左下の「＋」ボタンをタップして「位置情報」を選択すると、位置情報に応じた検索結果を返す。
現在地などを送信することで、現在いるお店や付近のお店を調べることができる。
※送信された位置情報は保存していない。

## お気に入り登録
検索結果の「Add to my favorites」をタップすると、場所をお気に入り登録できる。

## お気に入り一覧表示
**LINEBOT:** 「My FAVORITE」バナーをタップすると、登録したお気に入りが確認できる。
**LIFF:** トップページにデフォルトで表示。

## お気に入り解除
お気に入り一覧で「Remove」をタップすると、場所をお気に入り解除できる。

# 感想
Goの習熟を第一目的としていた割には、少し時間を割き過ぎました。。
自分でサービス開発をしたいと考えているので、入門的にもっとライトなものを作って、それから本格的な個人サービスを作れば良かったなという感じです。

ただ、Goは現場で即通用するレベルに引き上げられたと感じていますし、クリーンアーキテクチャやNuxt、LINEへの理解を深められたので良かったです。

しばらくは以下のことに注力していきたいと思います。

- モダンな案件への参画、できればGo使いたい
- 個人サービス開発
- （せっかくだから、知人経由でLINE BOTやLIFFアプリ開発の仕事とれないかな。。）

# Twitter
Twitterの方でもLINEに限らず、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください！
[@yagi_eng](https://twitter.com/yagi_eng)