【Node.js】Create-react-appしたプロジェクトでExpress/Knex/Postgres環境を設定、ミニマルなフルスタックアプリを作ってみる

#はじめに

Reactライブラリを使用してフルスタックプロジェクトを作る際、手順やディレクトリの構成などバックエンド側で必要な環境の設定にてこずったので、備忘録の意味も兼ねて本記事を作成します。

##やりたい事&使用したテックスタック
 1. Create-react-appしてプロジェクトを立ち上げ
 2. Expressを使用したサーバー立ち上げ
 3. Postgresを使用したDBの作成
 4. Knex.jsでMigration/Seed設定
 5. RESTapiを使用したデータ取得
 6. 取得データをフロントエンドに反映

の順でやって行きます。

本記事投稿時の私のローカル環境スペックは以下の通り。
 ・ macOS Catalina → ver10.15.7
 ・ node.js → ver14.14.0
 ・ postgres → ver13.0
 ・ Visual Studio Code → ver1.52.0

##参照記事
本記事作成にあたって、部分的に下記の文献を参考にさせてもらいました。
一つ目は全面英語ですが、 ステップbyステップでわかりやすく説明してくれています。
[Getting Started with Node.js, Express, and Knex](https://medium.com/@tobie.tsuzuki/getting-started-with-node-js-express-and-knex-5640f595df98)
[React開発時には、APIサーバーとReactアプリサーバーを別にして、プロキシを使うというベスト・プラクティス](https://applingo.tokyo/article/1568)

#1. Reactプロジェクトを立ち上げ
それでは早速やって行きましょう！
まずは任意の場所でReacプロジェクト(今回はproject-holidayという名前)を作成、VScodeで立ち上げます。

```javascript:User>Desktop
npx creae-react-app project-holiday
cd holiday-project
. code
```

###必要なDependenciesのインストール
```javascript:root
yarn add express pg knex nodemon dotenv
```
ここでnodemonを入れておくとサーバーを立ち上げている状態でもファイルの変更を即時反映してくれるホットリロードが使えて大変便利。
そしてknexはグローバルインストールしておかないと上手く機能しないので注意。
そしてそしてここではyarnが使えない場合があるので、パッケージマネージャーの混合が良くないのは承知ですがnpmでinstallします。

```javascript:root
npm install -g knex
```


#2. Expressを使用したサーバー立ち上げ
プロジェクトのルート直下、srcと同層にserverフォルダを用意し、中に使用するjsファイルを準備する。

```javascript:root
mkdir server
cd server
touch index.js app.js knex.js
```
app.jsとindex.jsに以下の通り記述しポートとサーバーの用意。

```javascript:server>app.js
const express = require('express')
const app = express()

app.use(express.json());

//確認用
app.get("/api", (req,res) => {
    res.send("We did it!")
})

module.exports = app;
```
```javascript:server>index.js
const app = require('./app')
const PORT = process.env.PORT || 9000

app.listen(PORT, () => console.log(`listening on port : ${PORT}`))
```
rootに戻り、nodemon serverでサーバーを起動してみる。
ブラウザでhttp://localhost:9000/api にアクセスしWe did it!と表示されていればサーバーの立ち上げ無事完了！


#3. Postgresを使用したDBの作成
データベースを作成します。

```
psql
CREATE DATABASE happy-holiday
\c happy-holiday
\dt
```
作り立てホヤホヤなのでテーブルは何も入っておらず、「Did not find any relations.」と表示されるはず。
今はそれで大丈夫。終わったら\qでDB環境から出ます。


#Tip: yarn ~ 独自コマンドを設定する
Migration/Seeding時にあると便利なコマンドを、package.jsonファイル内のscriptに追加します。

```javascript:root>package.json
...
"scripts": {
    ...
    "makeMigration": "./node_modules/knex/bin/cli.js migrate:make",
    "makeSeed": "./node_modules/knex/bin/cli.js seed:make",
    "migrate": "./node_modules/knex/bin/cli.js migrate:latest",
    "seed": "./node_modules/knex/bin/cli.js seed:run"
    "rollback": "./node_modules/knex/bin/cli.js migrate:rollback",
  },
...
```

#4. Knex.jsでMigration/Seed設定
前項で作ったDBの変更履歴管理用にマイグレーション、初期データ流し込み用にシードをKnexを使って作っていく。

```javascript:root
knex init
```
ルート直下にknexfile.jsが生成されます。中身をみると開発用やプロダクト用のコネクションコードが準備されていますが、簡潔に以下のコードに置き換えちゃいます。
初期値ではcientがSQliteになっているが今回はpostgresなのでpgに変更。機密情報は.envファイルに格納するのをお忘れなく。

```javascript:root>knexfile.js
module.exports = {
  client: "pg",
  connection:
    process.env.DATABASE_URL ||
    `postgres://${process.env.USER}@127.0.0.1:5432/happy_holiday`,
    searchPath: "public",
    migrations: {
      directory: __dirname + "/migrations"
    },
    seeds:{
      directory: __dirname + "/seeds"
    }
}
```
```
yarn makeMigration todo(テーブル名)
yarn makeSeed 01_Seed(シード名)
```
すると、root直下に
 - migrationsフォルダと配下にマイグレーションファイル
 - seedsフォルダと配下にシードファイル
が用意されるので、テーブルの中身と初期データを編集していく。

```javascript:root>migrations>マイグレーションファイル
exports.up = async function(knex) {
    await knex.schema.createTable('todo', table => {
        table.increments() //PRIMARY KEY=idとなる
        table.string('task') //列１
    })
};

//ロールバック用
exports.down = async function(knex) {
    await knex.schema.dropTableIfExists('todo')
};
```

```javascript:root>seeds>シードファイル
exports.seed = async function(knex) {
  // Deletes ALL existing entries
  await knex('todo').del()
  // Inserts seed entries
  await knex('todo').insert([
    {task: '家の掃除'},
    {task: '年賀状を書く'},
    {task: '実家に帰る'},
    {task: '美味しい物を食べる'},
    {task: 'コーディングする'},
  ]);
};
```
準備が出来たら下記のコードでテーブル作成、データを流し込む。

```
yarn migrate
yarn seed
```
DB内を確認してみると
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483050/be85fbf4-efd9-eca7-fd4a-bdd6b97f167b.png" width=40%>
無事成功！👏


#5. RESTapiを使用したデータ呼び出し
DBの設定が無事出来たら、RESTapiを使ってデータを呼び出していく。
今回はとりあえずGETで全データを呼び出せるかを検証。
その前に手付かずだったserevr>knex.jsを編集。app.jsとindex.jsでもrequireしてあげる。

```javascript:root>server>knex.js
//rootのknexfile.jsを読み込んでDB接続をする
knex = require('knex')
config = require('../knexfile')
database = knex(config)

module.exports = database;
```
```javascript:root>server>app.js&inde.js
const database = require('./knex')
```

これでapp.js(api) - knex.js(DB) - knexfile.js(DB設定)が繋がった。
app.js内で以下GETメソッドを追加して

```javascript:root>server>app.js
app.get("/api/todo", (req,res) => {
    database("happy_holiday").select().then((result) => {
        res.send(result);
    })
})
```
再びrootでnodemon server→ブラウザで http://localhost:9000/api/todo にアクセスしてみると。。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483050/bbf523fd-bc03-7753-1388-cc7c70cc0918.png width=100%>
無事取得出来てた👏

#6. 取得データをフロントエンドに反映させる
バックエンドの設定が出来たら次は取得したデータをフロント側で表示させたい。
まずはフロント(PORT:3000)とバック(PORT:9000)を繋ぐ為のsetupProxyファイルをsrcディレクトリ内に作成。
なんでこのファイルが必要なのか？という疑問に関しては[こちらの記事](https://applingo.tokyo/article/1568)でとてもわかりやすく解説されているので興味ある方はどうぞ。
srcディレクトリに入って必要dependencyのインストール

```
yarn add http-proxy-middleware
```
```javascript:root>src>setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    "/api",
    createProxyMiddleware({
      target: "http://localhost:9000",
      changeOrigin: true,
    })
  );
};
```

後はプロジェクト立ち上げ時に初期ファイルとして設定されたsrc配下のApp.jsで、Hooksや非同期処理を組み合わせながらfetchの処理を書いていきます。

```javascript:root>src>setupProxy.js
import './App.css';
import React, {useState, useEffect} from "react"

