Docker + NestJs + React(Vite)で環境構築

フルスタックアプリを作成するにあたって、環境構築をしました。
今後のために、流れを記録しておきたいと思います。

## ディレクトリ作成

```
$ mkdir bomberman
$ cd bomberman
```

githubレポジトリを作成しておく。

## サーバーサイド

### プロジェクト作成

nestのコマンドラインを使えるようにインストールする。
```
$ npm i -g @nestjs/cli
```
nestプロジェクトを作成
```
$ nest new server
```
パッケージ管理に何を使うか聞かれるので、yarnを選択する。

自動的にgitファイルが作られているので削除する。
```
$ rm -rf .git
```

- ポート番号を変更
  - 環境変数からポート番号を取得できるようにする（本番環境用）
  - クライアントサイドで3000番を使うので5000に変更（開発環境用）
```diff_typescript:server/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  - await app.listen(3000);
  + await app.listen(process.env.PORT || 5000);
}
bootstrap();
```

### 開発環境をDockerで構築してみる

```dockerfile:server/Dockerfile.local
FROM node:18.14.0-alpine3.17

WORKDIR /app

COPY package.json ./
COPY yarn.lock ./

RUN yarn install

COPY . .

EXPOSE 5000

CMD ["yarn", "run", "start:dev"]
```



```server/docker-compose.yml
services:
  server:
    container_name: server
    build: 
      context: .
      dockerfile: Dockerfile.local
    working_dir: /app
    ports:
      - 5000:5000
    restart: always
    tty: true
    volumes:
      - ./:/app
```


```server/.dockerignore
.git
node_modules/
dist/
```

### デプロイの設定

CloudBuildを使用して、CloudRunにデプロイする
mainブランチを更新すると自動的にデプロイされるように設定

##### docs
- CloudRun
https://cloud.google.com/run/docs/continuous-deployment-with-cloud-build?hl=ja

- Docker
https://docs.docker.com/get-started/

https://docs.docker.com/language/nodejs/

- 事例
https://zenn.dev/waddy/articles/nestjs-cloudrun-deploy#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E5%85%B1%E9%80%9A%E6%BA%96%E5%82%99


本番環境用のDockerfileを作成する

```dockerfile:server/Dockerfile
FROM node:18.14.0-alpine3.17 AS builder

WORKDIR /app

COPY package.json ./
COPY yarn.lock ./

RUN yarn install

COPY . .
RUN yarn build

##########################################################

FROM node:18.14.0-alpine3.17 AS runner

WORKDIR /app

ENV NODE_ENV production

EXPOSE 5000

COPY package.json ./
COPY yarn.lock ./

RUN yarn install

COPY --from=builder /app/dist ./dist
CMD ["yarn", "run", "start:prod"]
```

CloudBuildを使ってデプロイするので、ファイルを準備する。
```cloudbuild.yaml
steps:
 # Build the container image
 - name: 'gcr.io/cloud-builders/docker'
   args: ['build', '-t', 'gcr.io/$PROJECT_ID/bomberman:$COMMIT_SHA', '.']
   dir: 'server'
 # Push the container image to Container Registry
 - name: 'gcr.io/cloud-builders/docker'
   args: ['push', 'gcr.io/$PROJECT_ID/bomberman:$COMMIT_SHA']
 # Deploy container image to Cloud Run
 - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
   entrypoint: gcloud
   args:
   - 'run'
   - 'deploy'
   - 'bomberman'
   - '--image'
   - 'gcr.io/$PROJECT_ID/bomberman:$COMMIT_SHA'
   - '--region'
   - 'asia-northeast1'
 images:
 - 'gcr.io/$PROJECT_ID/bomberman:$COMMIT_SHA'

```



### ソケット通信
（ソケット通信を実装する場合）


```
$ yarn add @nestjs/websockets @nestjs/platform-socket.io socket.io
```

```
$ nest g module events
$ nest g gateway events
```

## クライアントサイド

### プロジェクト作成
```
$ cd bomberman
```

```
$ yarn create vite
```
色々聞かれるので、
プロジェクト名は「client」にして、react-tsを選択

```
$ yarn install
```


