Next.jsでサウナで整いたいお気持ち度合い記録アプリを3時間で作る

## はじめに
今日は私の誕生日です。記事をご覧いただきありがとうございます。
せっかくなので、記念に自分にアプリをプレゼントすることにしました。

ガバガバ設計なのでこのまま真似はおすすめしませんが、この程度のクオリティであれば３時間で作れます。
慣れてる方は秒で作れると思います。

## 概要
定期的にサウナに行きたくなる私ですが、正直そこまで行きたくないかなという気分の日が多いです。

なので、その日のサ○ナイキタイ度合いを可視化できたら最高だなと思い、作ることにしました。

行きたい度が100%を連続するようであれば、早急に整いに行く必要があり、それをひと目で把握することができます。

その名も、[サウナ気分](https://sauna-kibun.vercel.app/)。

### URL
https://sauna-kibun.vercel.app/

誰でも書けるので、ぜひ~~荒らしてみて~~記録してみてください。
できれば本当に「行きたい度合い」を書いてくれると嬉しいですが・・。




### 使用技術
- [Next.js(Vercel)](https://nextjs.org/)
- [Chart.js](https://www.chartjs.org/)
- MySQL 5.6 ([Cloud SQL](https://cloud.google.com/sql))


### トップページ
それっぽいカレンダーと、日毎に行きたい度をドーナツグラフで表示。
カレンダーは、ハッタリです。
ただのカレンダーです。いずれ、その人のその日のお気持ち％を色別で表示するようにします。

![Screen Shot 2020-12-19 at 1.03.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170251/0b46b758-d899-bfcb-db35-cbeec5ac119a.png)

### お気持ち入力画面
手抜き度が垣間見えます。New。CREATE。
行きたい度合いを表明します。

![Screen Shot 2020-12-19 at 1.09.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170251/e770a350-5580-2f31-9aa8-6178be4238c9.png)

### 入力後
お気持ちとともに、アメリカンスタイルな日付が、いつの投稿か教えてくれます(直せ)。

![Screen Shot 2020-12-19 at 1.17.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170251/4cc5ef11-9030-6f4e-bf99-5207cc126145.png)


## ハマりどころ

### NextApiHandler
デフォルトだと、`typeof parseInt(id) !== 'number'` という条件で、idが`string|string[]`型の場合怒られます。
雰囲気でtsを書いているのでもっと良い書き方あるとは思いますが、無理やり`string`の`newId`を定義して、idが配列だった場合の最初の値をぶちこむことで対応しました。

#### 変更前(デフォルトのget-entry.ts)
```ts:get-entry.ts
import { NextApiHandler } from 'next'
import { query } from '../../lib/db'

const handler: NextApiHandler = async (req, res) => {
  const { id } = req.query
  try {
    if (!id) {
      return res.status(400).json({ message: '`id` required' })
    }
    if (typeof parseInt(id) !== 'number') {
      return res.status(400).json({ message: '`id` must be a number' })
    }
    const results = await query(
      `
      SELECT id, title, content
      FROM entries
      WHERE id = ?
    `,
      id
    )

    return res.json(results[0])
  } catch (e) {
    res.status(500).json({ message: e.message })
  }
}

export default handler
```

#### 変更後
```diff
import { NextApiHandler } from 'next'
import { query } from '../../lib/db'

const handler: NextApiHandler = async (req, res) => {
-  const { id } = req.query
+  let { id } = req.query
+  let newId:string
  try {
    if (!id) {
      return res.status(400).json({ message: '`id` required' })
    }
+    if (Array.isArray(id)) {
+       newId = id[0]
+    }
-    if (typeof parseInt(id) !== 'number') {
+    if (typeof parseInt(newId) !== 'number') {
      return res.status(400).json({ message: '`id` must be a number' })
    }
    const results = await query(
      `
      SELECT id, title, content
      FROM entries
      WHERE id = ?
    `,
      id
    )

    return res.json(results[0])
  } catch (e) {
    res.status(500).json({ message: e.message })
  }
}

export default handler
```

### Next.jsの with-mysql テンプレート
`yarn migrate` しても絶対コケるので手動でSQL流しました。

### Error: Cannot read property '0' of null
https://www.npmjs.com/package/bad-words
`filter()`への渡し方に問題があったようなので、一度無効にしました。

```ts:handler
import Filter from 'bad-words'
import { query } from '../../lib/db'
    // 省略
    const filter = new Filter(
    const { foo, bar } = req.body
    const results = await query(
      `
      INSERT INTO entries (foo, baa)
      VALUES (?, ?, ?)
      `,
      // [filter.clean(foo), filter.clean(bar)]
      [foo, bar]
    )
```

## まとめ
- with-mysqlテンプレートで生成すれば、環境変数だけでDBに接続できる。デフォルトでCRUD周りも自動で生成してくれて楽。
- カレンダーは見せかけなので、今後色を反映して、より賢く可視化する
- 誰でも無限に書き込めます(認証周りまでやる元気なかった)
- 整いたい気持ちは3時間で作れる

今後もう少しきちんとお手入れしようと思いました。アプリでもサウナでも、更に整える境地を目指せそうです。
ぜひ皆さんも、整いたい気分を書き残してみてください！

## 参考
- [Create a Next.js App with a MySQL Database That Builds and Deploys with Vercel](https://vercel.com/guides/deploying-next-and-mysql-with-vercel)
- [【Next.js】GCPのデータベースをNext.jsプロジェクトに組み込みたい](https://nekorokkekun.hatenablog.com/entry/2019/09/20/104153)
- [Cloud SQL](https://cloud.google.com/sql)
- [サウナイキタイ](https://sauna-ikitai.com/)