function App() {

  const [todo, setTodo] = useState([]);

  async function fetchData(){
    await fetch("/api/todo")
    .then((response) => response.json())
    .then((data) => setTodo(data))
  }

  useEffect(()=>{
    fetchData()
  })

  return (
    <div className="App">
      <header className="App-header">
        <h2>Happy Holiday!</h2>
        <ul>
          {todo.map((element) => <li key={element.id}>{element.task}</li>)}
        </ul>
      </header>
    </div>
  );
}

export default App;
```

これで準備完了！ node server & yarn startを実行してみましょう.
さあブラウザに何がどう映るか・・!（ドキドキ）
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483050/0cb85f40-34b6-56f1-8f23-2ae325cf19e9.png width=90%>
DBのTodoデータがリストとなって表示されました！👏

#まとめ
今回はCreate-react-appのプロジェクト作成からExpressによるサーバー立ち上げ、
PostgresによるDB作成、Knes.jsによるマイグレーション・シードファイルの作成とテーブル/データ作成、
そしてRESTapiを記述してフロントに反映させる一巡の流れを記事にしました。
とてもとても小さいですが、これで一応フルスタックのアプリ開発が出来たこととなります！！（わーい）

思いつく限りでも下記の改善点が頭に出てくるので、そのうちまた記事を載せていきたいですね。
 - 複数のテーブルを用意した複雑なスキーマのDB作成（one-to-one,one-to-many,many-to-many）
 - GET以外のメソッド(POST/PUT/DELETE)を使ったapi作成
 - ボタンや入力フォーム、検索バーや非表示などの他インタラクティブな機能を追加
 - ホスティングサービスへのデプロイ

最後まで読んでくださった方、ありがとうございました。
Happy Hacking~👋