### デプロイ
vercelにデプロイする。
デプロイするディレクトリをclientに指定し、以下の環境変数を設定
```
VITE_API_URL : バックエンドのURL
```
### 環境構築

#### Eslint

eslintをインストール
```
$ yarn add -D eslint
```

以下のコマンドでセットアップを始める。
```
$ npm init @eslint/config
```

```
Need to install the following packages:
  @eslint/create-config@0.4.2
Ok to proceed? (y)
```
パッケージのインストールが必要みたいなので、「y」を入力してEnterを押す。

```
? How would you like to use ESLint? … 
  To check syntax only
▸ To check syntax and find problems
  To check syntax, find problems, and enforce code style
```

「To check syntax and find problems」を選択

```
? What type of modules does your project use? … 
▸ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these
```

「JavaScript modules (import/export)」を選択

```
? Which framework does your project use? … 
▸ React
  Vue.js
  None of these
```

「React」を選択

```
? Does your project use TypeScript? ‣ No / Yes
```

「Yes」を選択

```
? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node
```

「Browser」を選択

```
? What format do you want your config file to be in? … 
▸ JavaScript
  YAML
  JSON
```

「Javascript」を選択（好きなもので良い）


```
The config that you've selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
? Would you like to install them now? ‣ No / Yes
```

「Yes」を選択

```
? Which package manager do you want to use? … 
  npm
▸ yarn
  pnpm
```

「yarn」を選択

以下のファイルが作られる。
```client/.eslintrc.cjs
module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "overrides": [
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
}
```


#### Prettier

```
$ yarn add --dev --exact prettier
```

```
$ echo {}> .prettierrc.json
```

```client/.prettierignore
# Ignore artifacts:
build
coverage
```

```
$ yarn prettier --write .
```

https://prettier.io/docs/en/install.html#set-up-your-editor

```
$ yarn add -D eslint-config-prettier
```

```diff_typescript:client/.eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
+   "prettier"
  ],
  overrides: [],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["react", "@typescript-eslint"],
  rules: {},
};
```

https://github.com/prettier/eslint-config-prettier#installation

```
$ yarn add -D eslint-plugin-prettier
```

```diff_typescript:client/.eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier",
  ],
  overrides: [],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
- plugins: ["react", "@typescript-eslint"],
+ plugins: ["react", "@typescript-eslint", "prettier"],
  rules: {
+   "prettier/prettier": "error",
  },
};
```

https://github.com/prettier/eslint-plugin-prettier#recommended-configuration


```diff_typescript:client/.eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
-   "prettier",
+   "plugin:prettier/recommended"
  ],
  overrides: [],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
- plugins: ["react", "@typescript-eslint", "prettier"],
+ plugins: ["react", "@typescript-eslint"],
  rules: {
-   "prettier/prettier": "error",
  },
};
```


```
$ yarn add -D eslint-plugin-react-hooks
```

```diff_typescript:client/.eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
+   "plugin:react-hooks/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
  ],
  overrides: [],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
- plugins: ["react", "@typescript-eslint"],
+ plugins: ['react', 'react-hooks', '@typescript-eslint'],
  rules: {},
};
```


https://www.npmjs.com/package/eslint-plugin-react-hooks



```typescript:client/.prettierrc.json
{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true,
  "jsxSingleQuote": true,
  "printWidth": 80
}
```
https://prettier.io/docs/en/configuration.html#docsNav


```diff_typescript:client/package.json
{
"scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
+   "format": "prettier --write 'src/**/*.{ts,tsx,css}'",
+   "lint": "eslint --fix 'src/**/*.{ts,tsx}'"
  },
}
```



https://prettier.io/docs/en/cli.html

https://eslint.org/docs/latest/use/command-line-interface



```
$ yarn run lint
```

「React' must be in scope when using JSX」というエラーが表示されるので、eslintの設定を修正する。

```diff_typescript:client/.eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  overrides: [],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', 'react-hooks', '@typescript-eslint'],
  rules: {
+   'react/react-in-jsx-scope': 'off',
  },
};
```

https://zenn.dev/ryuu/scraps/583dad79532879



#### 参考記事

https://blog.devgenius.io/eslint-prettier-typescript-and-react-in-2022-e5021ebca2b1

https://qiita.com/massu159/items/477e8e80092d3f47fb06#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB