├── .gitignore ├── .prettierrc ├── README.md ├── docker-python-flask-redis ├── .gitignore ├── Dockerfile ├── README.md ├── app.py ├── docker-compose.yml ├── requirements.txt └── templates │ └── index.html ├── recipe-1 ├── README.md ├── docker-compose.yml ├── docker │ └── app │ │ └── Dockerfile └── images │ ├── docker-01.png │ ├── docker-02.png │ ├── docker-compose-01.png │ ├── docker-compose-02.png │ ├── docker-compose-03.png │ ├── docker-compose-04.png │ ├── docker-compose-05.png │ └── docker-compose-06.png ├── recipe-2-0 ├── README.md ├── docker-compose.yml ├── docker │ ├── express-app │ │ └── Dockerfile │ └── react-app │ │ └── Dockerfile ├── images │ ├── docker-01.png │ ├── docker-02.png │ ├── docker-03.png │ ├── docker-04.png │ ├── docker-05.png │ ├── docker-06.png │ ├── docker-07.png │ ├── docker-08.png │ ├── docker-09.png │ ├── docker-10.png │ ├── docker-compose-01.png │ ├── docker-compose-02.png │ ├── docker-compose-03.png │ ├── docker-compose-04.png │ ├── docker-compose-05.png │ └── docker-compose-06.png └── sample │ ├── express-app │ ├── example.js │ ├── index.js │ └── views │ │ └── index.pug │ └── react-app │ ├── Index.js │ └── components │ ├── App.js │ └── Example.js ├── recipe-2-1 ├── README.md ├── docker-compose.yml ├── docker │ ├── express-app │ │ └── Dockerfile │ ├── mysql │ │ ├── Dockerfile │ │ └── conf.d │ │ │ └── my.cnf │ └── react-app │ │ └── Dockerfile └── sample │ ├── express-app │ ├── config │ │ ├── db.js │ │ └── index.js │ ├── index.js │ ├── router │ │ └── api │ │ │ └── users.js │ └── views │ │ └── index.pug │ └── react-app │ ├── Index.js │ └── components │ └── App.js ├── recipe-2-2 ├── README.md ├── docker-compose.yml ├── docker │ ├── express-app │ │ └── Dockerfile │ ├── mysql │ │ ├── Dockerfile │ │ └── conf.d │ │ │ └── my.cnf │ └── react-app │ │ └── Dockerfile └── sample │ ├── express-app │ ├── api │ │ ├── csrfToken.js │ │ ├── login.js │ │ ├── logout.js │ │ ├── tasks.js │ │ └── users.js │ ├── config │ │ ├── db.js │ │ ├── index.js │ │ └── jwt.config.js │ ├── index.js │ ├── middlewares │ │ ├── validator.js │ │ └── verifyToken.js │ ├── package.json │ └── views │ │ └── index.pug │ └── react-app │ └── src │ ├── Index.js │ ├── app │ └── store.js │ ├── components │ ├── App.js │ ├── Footer.js │ ├── Header.js │ ├── Layout.js │ ├── Login.js │ ├── Main.js │ ├── Modal.js │ ├── SignUp.js │ ├── Task.js │ ├── TaskDeleteModal.js │ ├── TaskInsertModal.js │ └── TaskUpdateModal.js │ ├── config │ └── index.js │ ├── features │ ├── auth │ │ └── authSlice.js │ └── tasks │ │ └── tasksSlice.js │ └── index.css ├── recipe-2.5 ├── README.md ├── docker-compose.yml ├── docker │ ├── express-app │ │ └── Dockerfile │ ├── mysql │ │ ├── Dockerfile │ │ └── conf.d │ │ │ └── my.cnf │ └── react-app │ │ └── Dockerfile └── sample │ ├── express-app │ ├── api │ │ ├── login.js │ │ └── users.js │ ├── config │ │ └── jwt.config.js │ ├── index.js │ └── middlewares │ │ └── verifyToken.js │ └── react-app │ ├── Index.js │ └── components │ └── App.js ├── recipe-docker-nginx-express ├── README.md ├── docker-compose.yml └── docker │ ├── api │ ├── Dockerfile │ ├── index.js │ └── package.json │ └── nginx │ ├── Dockerfile │ └── conf.d │ └── default.conf ├── recipe-express-socketio-redis ├── README.md ├── demo.gif ├── docker-compose.yml └── docker │ └── express-app │ ├── Dockerfile │ ├── index.html │ └── main.js ├── recipe-https-nginx-typescript-react-express-mysql ├── .gitignore ├── README.md ├── docker-compose.yml ├── docker │ ├── express-app │ │ └── Dockerfile │ ├── mysql │ │ ├── Dockerfile │ │ └── conf.d │ │ │ └── my.cnf │ ├── nginx │ │ ├── Dockerfile │ │ └── conf.d │ │ │ └── default.conf │ └── react-app │ │ └── Dockerfile └── sample │ ├── express-app │ └── index.ts │ └── react-app │ └── App.tsx ├── recipe-mysql-dockerfile ├── README.md └── docker │ └── mysql │ ├── Dockerfile │ └── conf.d │ └── my.cnf ├── recipe-mysql-no-dockerfile └── README.md ├── recipe-nginx-react-express-mysql ├── README.md ├── docker-compose.yml └── docker │ ├── express-app │ └── Dockerfile │ ├── mysql │ ├── Dockerfile │ └── conf.d │ │ └── my.cnf │ ├── nginx │ ├── Dockerfile │ └── conf.d │ │ └── default.conf │ └── react-app │ └── Dockerfile ├── recipe-postgresql-dockerfile ├── README.md └── docker │ └── postgresql │ └── Dockerfile ├── recipe-python-flask-blueprint ├── .gitignore ├── README.md ├── docker │ └── flask │ │ ├── Dockerfile │ │ └── requirements.txt └── sample │ └── app │ ├── __init__.py │ ├── api │ ├── __init__.py │ └── users.py │ └── templates │ └── base.html ├── recipe-rails-postgresql ├── README.md ├── docker-compose.yml └── docker │ ├── postgresql │ └── Dockerfile │ └── rails │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ └── database.yml ├── recipe-volume-share ├── README.md ├── docker-compose.yml └── docker │ ├── express-app │ ├── Dockerfile │ ├── index.js │ └── package.json │ └── nginx │ ├── Dockerfile │ └── conf.d │ └── default.conf ├── recipe-vscode-git-github-https ├── README.md └── images │ ├── git-00.png │ ├── git-01.png │ ├── git-02.png │ ├── git-03.png │ └── git-04.png └── tips ├── README.md ├── mysql-backup-restore └── README.md └── network-node-mysql └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | app/* 2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,windows,node 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,node 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Node ### 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | lerna-debug.log* 42 | 43 | # Diagnostic reports (https://nodejs.org/api/report.html) 44 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 45 | 46 | # Runtime data 47 | pids 48 | *.pid 49 | *.seed 50 | *.pid.lock 51 | 52 | # Directory for instrumented libs generated by jscoverage/JSCover 53 | lib-cov 54 | 55 | # Coverage directory used by tools like istanbul 56 | coverage 57 | *.lcov 58 | 59 | # nyc test coverage 60 | .nyc_output 61 | 62 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 63 | .grunt 64 | 65 | # Bower dependency directory (https://bower.io/) 66 | bower_components 67 | 68 | # node-waf configuration 69 | .lock-wscript 70 | 71 | # Compiled binary addons (https://nodejs.org/api/addons.html) 72 | build/Release 73 | 74 | # Dependency directories 75 | node_modules/ 76 | jspm_packages/ 77 | 78 | # TypeScript v1 declaration files 79 | typings/ 80 | 81 | # TypeScript cache 82 | *.tsbuildinfo 83 | 84 | # Optional npm cache directory 85 | .npm 86 | 87 | # Optional eslint cache 88 | .eslintcache 89 | 90 | # Optional stylelint cache 91 | .stylelintcache 92 | 93 | # Microbundle cache 94 | .rpt2_cache/ 95 | .rts2_cache_cjs/ 96 | .rts2_cache_es/ 97 | .rts2_cache_umd/ 98 | 99 | # Optional REPL history 100 | .node_repl_history 101 | 102 | # Output of 'npm pack' 103 | *.tgz 104 | 105 | # Yarn Integrity file 106 | .yarn-integrity 107 | 108 | # dotenv environment variables file 109 | .env 110 | .env.test 111 | .env*.local 112 | 113 | # parcel-bundler cache (https://parceljs.org/) 114 | .cache 115 | .parcel-cache 116 | 117 | # Next.js build output 118 | .next 119 | 120 | # Nuxt.js build / generate output 121 | .nuxt 122 | dist 123 | 124 | # Storybook build outputs 125 | .out 126 | .storybook-out 127 | storybook-static 128 | 129 | # rollup.js default build output 130 | dist/ 131 | 132 | # Gatsby files 133 | .cache/ 134 | # Comment in the public line in if your project uses Gatsby and not Next.js 135 | # https://nextjs.org/blog/next-9-1#public-directory-support 136 | # public 137 | 138 | # vuepress build output 139 | .vuepress/dist 140 | 141 | # Serverless directories 142 | .serverless/ 143 | 144 | # FuseBox cache 145 | .fusebox/ 146 | 147 | # DynamoDB Local files 148 | .dynamodb/ 149 | 150 | # TernJS port file 151 | .tern-port 152 | 153 | # Stores VSCode versions used for testing VSCode extensions 154 | .vscode-test 155 | 156 | # Temporary folders 157 | tmp/ 158 | temp/ 159 | 160 | ### Windows ### 161 | # Windows thumbnail cache files 162 | Thumbs.db 163 | Thumbs.db:encryptable 164 | ehthumbs.db 165 | ehthumbs_vista.db 166 | 167 | # Dump file 168 | *.stackdump 169 | 170 | # Folder config file 171 | [Dd]esktop.ini 172 | 173 | # Recycle Bin used on file shares 174 | $RECYCLE.BIN/ 175 | 176 | # Windows Installer files 177 | *.cab 178 | *.msi 179 | *.msix 180 | *.msm 181 | *.msp 182 | 183 | # Windows shortcuts 184 | *.lnk 185 | 186 | # End of https://www.toptal.com/developers/gitignore/api/macos,windows,node -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker-DockerCompose-Training 2 | 3 | ## 公式ドキュメント 4 | 5 | [docker docs](https://docs.docker.com/) 6 | 7 | ## 動作環境 8 | 9 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能) 10 | 11 | ## recipe 12 | 13 | - [tips](./tips/) 14 | 1. [mysql-backup-restore](./tips/mysql-backup-restore/) 15 | 1. MySQL コンテナのバックアップリストア 16 | 2. [network-node-mysql](./tips/network-node-mysql/) 17 | 1. docker の network 機能を用いてコンテナ間の通信の設定サンプル。 具体的には node(`node-app`)コンテナと mysql(`mysqld`)コンテナをコンテナ名で通信できるよう設定する 18 | - [recipe-1](./recipe-1/README.md) 19 | 1. Docker で環境構築 20 | 1. docker exec で接続し開発 21 | 1. VSCode から接続し開発 22 | 1. Docker の基本的な操作 23 | 2. Docker Compose で環境構築 24 | 1. Docker をマウントし開発 25 | 1. VSCode から接続し開発 26 | 1. Docker Compose の基本的な操作 27 | - [recipe-2-0](./recipe-2-0/README.md) 28 | 1. 全体を通して Docker、Docker Compose の基本的な使い方の理解 29 | 2. 全体を通して VSCode から Docker コンテナに接続しアプリケーションコードを作成 30 | 3. docker を用いてフロントサーバ、API サーバをたて連携(Part 1) 31 | 4. 3 の環境構築を Docker Compose で行う(Part 2) 32 | 5. (Training) 4 で改めて環境構築した環境で 3 のアプリを実装(Part 3)し他の機能も追加する 33 | - [recipe-2-1](./recipe-2-1/README.md) 34 | 1. [recipe-2-0](./recipe-2-0/README.md) に MySQL を追加、express-app を router で書き直し 35 | - [recipe-2-2](./recipe-2-2/README.md) 36 | 1. [recipe-2-0](./recipe-2-0/README.md),[recipe-2-1](./recipe-2-1/README.md) に jwt,cookie,csrf を追加 37 | - [recipe-mysql-dockerfile](./recipe-mysql-dockerfile/README.md) 38 | 1. Dockerfile を用いた MySQL の環境構築 39 | - [recipe-mysql-no-dockerfile](./recipe-mysql-no-dockerfile/README.md) 40 | 1. Dockerfile を用いずコマンドのみでの MySQL の環境構築 41 | - [recipe-python-flask-blueprint](./recipe-python-flask-blueprint/README.md) 42 | 1. Docker で Flask の開発環境(Blueprint を用いたサンプルコード`sample`配下まで配置)の構築例 43 | - [recipe-rails-postgresql](./recipe-rails-postgresql) 44 | 1. rails + PostgreSQL の環境 45 | - [recipe-postgresql-dockerfile](./recipe-postgresql-dockerfile) 46 | 1. PostgreSQL 単体の環境 47 | - [recipe--nginx-react-express-mysql] 48 | 1. Nginx + React + Express + MySQL の環境レシピ(React は build image を Nginx に volume の共有で認識させる) 49 | - [recipe-https-nginx-typescript-react-express-mysql](./recipe-https-nginx-typescript-react-express-mysql) 50 | 1. HTTPS + Nginx + TypeScript + React + Express + MySQL の環境レシピ(HTTPS は Nginx に証明書を設定する、React は build image を Nginx に volume の共有で認識させる) 51 | - [recipe-docker-nginx-express](./recipe-docker-nginx-express) 52 | 1. docker-compose での Web(Nginx) + API(express) の環境構築 53 | -------------------------------------------------------------------------------- /docker-python-flask-redis/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### Python ### 34 | # Byte-compiled / optimized / DLL files 35 | __pycache__/ 36 | *.py[cod] 37 | *$py.class 38 | 39 | # C extensions 40 | *.so 41 | 42 | # Distribution / packaging 43 | .Python 44 | build/ 45 | develop-eggs/ 46 | dist/ 47 | downloads/ 48 | eggs/ 49 | .eggs/ 50 | lib/ 51 | lib64/ 52 | parts/ 53 | sdist/ 54 | var/ 55 | wheels/ 56 | share/python-wheels/ 57 | *.egg-info/ 58 | .installed.cfg 59 | *.egg 60 | MANIFEST 61 | 62 | # PyInstaller 63 | # Usually these files are written by a python script from a template 64 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 65 | *.manifest 66 | *.spec 67 | 68 | # Installer logs 69 | pip-log.txt 70 | pip-delete-this-directory.txt 71 | 72 | # Unit test / coverage reports 73 | htmlcov/ 74 | .tox/ 75 | .nox/ 76 | .coverage 77 | .coverage.* 78 | .cache 79 | nosetests.xml 80 | coverage.xml 81 | *.cover 82 | *.py,cover 83 | .hypothesis/ 84 | .pytest_cache/ 85 | cover/ 86 | 87 | # Translations 88 | *.mo 89 | *.pot 90 | 91 | # Django stuff: 92 | *.log 93 | local_settings.py 94 | db.sqlite3 95 | db.sqlite3-journal 96 | 97 | # Flask stuff: 98 | instance/ 99 | .webassets-cache 100 | 101 | # Scrapy stuff: 102 | .scrapy 103 | 104 | # Sphinx documentation 105 | docs/_build/ 106 | 107 | # PyBuilder 108 | .pybuilder/ 109 | target/ 110 | 111 | # Jupyter Notebook 112 | .ipynb_checkpoints 113 | 114 | # IPython 115 | profile_default/ 116 | ipython_config.py 117 | 118 | # pyenv 119 | # For a library or package, you might want to ignore these files since the code is 120 | # intended to run in multiple environments; otherwise, check them in: 121 | # .python-version 122 | 123 | # pipenv 124 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 125 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 126 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 127 | # install all needed dependencies. 128 | #Pipfile.lock 129 | 130 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 131 | __pypackages__/ 132 | 133 | # Celery stuff 134 | celerybeat-schedule 135 | celerybeat.pid 136 | 137 | # SageMath parsed files 138 | *.sage.py 139 | 140 | # Environments 141 | .env 142 | .venv 143 | env/ 144 | venv/ 145 | ENV/ 146 | env.bak/ 147 | venv.bak/ 148 | 149 | # Spyder project settings 150 | .spyderproject 151 | .spyproject 152 | 153 | # Rope project settings 154 | .ropeproject 155 | 156 | # mkdocs documentation 157 | /site 158 | 159 | # mypy 160 | .mypy_cache/ 161 | .dmypy.json 162 | dmypy.json 163 | 164 | # Pyre type checker 165 | .pyre/ 166 | 167 | # pytype static type analyzer 168 | .pytype/ 169 | 170 | # Cython debug symbols 171 | cython_debug/ 172 | 173 | # End of https://www.toptal.com/developers/gitignore/api/python,macos -------------------------------------------------------------------------------- /docker-python-flask-redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.4-alpine 2 | USER root 3 | 4 | ADD . /code 5 | WORKDIR /code 6 | 7 | RUN pip install -r requirements.txt 8 | -------------------------------------------------------------------------------- /docker-python-flask-redis/README.md: -------------------------------------------------------------------------------- 1 | # python-redis 2 | 3 | python、redis が動作する環境 4 | 5 | python は docker 起動時にはアプリは起動せず docker 起動後に適時起動する(flask の想定。そのため Port5000 番を解放) 6 | 7 | ## run 8 | 9 | ``` 10 | docker-compose up -d 11 | ``` 12 | 13 | ## stop & start 14 | 15 | ``` 16 | docker-compose stop 17 | ``` 18 | 19 | ``` 20 | docker-compose start 21 | ``` 22 | 23 | ## container 内 flask 起動 24 | 25 | アプリをセーブしたら Flask に反映させるため debug,development で実行 26 | 27 | VSCode で接続し Terminal で以下を実行(フォアグランドで実行し続ける) 28 | 29 | ``` 30 | FLASK_APP=app.py FLASK_ENV=development flask run 31 | ``` 32 | 33 | bash 接続し上と同じく実行 34 | 35 | ``` 36 | docker container --it xxx bash 37 | FLASK_APP=app.py FLASK_ENV=development flask run 38 | ``` 39 | -------------------------------------------------------------------------------- /docker-python-flask-redis/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request,render_template 2 | from redis import Redis 3 | 4 | app = Flask(__name__) 5 | redis = Redis(host='redis', port=6379) 6 | 7 | @app.route('/',methods=['GET', 'POST']) 8 | def form(): 9 | count = redis.incr('hits') 10 | print(count) 11 | count = redis.decr('hits') 12 | return render_template('index.html') 13 | 14 | if __name__ == "__main__": 15 | app.run(host="0.0.0.0", debug=True) 16 | -------------------------------------------------------------------------------- /docker-python-flask-redis/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "5000:5000" 7 | tty: true 8 | volumes: 9 | - .:/code 10 | redis: 11 | image: "redis:alpine" 12 | -------------------------------------------------------------------------------- /docker-python-flask-redis/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | redis -------------------------------------------------------------------------------- /docker-python-flask-redis/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |

hello

13 |

hoge

14 | 15 | 16 | -------------------------------------------------------------------------------- /recipe-1/README.md: -------------------------------------------------------------------------------- 1 | # recipe-1 Docker-DockerCompose-Training 2 | 3 | ## 動作環境 4 | 5 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能) 6 | 7 | Node.js,npm が動作すること 8 | 9 | ``` 10 | $ node -v 11 | v14.15.4 12 | $ npm -v 13 | 7.6.0 14 | $ npx -v 15 | 7.6.0 16 | $ yarn -v 17 | 1.22.10 18 | ``` 19 | 20 | ## 公式ドキュメント 21 | 22 | [docker docs](https://docs.docker.com/) 23 | 24 | ## すること 25 | 26 | 1. Docker で環境構築 27 | 1. docker exec で接続し開発 28 | 1. VSCode から接続し開発 29 | 1. Docker の基本的な操作 30 | 1. Docker Compose で環境構築 31 | 1. Docker をマウントし開発 32 | 1. VSCode から接続し開発 33 | 1. Docker Compose の基本的な操作 34 | 35 | ## 1. Docker で環境構築 36 | 37 | ### 今回の作業ディレクトリの作成 38 | 39 | ``` 40 | $ mkdir Docker-DockerCompose-Training 41 | $ cd Docker-DockerCompose-Training 42 | ``` 43 | 44 | ### サンプルコード用ディレクトリの作成 45 | 46 | React のサンプルコードを Docker コンテナにマウントする際に利用する`app`ディレクトリを作成する 47 | 48 | ``` 49 | $ mkdir app 50 | ``` 51 | 52 | ### gitignore の作成 53 | 54 | [gitignore.io](https://www.toptal.com/developers/gitignore)で`vscode`を指定して作成する(ローカルで作成しても問題ない) 55 | 56 | `.gitignore`に以下を追記し管理対象から除外 57 | 58 | ``` 59 | + app/* 60 | ``` 61 | 62 | ### Dockerfile の作成 63 | 64 | `docker/app`ディレクトリを作成し配下に`Node.js`環境用の[`Dockerfile`](./docker/app/Dockerfile)を作成する 65 | 66 | ``` 67 | $ mkdir -p docker/app 68 | ``` 69 | 70 | ``` 71 | $ cat Dockerfile 72 | FROM node:latest 73 | 74 | ENV APP_PATH=/app 75 | RUN mkdir $APP_PATH 76 | WORKDIR $APP_PATH 77 | ``` 78 | 79 | ### Docker イメージの確認 80 | 81 | ``` 82 | $ docker images 83 | ``` 84 | 85 | ### docker build 86 | 87 | `Docker-DockerCompose-Training`から実行 88 | 89 | ``` 90 | $ docker build --file=./docker/app/Dockerfile -t sample:1 . 91 | [+] Building 39.6s (7/7) FINISHED 92 | => [internal] load build definition from Dockerfile 0.0s 93 | => => transferring dockerfile: 115B 0.0s 94 | => [internal] load .dockerignore 0.0s 95 | => => transferring context: 2B 0.0s 96 | => [internal] load metadata for docker.io/library/node:latest 4.1s 97 | => [1/3] FROM docker.io/library/node:latest@sha256:def7bb01cc33bc226e2fb94e6b820e5db9a3793e342c21d70e18ed6e3e3cc68a 34.4s 98 | => => resolve docker.io/library/node:latest@sha256:def7bb01cc33bc226e2fb94e6b820e5db9a3793e342c21d70e18ed6e3e3cc68a 0.0s 99 | => => sha256:def7bb01cc33bc226e2fb94e6b820e5db9a3793e342c21d70e18ed6e3e3cc68a 776B / 776B 0.0s 100 | => => sha256:ed78ee5153382701a4ca5d363a56b526fca073841186b70b9ed1c280dee92363 2.21kB / 2.21kB 0.0s 101 | => => sha256:56bc674036dc4e2bd2cd490939d04842c10a0c195c9184363573860c359086fc 7.83kB / 7.83kB 0.0s 102 | => => sha256:16cf3fa6cb1190b4dfd82a5319faa13e2eb6e69b7b4828d4d98ba1c0b216e446 45.38MB / 45.38MB 10.9s 103 | => => sha256:3ddd031622b35dad4be68eec6ac0787b0394f37b3dbb600a04e8b2363297b8d7 11.27MB / 11.27MB 2.4s 104 | => => sha256:69c3fcab77df556f3a56ec3d2a5b5cc304f4c4d4341b6f8423dd86ebe5ddaebb 4.34MB / 4.34MB 2.6s 105 | => => sha256:a403cc031caeb1ddbae71b9f7e47d7854415c8aa6f0b84b8e7be4b3db513867e 49.79MB / 49.79MB 6.3s 106 | => => sha256:b900c5ffbaf4b4884b18eb27cec8a890510d745d6a65ba90efe10c9cdeaaade8 214.34MB / 214.34MB 22.3s 107 | => => sha256:f877dc3acfca02604e73fb01208848941a1718b55297038decc5464f36edd649 4.19kB / 4.19kB 6.5s 108 | => => sha256:6779ae40941d54401cbb2a16e55b2a4e2dcaf635aaf5c0edc1140867660fbd78 34.05MB / 34.05MB 9.7s 109 | => => sha256:cc22e2b1bc09a54e7d1fb7dc84f037cedba749071cc5fa0f9056232054801884 2.38MB / 2.38MB 10.2s 110 | => => sha256:70e96dddd4f16fb4e8cbe8c671d40acdb485440e5346e86eca021b82d7c739e2 294B / 294B 10.4s 111 | => => extracting sha256:16cf3fa6cb1190b4dfd82a5319faa13e2eb6e69b7b4828d4d98ba1c0b216e446 2.5s 112 | => => extracting sha256:3ddd031622b35dad4be68eec6ac0787b0394f37b3dbb600a04e8b2363297b8d7 0.8s 113 | => => extracting sha256:69c3fcab77df556f3a56ec3d2a5b5cc304f4c4d4341b6f8423dd86ebe5ddaebb 0.2s 114 | => => extracting sha256:a403cc031caeb1ddbae71b9f7e47d7854415c8aa6f0b84b8e7be4b3db513867e 2.7s 115 | => => extracting sha256:b900c5ffbaf4b4884b18eb27cec8a890510d745d6a65ba90efe10c9cdeaaade8 9.3s 116 | => => extracting sha256:f877dc3acfca02604e73fb01208848941a1718b55297038decc5464f36edd649 0.1s 117 | => => extracting sha256:6779ae40941d54401cbb2a16e55b2a4e2dcaf635aaf5c0edc1140867660fbd78 1.6s 118 | => => extracting sha256:cc22e2b1bc09a54e7d1fb7dc84f037cedba749071cc5fa0f9056232054801884 0.2s 119 | => => extracting sha256:70e96dddd4f16fb4e8cbe8c671d40acdb485440e5346e86eca021b82d7c739e2 0.0s 120 | => [2/3] RUN mkdir /app 0.9s 121 | => [3/3] WORKDIR /app 0.0s 122 | => exporting to image 0.0s 123 | => => exporting layers 0.0s 124 | => => writing image sha256:22d744614838a22ae305967921f49f179fac325b2f4987a3f54511180075f7bf 0.0s 125 | => => naming to docker.io/library/sample:1 126 | ``` 127 | 128 | ### Docker イメージの確認 129 | 130 | build で作成したイメージ`sample`が存在すること 131 | 132 | ``` 133 | $ docker images 134 | REPOSITORY TAG IMAGE ID CREATED SIZE 135 | sample 1 43a2ae7ab3ee About a minute ago 936MB 136 | ``` 137 | 138 | ### Docker コンテナの確認 139 | 140 | イメージをコンテナと起動していないため何も出力されないこと 141 | 142 | ``` 143 | $ docker ps -a 144 | ``` 145 | 146 | ### 新しい Docker コンテナの起動 147 | 148 | **`pwd`がローカルで実行できない場合、フルパスを出力し書き換えて実行する** 149 | 150 | ``` 151 | $ docker run -v `pwd`/app:/app -it -d --name sample-1 -p 3000:3000 sample:1 152 | ``` 153 | 154 | ### Docker コンテナの確認 155 | 156 | **`CONTAINER ID`を今後は指定するので控えること** 157 | 158 | `NAMES`に`sample-1`が存在し`STATUS`が`UP`であること 159 | 160 | ``` 161 | $ docker ps -a 162 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 163 | 54306bc7ef99 sample:1 "docker-entrypoint.s…" 29 seconds ago Up 28 seconds 0.0.0.0:3000->3000/tcp sample-1 164 | ``` 165 | 166 | ### 1.1 docker exec で接続し開発 167 | 168 | `docker ps -a`で確認した`CONTAINER ID`を`-it`以降に指定する 169 | 170 | ``` 171 | $ docker exec -it 54306bc7ef99 bash 172 | ``` 173 | 174 | #### ファイルの作成と確認 175 | 176 | ``` 177 | # touch hoge 178 | # ls -la 179 | total 8 180 | drwxr-xr-x 2 root root 4096 Feb 8 06:20 . 181 | drwxr-xr-x 1 root root 4096 Feb 8 06:18 .. 182 | -rw-r--r-- 1 root root 0 Feb 8 06:20 hoge 183 | 184 | # exit 185 | ``` 186 | 187 | #### ファイルの確認 188 | 189 | `app`内に`hoge`が存在すれば`docker`にマウントが成功している 190 | 191 | ``` 192 | $ ls -la app 193 | total 0 194 | drwxr-xr-x 3 miurahironori staff 96 2 8 15:29 . 195 | drwxr-xr-x 4 miurahironori staff 128 2 8 15:28 .. 196 | -rw-r--r-- 1 miurahironori staff 0 2 8 15:29 hoge 197 | ``` 198 | 199 | ### 1.2 VSCode から接続し開発 200 | 201 | #### 拡張機能のインストール 202 | 203 | VSCode -> 拡張機能 -> [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)をインストール 204 | 205 | #### 接続 206 | 207 | `sample:1`の横の`+`を押下後、ディレクトリ(フォルダー)の入力を聞かれた場合は`/app`を入力 208 | ![docker-01](./images/docker-01.png) 209 | 210 | 新しいウインドウで作成した Docker 環境に接続し開発ができる状態であること 211 | ![docker-02](./images/docker-02.png) 212 | 213 | fuga と言うファイルを追加し`Docker-DockerCompose-Training`から確認 214 | 215 | ``` 216 | $ ls -la app 217 | total 0 218 | drwxr-xr-x 3 miurahironori staff 96 2 8 15:29 . 219 | drwxr-xr-x 4 miurahironori staff 128 2 8 15:28 .. 220 | -rw-r--r-- 1 miurahironori staff 0 2 8 15:29 hoge 221 | -rw-r--r-- 1 miurahironori staff 0 2 8 15:33 fuga 222 | ``` 223 | 224 | ### 1.3 Docker の基本的な操作 225 | 226 | #### Docker イメージの確認 227 | 228 | ``` 229 | $ docker images 230 | REPOSITORY TAG IMAGE ID CREATED SIZE 231 | sample 1 43a2ae7ab3ee About an hour ago 936MB 232 | node latest 96e42e8537de 2 days ago 936MB 233 | ``` 234 | 235 | #### build 236 | 237 | **build 済みなので実行しない** 238 | 239 | ~~初回は`Docker-DockerCompose-Training`から実行~~ 240 | 241 | ``` 242 | $ docker build --file=./docker/app/Dockerfile -t sample:1 . 243 | ``` 244 | 245 | #### Docker コンテナの確認 246 | 247 | ``` 248 | $ docker ps -a 249 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 250 | d621283c4a42 sample:1 "docker-entrypoint.s…" 16 minutes ago Up 16 minutes 0.0.0.0:3000->3000/tcp sample-1 251 | ``` 252 | 253 | #### 新しい Docker コンテナの起動 254 | 255 | **実行済みなので実行しない** 256 | 257 | ~~初回は`Docker-DockerCompose-Training`から実行~~ 258 | 259 | ``` 260 | $ docker run -v `pwd`/app:/app -it -d --name sample-1 -p 3000:3000 sample:1 261 | ``` 262 | 263 | #### Docker コンテナの停止 264 | 265 | `docker ps -a`で確認した`CONTAINER ID`を指定 266 | 267 | ``` 268 | $ docker stop d621283c4a42 269 | ``` 270 | 271 | #### Docker コンテナの起動 272 | 273 | `docker ps -a`で確認した`CONTAINER ID`を指定 274 | 275 | ``` 276 | $ docker start d621283c4a42 277 | ``` 278 | 279 | #### Docker コンテナの削除 280 | 281 | 停止後`docker ps -a`で確認した`CONTAINER ID`を指定 282 | 283 | ``` 284 | $ docker rm d621283c4a42 285 | ``` 286 | 287 | 確認 288 | 289 | ``` 290 | $ docker ps -a 291 | ``` 292 | 293 | #### Docker イメージの削除 294 | 295 | `docker images`で確認した`IMAGE ID`を指定 296 | 297 | ``` 298 | $ docker images 299 | REPOSITORY TAG IMAGE ID CREATED SIZE 300 | sample 1 43a2ae7ab3ee 2 hours ago 936MB 301 | 302 | $ docker rmi 43a2ae7ab3ee 303 | ``` 304 | 305 | ### 掃除 306 | 307 | `app`内を掃除 308 | 309 | ``` 310 | $ rm app/* 311 | ``` 312 | 313 | コンテナから開いていた VSCode も閉じる 314 | 315 | ## 2. Docker Compose で環境構築 316 | 317 | ### React サンプルコードの準備 318 | 319 | ローカルの`Docker-DockerCompose-Training`直下から実行 320 | 321 | ``` 322 | $ cd app 323 | $ npx create-react-app . 324 | ``` 325 | 326 | ### docker-compose.yml の作成 327 | 328 | `Docker-DockerCompose-Training` 直下に[docker-compose.yml](./docker-compose.yml)を作成 329 | 330 | ``` 331 | $ cat docker-compose.yml 332 | version: "3" 333 | services: 334 | app: 335 | build: docker/app 336 | tty: true 337 | volumes: 338 | - ./app:/app 339 | ports: 340 | - "3000:3000" 341 | user: node 342 | command: yarn start 343 | ``` 344 | 345 | ### docker-compose up 346 | 347 | `Docker-DockerCompose-Training` 直下で実行 348 | 349 | ``` 350 | $ docker-compose up 351 | ``` 352 | 353 | `Compiled successfully!`となること 354 | ![docker-compose-01](./images/docker-compose-01.png) 355 | 356 | ブラウザに`http://localhost:3000`を入力しサンプルコードが表示されること(確認後、Ctrl+C で停止できる、ただし今はこのまま動作させ続けること) 357 | 358 | ![docker-compose-02](./images/docker-compose-02.png) 359 | 360 | ### 2.1 Docker にマウントした app 配下の React のサンプルコードを用い開発 361 | 362 | `app/src/index.js`,`app/src/App.js`を編集し`hello`と表示されることを確認しましょう 363 | 364 | `index.js` 365 | 366 | ``` 367 | import React from "react" 368 | import ReactDOM from "react-dom" 369 | import App from "./App" 370 | 371 | ReactDOM.render( 372 | 373 | 374 | , 375 | document.getElementById("root") 376 | ) 377 | ``` 378 | 379 | `App.js` 380 | 381 | ``` 382 | import React from 'react' 383 | 384 | const App = () => { 385 | return ( 386 | <> 387 | hello 388 | 389 | ); 390 | } 391 | 392 | export default App; 393 | ``` 394 | 395 | 表示が変わらない場合は`docker-compose stop`後に`docker-compose start`をし確認する(少し時間が掛かる) 396 | 397 | ### 2.2 VSCode から接続し開発 398 | 399 | #### 接続 400 | 401 | リモートエクスプローラー -> 今回作成した Docker-Compose の環境を選択 402 | 403 | ![docker-compose-03](./images/docker-compose-03.png) 404 | 405 | フォルダを押下 406 | ![docker-compose-04](./images/docker-compose-04.png) 407 | 408 | `app`を指定し`OK`を押下(2 回) 409 | ![docker-compose-05](./images/docker-compose-05.png) 410 | 411 | #### 開発 412 | 413 | `app/src/App.js`を編集し`docker compose hello`と表示されることを確認しましょう 414 | 415 | ### 2.3 Docker Compose の基本的な操作 416 | 417 | #### 起動 418 | 419 | フォアグランドで起動(停止は Ctrl+C) 420 | 421 | ``` 422 | $ docker-compose up 423 | ``` 424 | 425 | バックグラウンドで起動 426 | 427 | ``` 428 | $ docker-compose up 429 | ``` 430 | 431 | #### 起動 432 | 433 | ``` 434 | $ docker-compose start 435 | ``` 436 | 437 | #### 停止 438 | 439 | ``` 440 | $ docker-compose stop 441 | ``` 442 | 443 | #### 確認 444 | 445 | ``` 446 | $ docker-compose ps 447 | Name Command State Ports 448 | ----------------------------------------------------------------------------------------------------- 449 | docker-dockercompose-training_app_1 docker-entrypoint.sh yarn ... Up 0.0.0.0:3000->3000/tcp 450 | ``` 451 | -------------------------------------------------------------------------------- /recipe-1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | build: docker/app 5 | tty: true 6 | volumes: 7 | - ./app:/app 8 | ports: 9 | - "3000:3000" 10 | user: node 11 | command: yarn start 12 | -------------------------------------------------------------------------------- /recipe-1/docker/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH -------------------------------------------------------------------------------- /recipe-1/images/docker-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-01.png -------------------------------------------------------------------------------- /recipe-1/images/docker-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-02.png -------------------------------------------------------------------------------- /recipe-1/images/docker-compose-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-01.png -------------------------------------------------------------------------------- /recipe-1/images/docker-compose-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-02.png -------------------------------------------------------------------------------- /recipe-1/images/docker-compose-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-03.png -------------------------------------------------------------------------------- /recipe-1/images/docker-compose-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-04.png -------------------------------------------------------------------------------- /recipe-1/images/docker-compose-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-05.png -------------------------------------------------------------------------------- /recipe-1/images/docker-compose-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-06.png -------------------------------------------------------------------------------- /recipe-2-0/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | react-app: 4 | build: docker/react-app 5 | tty: true 6 | volumes: 7 | - react-app:/react-app 8 | ports: 9 | - "3000:3000" 10 | user: root 11 | 12 | express-app: 13 | build: docker/express-app 14 | tty: true 15 | volumes: 16 | - express-app:/express-app 17 | ports: 18 | - "5000:5000" 19 | user: root 20 | volumes: 21 | react-app: 22 | express-app: 23 | -------------------------------------------------------------------------------- /recipe-2-0/docker/express-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/express-app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH 6 | 7 | RUN npm init -y 8 | RUN npm install -y express cors pug -------------------------------------------------------------------------------- /recipe-2-0/docker/react-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/react-app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH 6 | 7 | RUN npx create-react-app . 8 | 9 | -------------------------------------------------------------------------------- /recipe-2-0/images/docker-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-01.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-02.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-03.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-04.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-05.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-06.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-07.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-08.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-09.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-10.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-compose-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-01.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-compose-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-02.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-compose-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-03.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-compose-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-04.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-compose-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-05.png -------------------------------------------------------------------------------- /recipe-2-0/images/docker-compose-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-06.png -------------------------------------------------------------------------------- /recipe-2-0/sample/express-app/example.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const cors = require('cors') 3 | const http = require('http') 4 | 5 | const app = express() 6 | const server = http.createServer(app) 7 | 8 | app.set('view engine', 'pug') 9 | 10 | app.use( 11 | express.urlencoded({ 12 | extended: true, 13 | }) 14 | ) 15 | app.use(express.json()) 16 | 17 | app.use( 18 | cors({ 19 | origin: 'http://localhost:3000', 20 | credentials: true, 21 | optionsSuccessStatus: 200, 22 | }) 23 | ) 24 | 25 | app.get('/', (req, res) => { 26 | res.render('index', { 27 | title: 'Hey', 28 | message: 'Hello there!', 29 | }) 30 | }) 31 | 32 | app.get('/api/users', (req, res) => { 33 | res.json([{ name: 'Bob' }, { name: '花子' }, { name: '太郎' }]) 34 | }) 35 | 36 | app.get('/api/users/:id', (req, res) => { 37 | res.json({ name: `No.${req.params.id} is Bob` }) 38 | }) 39 | 40 | app.post('/api/users', (req, res) => { 41 | res.json({ 42 | message: 'POST ' + req.body.name + ' Successful', 43 | }) 44 | }) 45 | 46 | server.listen(5000, () => { 47 | console.log('listening on *:5000') 48 | }) 49 | -------------------------------------------------------------------------------- /recipe-2-0/sample/express-app/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const cors = require('cors') 3 | const http = require('http') 4 | 5 | const app = express() 6 | const server = http.createServer(app) 7 | 8 | app.set('view engine', 'pug') 9 | 10 | app.use( 11 | cors({ 12 | origin: 'http://localhost:3000', 13 | credentials: true, 14 | optionsSuccessStatus: 200, 15 | }) 16 | ) 17 | 18 | app.get('/', (req, res) => { 19 | res.render('index', { 20 | title: 'Hey', 21 | message: 'Hello there!', 22 | }) 23 | }) 24 | 25 | app.get('/api/users', (req, res) => { 26 | res.json([{ name: 'Bob' }, { name: '花子' }, { name: '太郎' }]) 27 | }) 28 | 29 | app.get('/api/users/:id', (req, res) => { 30 | res.json({ name: `No.${req.params.id} is Bob` }) 31 | }) 32 | 33 | server.listen(5000, () => { 34 | console.log('listening on *:5000') 35 | }) 36 | -------------------------------------------------------------------------------- /recipe-2-0/sample/express-app/views/index.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title= title 4 | body 5 | h1= message -------------------------------------------------------------------------------- /recipe-2-0/sample/react-app/Index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import App from "./components/App" 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById("root") 10 | ) 11 | -------------------------------------------------------------------------------- /recipe-2-0/sample/react-app/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | 3 | const App = () => { 4 | const [users, setUsers] = useState([{ name: '一郎' }, { name: 'Mike' }]) 5 | useEffect(() => { 6 | fetch('http://localhost:5000/api/users') 7 | .then((res) => res.json()) 8 | .then((data) => setUsers([...data])) 9 | }) 10 | return ( 11 |
12 | {users.map((user, index) => ( 13 |
{user.name}
14 | ))} 15 |
16 | ) 17 | } 18 | 19 | export default App 20 | -------------------------------------------------------------------------------- /recipe-2-0/sample/react-app/components/Example.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | 3 | const App = () => { 4 | const [name, setName] = useState('hoge') 5 | const [users, setUsers] = useState([]) 6 | useEffect(() => { 7 | fetch('http://localhost:5000/api/users') 8 | .then((res) => res.json()) 9 | .then((data) => setUsers([...data])) 10 | }, []) 11 | return ( 12 |
13 | {users.map((user, index) => ( 14 |
{user.name}
15 | ))} 16 | setName(e.target.value)} 20 | /> 21 | 44 |
45 | ) 46 | } 47 | 48 | export default App 49 | -------------------------------------------------------------------------------- /recipe-2-1/README.md: -------------------------------------------------------------------------------- 1 | # recipe-2.1 Docker-React-Express-MySQL 2 | 3 | recipe-2-0 に MySQL を追加、express-app を router で書き直し 4 | 5 | ## 前提 6 | 7 | recipe-2-0 に追加する形で実装(`Docker-React-Express`ディレクトリ配下で実装する) 8 | 9 | ## MySQL 10 | 11 | `docker`配下に`mysql`ディレクトリを作成し Dockerfile を配置する 12 | 13 | ``` 14 | FROM mysql:latest 15 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf 16 | ``` 17 | 18 | `docker/mysql`配下に`conf.d`ディレクトリを作成し my.cnf を配置する 19 | 20 | ``` 21 | [mysqld] 22 | innodb-buffer-pool-size=128M 23 | default-authentication-plugin=mysql_native_password 24 | ``` 25 | 26 | ## express-app/Dockerfile 27 | 28 | `mysql2`のインストールを追記する 29 | 30 | ``` 31 | FROM node:latest 32 | 33 | ENV APP_PATH=/express-app 34 | RUN mkdir $APP_PATH 35 | WORKDIR $APP_PATH 36 | 37 | RUN npm init -y 38 | RUN npm install -y express cors mysql2 pug 39 | ``` 40 | 41 | ## docker-compose.yml 42 | 43 | MySQL の設定を追加しサーバサイド(`express-ap`)から参照できるよう追記する 44 | 45 | ``` 46 | version: '3' 47 | services: 48 | react-app: 49 | build: docker/react-app 50 | tty: true 51 | volumes: 52 | - react-app:/react-app 53 | ports: 54 | - '3000:3000' 55 | user: root 56 | express-app: 57 | build: docker/express-app 58 | tty: true 59 | volumes: 60 | - express-app:/express-app 61 | ports: 62 | - '5000:5000' 63 | user: root 64 | depends_on: 65 | - db 66 | db: 67 | build: docker/mysql 68 | tty: true 69 | volumes: 70 | - mysql:/var/lib/mysql 71 | environment: 72 | MYSQL_DATABASE: test 73 | MYSQL_ROOT_PASSWORD: mysql 74 | MYSQL_USER: appuser 75 | MYSQL_PASSWORD: mysql 76 | expose: 77 | - '3306' 78 | ports: 79 | - '3307:3306' 80 | volumes: 81 | react-app: 82 | express-app: 83 | mysql: 84 | ``` 85 | 86 | ## run 87 | 88 | ``` 89 | $ docker-compose up --build -d 90 | ``` 91 | 92 | ## express-app 93 | 94 | VSCode から接続しサーバ(`index.js`)を起動する 95 | 96 | ``` 97 | node index.js 98 | ``` 99 | 100 | ## react-app 101 | 102 | VSCode から接続しアプリを起動する 103 | 104 | ``` 105 | yarn start 106 | ``` 107 | 108 | ## express-app の修正 109 | 110 | `index.js`を MySQL に接続しレコードを取得しレスポンスで返すよう修正する 111 | 112 | ``` 113 | import express from 'express' 114 | import http from 'http' 115 | import cors from 'cors' 116 | import { promisePool } from './config/db.js' 117 | import { ORIGIN_URL } from './config/index.js' 118 | import users from './router/api/users.js' 119 | 120 | const app = express() 121 | const server = http.createServer(app) 122 | 123 | app.set('view engine', 'pug') 124 | 125 | app.use('/static', express.static('static')) 126 | 127 | app.use( 128 | cors({ 129 | origin: ORIGIN_URL, 130 | credentials: true, 131 | optionsSuccessStatus: 200, 132 | }) 133 | ) 134 | 135 | app.use( 136 | express.urlencoded({ 137 | extended: true, 138 | }) 139 | ) 140 | 141 | app.use(express.json()) 142 | 143 | app.use('/api/users', users) 144 | app.use('/api/users/:id', users) 145 | 146 | app.get('/', async (req, res) => { 147 | const [rows, fields] = await promisePool.query('select 1 as num') 148 | res.render('index', { 149 | title: 'Hey', 150 | message: `Hello there!num is ${rows[0].num}`, 151 | }) 152 | }) 153 | 154 | server.listen(5000, () => { 155 | console.log('listening 5000') 156 | }) 157 | ``` 158 | 159 | `express-app`配下に`router/api`ディレクトリを作成し`user.js`を配置する 160 | 161 | ``` 162 | import express from 'express' 163 | import { promisePool } from '../../config/db.js' 164 | 165 | const users = express.Router() 166 | 167 | users 168 | .route('/') 169 | .get(async (req, res) => { 170 | const [rows] = await promisePool.query('select id,name from users') 171 | res.json(rows) 172 | }) 173 | .post(async (req, res) => { 174 | const ret = await promisePool.query('insert into users(name) values(?)', [ 175 | req.body.name, 176 | ]) 177 | console.log(ret[0].insertId) 178 | res.json({ message: 'ok', insertId: ret[0].insertId }) 179 | }) 180 | 181 | users 182 | .route('/:id') 183 | .get(async (req, res) => { 184 | const [rows] = await promisePool.query( 185 | 'select name from users where id = ?', 186 | [req.params.id] 187 | ) 188 | res.json({ name: rows[0].name }) 189 | }) 190 | .post(async (req, res) => { 191 | const ret = await promisePool.query( 192 | 'update users set name = ? where id = ?', 193 | [req.body.name, req.params.id] 194 | ) 195 | // console.log(ret[0]) 196 | res.json({ message: ret[0].info, insertId: ret[0].insertId }) 197 | }) 198 | 199 | export default users 200 | ``` 201 | 202 | `express-app`配下に`config`ディレクトリを作成し`db.js`,`index.js`を配置する 203 | 204 | `db.js` 205 | 206 | ``` 207 | import mysql from 'mysql2' 208 | 209 | const pool = mysql.createPool({ 210 | connectionLimit: 5, 211 | host: 'db', 212 | user: 'appuser', 213 | password: 'mysql', 214 | database: 'test', 215 | }) 216 | 217 | export const promisePool = pool.promise() 218 | ``` 219 | 220 | `index.js` 221 | 222 | ``` 223 | export const ORIGIN_URL = 'http://localhost:3000' 224 | ``` 225 | 226 | ES6 で実装するため`package.json`に`"type": "module",`を`scripts`以下に追記する 227 | 228 | ``` 229 | "scripts": { 230 | "test": "echo \"Error: no test specified\" && exit 1" 231 | }, 232 | "type": "module", 233 | ``` 234 | 235 | ## MySQL 236 | 237 | MySQL に接続し`test`DB に api 用のテーブルを作成する 238 | 239 | ``` 240 | mysql -u root -p --port=3307 -h127.0.0.1 241 | 242 | mysql> use test 243 | 244 | ``` 245 | 246 | 以下を実行 247 | 248 | ``` 249 | create table users ( 250 | id int auto_increment not null, 251 | name varchar(10) not null , 252 | primary key(id) 253 | ); 254 | ``` 255 | 256 | サンプルデータを insert する 257 | 258 | ``` 259 | insert into users(name) values('太郎'),('John'),('花子'); 260 | ``` 261 | 262 | exit 263 | 264 | ``` 265 | mysql> exit 266 | ``` 267 | 268 | ## サーバの再起動 269 | 270 | express-app の`index.js`が起動している場合 CTRL + C で停止し再度起動する 271 | 272 | ``` 273 | node index.js 274 | ``` 275 | -------------------------------------------------------------------------------- /recipe-2-1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | react-app: 4 | build: docker/react-app 5 | tty: true 6 | volumes: 7 | - react-app:/react-app 8 | ports: 9 | - '3000:3000' 10 | user: root 11 | express-app: 12 | build: docker/express-app 13 | tty: true 14 | volumes: 15 | - express-app:/express-app 16 | ports: 17 | - '5000:5000' 18 | user: root 19 | depends_on: 20 | - db 21 | db: 22 | build: docker/mysql 23 | tty: true 24 | volumes: 25 | - mysql:/var/lib/mysql 26 | environment: 27 | MYSQL_DATABASE: test 28 | MYSQL_ROOT_PASSWORD: mysql 29 | MYSQL_USER: appuser 30 | MYSQL_PASSWORD: mysql 31 | expose: 32 | - '3306' 33 | ports: 34 | - '3307:3306' 35 | volumes: 36 | react-app: 37 | express-app: 38 | mysql: 39 | -------------------------------------------------------------------------------- /recipe-2-1/docker/express-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/express-app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH 6 | 7 | RUN npm init -y 8 | RUN npm install -y express cors mysql2 pug -------------------------------------------------------------------------------- /recipe-2-1/docker/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:latest 2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf -------------------------------------------------------------------------------- /recipe-2-1/docker/mysql/conf.d/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | innodb-buffer-pool-size=128M 3 | default-authentication-plugin=mysql_native_password -------------------------------------------------------------------------------- /recipe-2-1/docker/react-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/react-app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH 6 | 7 | RUN npx create-react-app . -------------------------------------------------------------------------------- /recipe-2-1/sample/express-app/config/db.js: -------------------------------------------------------------------------------- 1 | import mysql from 'mysql2' 2 | 3 | const pool = mysql.createPool({ 4 | connectionLimit: 5, 5 | host: 'db', 6 | user: 'appuser', 7 | password: 'mysql', 8 | database: 'test', 9 | }) 10 | 11 | export const promisePool = pool.promise() 12 | -------------------------------------------------------------------------------- /recipe-2-1/sample/express-app/config/index.js: -------------------------------------------------------------------------------- 1 | export const ORIGIN_URL = 'http://localhost:3000' 2 | -------------------------------------------------------------------------------- /recipe-2-1/sample/express-app/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import http from 'http' 3 | import cors from 'cors' 4 | import { promisePool } from './config/db.js' 5 | import { ORIGIN_URL } from './config/index.js' 6 | import users from './router/api/users.js' 7 | 8 | const app = express() 9 | const server = http.createServer(app) 10 | 11 | app.set('view engine', 'pug') 12 | 13 | app.use('/static', express.static('static')) 14 | 15 | app.use( 16 | cors({ 17 | origin: ORIGIN_URL, 18 | credentials: true, 19 | optionsSuccessStatus: 200, 20 | }) 21 | ) 22 | 23 | app.use( 24 | express.urlencoded({ 25 | extended: true, 26 | }) 27 | ) 28 | 29 | app.use(express.json()) 30 | 31 | app.use('/api/users', users) 32 | app.use('/api/users/:id', users) 33 | 34 | app.get('/', async (_, res) => { 35 | const [rows, fields] = await promisePool.query('select 1 as num') 36 | res.render('index', { 37 | title: 'Hey', 38 | message: `Hello there!num is ${rows[0].num}`, 39 | }) 40 | }) 41 | 42 | server.listen(5000, () => { 43 | console.log('listening on *:5000') 44 | }) 45 | -------------------------------------------------------------------------------- /recipe-2-1/sample/express-app/router/api/users.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { promisePool } from '../../config/db.js' 3 | 4 | const users = express.Router() 5 | 6 | users 7 | .route('/') 8 | .get(async (req, res) => { 9 | const [rows] = await promisePool.query('select id,name from users') 10 | res.json(rows) 11 | }) 12 | .post(async (req, res) => { 13 | const ret = await promisePool.query('insert into users(name) values(?)', [ 14 | req.body.name, 15 | ]) 16 | console.log(ret[0].insertId) 17 | res.json({ message: 'ok', insertId: ret[0].insertId }) 18 | }) 19 | 20 | users 21 | .route('/:id') 22 | .get(async (req, res) => { 23 | const [rows] = await promisePool.query( 24 | 'select name from users where id = ?', 25 | [req.params.id] 26 | ) 27 | res.json({ name: rows[0].name }) 28 | }) 29 | .post(async (req, res) => { 30 | const ret = await promisePool.query( 31 | 'update users set name = ? where id = ?', 32 | [req.body.name, req.params.id] 33 | ) 34 | // console.log(ret[0]) 35 | res.json({ message: ret[0].info, insertId: ret[0].insertId }) 36 | }) 37 | 38 | export default users 39 | -------------------------------------------------------------------------------- /recipe-2-1/sample/express-app/views/index.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title= title 4 | body 5 | h1= message 6 | -------------------------------------------------------------------------------- /recipe-2-1/sample/react-app/Index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import App from "./components/App" 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById("root") 10 | ) 11 | -------------------------------------------------------------------------------- /recipe-2-1/sample/react-app/components/App.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | const API_URL = 'http://localhost:5000/api/users' 4 | 5 | const userEntry = async (name, setUsers, setName) => { 6 | const bodyData = JSON.stringify({ name: name }) 7 | 8 | const res = await fetch(API_URL, { 9 | method: 'POST', 10 | mode: 'cors', 11 | cache: 'no-cache', 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | }, 15 | body: bodyData, 16 | }) 17 | const data = await res.json() 18 | 19 | setUsers((users) => [...users, { id: data.insertId, name: name }]) 20 | setName('') 21 | } 22 | 23 | const getUsers = async (setUsers) => { 24 | const res = await fetch(API_URL) 25 | const json = await res.json() 26 | setUsers([...json]) 27 | } 28 | 29 | const App = () => { 30 | const [name, setName] = useState('bob') 31 | const [users, setUsers] = useState([]) 32 | const [buttonIs, setButtonIs] = useState(true) 33 | 34 | useEffect(() => { 35 | getUsers(setUsers) 36 | }, []) 37 | 38 | useEffect(() => { 39 | if (name.length !== 0) { 40 | setButtonIs(false) 41 | } else { 42 | setButtonIs(true) 43 | } 44 | }, [name]) 45 | 46 | return ( 47 |
48 | {users.map((user, index) => ( 49 |
50 | {user.id}:{user.name} 51 |
52 | ))} 53 | { 56 | setName(e.target.value) 57 | if (name.length !== 0) { 58 | setButtonIs(false) 59 | } 60 | }} 61 | value={name} 62 | /> 63 | 72 |
73 | ) 74 | } 75 | 76 | export default App 77 | -------------------------------------------------------------------------------- /recipe-2-2/README.md: -------------------------------------------------------------------------------- 1 | # recipe-2-2 Docker-React-Express-MySQL-JWT-COOKIE-CSRF 2 | 3 | recipe-2-1 に サーバサイド jwt,cookie,csrf,validator を追加、フロント redux-toolkit tailwind を追加 4 | 5 | ## 前提 6 | 7 | recipe-2-0,recipe-2-1 に追加だが、`create-react-app`のテンプレートを変更するため新規で作成する(`Docker-React-Express`ディレクトリ配下で実装する) 8 | 9 | ``` 10 | mkdir Docker-React-Express 11 | cd Docker-React-Express 12 | ``` 13 | 14 | ## docker/express-app/Dockerfile 15 | 16 | `Dockerfile`に`jsonwebtoken`,`cookie-parser`,`csurf`,`bcrypt`,`express-validator`を追記 17 | 18 | ``` 19 | FROM node:latest 20 | 21 | ENV APP_PATH=/express-app 22 | RUN mkdir $APP_PATH 23 | WORKDIR $APP_PATH 24 | 25 | RUN npm init -y 26 | RUN npm install -y express cors mysql2 pug jsonwebtoken cookie-parser csurf bcrypt express-validator 27 | 28 | ``` 29 | 30 | ## docker/react-app/Dockerfile 31 | 32 | recipe-2-1 から テンプレートを redux-toolkit に変更し使うため template redux の利用に Dockerfile を修正(`yarn start`で`node:latest`だとエラーになる場合`FROM node:16.0`を指定し build し直す)、Tailwind 周りのインストールを追加 33 | 34 | ``` 35 | FROM node:latest 36 | 37 | ENV APP_PATH=/react-app 38 | RUN mkdir $APP_PATH 39 | WORKDIR $APP_PATH 40 | 41 | RUN npx create-react-app . --template redux 42 | RUN yarn add @headlessui/react 43 | RUN yarn add @heroicons/react 44 | RUN yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 45 | RUN yarn add @craco/craco 46 | ``` 47 | 48 | ## docker/mysql/Dockerfile 49 | 50 | recipe-2-1 と同じ 51 | 52 | ## docker/mysql/conf.d/my.cnf 53 | 54 | recipe-2-1 と同じ 55 | 56 | ## docker-compose.yml 57 | 58 | recipe-2-1 と同じ 59 | 60 | ## up 61 | 62 | ``` 63 | $ docker-compose up --build -d 64 | ``` 65 | 66 | `jwt.config.js`の`secret`は以下で作成 67 | 68 | ``` 69 | $ node -e "console.log(require('jsonwebtoken').sign({username:'hoge'},'my_secret'))" 70 | ``` 71 | 72 | ## MySQL 73 | 74 | recipe-2-1 と以下同じ(email,password を追加) 75 | 76 | MySQL に接続し`test`DB に api 用のテーブルを作成する 77 | 78 | ``` 79 | mysql -u root -p --port=3307 -h127.0.0.1 80 | 81 | mysql> use test 82 | 83 | ``` 84 | 85 | `users`テーブルの削除&作成 86 | 87 | ``` 88 | drop table users; 89 | create table users ( 90 | id int auto_increment not null, 91 | name varchar(100) not null , 92 | email varchar(100) not null, 93 | password varchar(100) not null, 94 | primary key(id), 95 | unique (email) 96 | ); 97 | 98 | drop table task_status; 99 | create table task_status ( 100 | id int auto_increment not null, 101 | name varchar(100) not null, 102 | primary key(id), 103 | unique (name) 104 | ); 105 | 106 | drop table tasks; 107 | create table tasks ( 108 | id int auto_increment not null, 109 | title varchar(100) not null, 110 | task text not null, 111 | status int not null, 112 | user_id int not null, 113 | primary key(id), 114 | FOREIGN KEY (status) REFERENCES task_status (id) 115 | ); 116 | 117 | ``` 118 | 119 | `users`,`task_status`,`tasks`テーブルのサンプルデータを insert (`bcrypt` で hash 化してある、パスワードは全て`abcd`) 120 | 121 | ``` 122 | truncate table users; 123 | insert into users(id,name,email,password) values 124 | (1,'太郎','taro@example.com','$2b$10$iFCxa4wOsuZhklYp00bnCuk0sBJxGOU.e4YnfqvoDEyIk1C1rrd0K'), 125 | (2,'John','john@example.com','$2b$10$8W1a6GfBsmn/gY8jhXjGbOCQwcWfF/PeI5O07ONakuhX9bYIZNe82'), 126 | (3,'花子','hanako@example.com','$2b$10$TXCGCYDpn6p35Csz5UyoA.UHJ9SkE3Q7JP6lRO9ZgMaXuNwEo.wWW'); 127 | 128 | truncate table task_status; 129 | insert into task_status(id,name) values 130 | (1,'未着手'),(2,'着手中'),(3,'完了'); 131 | 132 | truncate table tasks; 133 | insert into tasks(id,title,task,status,user_id) values 134 | (1,'太郎 タスク1','タスク1の詳細',2,1), 135 | (2,'太郎 タスク2','タスク2の詳細',1,1), 136 | (3,'太郎 タスク3','タスク3の詳細',3,1), 137 | (4,'太郎 タスク3','タスク3の詳細',1,1), 138 | (5,'John タスク1','タスク1の詳細',1,2), 139 | (6,'John タスク2','タスク2の詳細',1,2); 140 | 141 | 142 | ``` 143 | 144 | exit 145 | 146 | ``` 147 | mysql> exit 148 | ``` 149 | 150 | ## express-app アプリ 151 | 152 | [sample/espress-app](./sample/express-app)以下を`express-app`に記述 153 | 154 | ### Memo 155 | 156 | ``` 157 | mkdir api config middlewares views 158 | touch api/csrfToken.js api/login.js api/logout.js api/users.js api/tasks.js 159 | touch config/db.js config/index.js config/jwt.config.js 160 | touch middlewares/validator.js middlewares/verifyToken.js 161 | touch views/index.pug 162 | touch index.js 163 | ``` 164 | 165 | ### Run 166 | 167 | ``` 168 | node index.js 169 | ``` 170 | 171 | ## react-app アプリ 172 | 173 | `package.json`の scripts を`craco`で構成する 174 | 175 | before 176 | 177 | ``` 178 | "start": "react-scripts start", 179 | "build": "react-scripts build", 180 | "test": "react-scripts test", 181 | ``` 182 | 183 | after(eject は削除) 184 | 185 | ``` 186 | "start": "craco start", 187 | "build": "craco build", 188 | "test": "craco test" 189 | ``` 190 | 191 | `craco.config.js`を作成(touch ではなく VSCode からファイル作成でも良い) 192 | 193 | ``` 194 | touch craco.config.js 195 | ``` 196 | 197 | 作成した`craco.config.js`に以下を記述 198 | 199 | ``` 200 | module.exports = { 201 | style: { 202 | postcss: { 203 | plugins: [require('tailwindcss'), require('autoprefixer')], 204 | }, 205 | }, 206 | } 207 | ``` 208 | 209 | tailwind の初期化 210 | 211 | ``` 212 | npx tailwindcss init -p 213 | ``` 214 | 215 | `tailwind.config.js`の purge を修正 216 | 217 | ``` 218 | - purge: [], 219 | 220 | + purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], 221 | ``` 222 | 223 | `./src/index.css` を tailwind を利用する設定に修正(以下の 3 行に全てを書き換え) 224 | 225 | ``` 226 | @tailwind base; 227 | @tailwind components; 228 | @tailwind utilities; 229 | ``` 230 | 231 | [sample/react-app](./sample/react-app)以下を`react-app`に記述 232 | 233 | ### Run 234 | 235 | ``` 236 | yarn start 237 | ``` 238 | 239 | ### Memo 240 | 241 | ``` 242 | mkdir src/app src/components src/config src/features/auth src/features/tasks 243 | touch src/app/store.js src/components/Footer.js src/components/Header.js src/components/Layout.js src/components/Login.js src/components/Main.js src/components/Modal.js src/components/SignUp.js src/components/Task.js src/components/TaskDeleteModal.js src/components/TaskInsertModal.js src/components/TaskUpdateModal.js src/config/index.js src/features/auth/authSlice.js src/features/tasks/tasksSlice.js 244 | ``` 245 | 246 | ## 掃除 247 | 248 | ``` 249 | docker-compose stop 250 | docker-compose rm 251 | 252 | docker image rm docker-react-express_react-app 253 | docker image rm docker-react-express_express-app 254 | docker image rm docker-react-express_db 255 | docker volume rm docker-react-express_react-app 256 | docker volume rm docker-react-express_express-app 257 | docker volume rm docker-react-express_mysql 258 | ``` 259 | -------------------------------------------------------------------------------- /recipe-2-2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | react-app: 4 | build: docker/react-app 5 | tty: true 6 | volumes: 7 | - react-app:/react-app 8 | ports: 9 | - '3000:3000' 10 | user: root 11 | express-app: 12 | build: docker/express-app 13 | tty: true 14 | volumes: 15 | - express-app:/express-app 16 | ports: 17 | - '5000:5000' 18 | user: root 19 | depends_on: 20 | - db 21 | db: 22 | build: docker/mysql 23 | tty: true 24 | volumes: 25 | - mysql:/var/lib/mysql 26 | environment: 27 | MYSQL_DATABASE: test 28 | MYSQL_ROOT_PASSWORD: mysql 29 | MYSQL_USER: appuser 30 | MYSQL_PASSWORD: mysql 31 | expose: 32 | - '3306' 33 | ports: 34 | - '3307:3306' 35 | volumes: 36 | react-app: 37 | express-app: 38 | mysql: 39 | -------------------------------------------------------------------------------- /recipe-2-2/docker/express-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/express-app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH 6 | 7 | RUN npm init -y 8 | RUN npm install -y express cors mysql2 pug jsonwebtoken cookie-parser csurf bcrypt express-validator dotenv -------------------------------------------------------------------------------- /recipe-2-2/docker/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:latest 2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf -------------------------------------------------------------------------------- /recipe-2-2/docker/mysql/conf.d/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | innodb-buffer-pool-size=128M 3 | default-authentication-plugin=mysql_native_password -------------------------------------------------------------------------------- /recipe-2-2/docker/react-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV APP_PATH=/react-app 4 | RUN mkdir $APP_PATH 5 | WORKDIR $APP_PATH 6 | 7 | RUN npx create-react-app . --template redux 8 | RUN yarn add @headlessui/react 9 | RUN yarn add @heroicons/react 10 | RUN yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 11 | RUN yarn add @craco/craco -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/api/csrfToken.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | 3 | router.get('/', (req, res) => { 4 | const csrfToken = req.csrfToken() 5 | res.json({ csrfToken: csrfToken }) 6 | }) 7 | 8 | module.exports = router 9 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/api/login.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const jwt = require('jsonwebtoken') 3 | const config = require('../config/jwt.config') 4 | const promisePool = require('../config/db.js') 5 | const bcrypt = require('bcrypt') 6 | const { 7 | validator, 8 | checkEmailIsEmpty, 9 | checkEmailIsEmail, 10 | checkPasswordIsEmpty, 11 | } = require('../middlewares/validator') 12 | const isNotEmailPassWordErrorMessage = { 13 | isSuccess: false, 14 | message: 'ユーザーIDまたはパスワードが違います。', 15 | } 16 | const authErrorMessage = { 17 | isSuccess: false, 18 | message: 'トークンの認証に失敗しました。', 19 | } 20 | const isNotTokenErrorMessage = { 21 | isSuccess: false, 22 | message: 'トークンがありません。', 23 | } 24 | 25 | router 26 | .route('/') 27 | .get((req, res) => { 28 | const authHeader = req.headers.authorization 29 | const token = req.cookies.token 30 | ? req.cookies.token 31 | : authHeader && authHeader.split(' ')[1] 32 | if (token) { 33 | jwt.verify(token, config.jwt.secret, (error, _) => { 34 | if (error) { 35 | console.log('authErrorMessage') 36 | return res.status(200).send(authErrorMessage) 37 | } 38 | console.log('success') 39 | return res.status(200).send({ 40 | isSuccess: true, 41 | message: 'success', 42 | token: token, 43 | }) 44 | }) 45 | } else { 46 | console.log(isNotTokenErrorMessage) 47 | return res.status(200).send(isNotTokenErrorMessage) 48 | } 49 | }) 50 | .post( 51 | [checkEmailIsEmpty, checkEmailIsEmail, checkPasswordIsEmpty], 52 | validator, 53 | async (req, res) => { 54 | console.log(req.body) 55 | try { 56 | const [rows, fields] = await promisePool.query( 57 | 'select id,name,email,password from users where email = ?', 58 | [req.body.email] 59 | ) 60 | 61 | if (rows.length === 0) return res.json(isNotEmailPassWordErrorMessage) 62 | 63 | const ret = await new Promise((resolve) => 64 | bcrypt.compare( 65 | req.body.password, 66 | rows[0].password.toString(), 67 | (err, isValid) => resolve(isValid) 68 | ) 69 | ) 70 | 71 | if (req.body.email === rows[0].email && ret) { 72 | const payload = { 73 | id: rows[0].id, 74 | email: req.body.email, 75 | } 76 | const token = jwt.sign(payload, config.jwt.secret, config.jwt.options) 77 | res.cookie('token', token, { 78 | httpOnly: true, 79 | domain: 'localhost', 80 | path: '/', 81 | sameSite: 'none', 82 | secure: true, 83 | }) 84 | 85 | return res.json({ 86 | isSuccess: true, 87 | token: token, 88 | }) 89 | } 90 | } catch (err) { 91 | return res.json({ isSuccess: false, message: 'データベースエラー' }) 92 | } 93 | return res.json(isNotEmailPassWordErrorMessage) 94 | } 95 | ) 96 | 97 | module.exports = router 98 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/api/logout.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const jwt = require('jsonwebtoken') 3 | const config = require('../config/jwt.config') 4 | const verifyToken = require('../middlewares/verifyToken') 5 | 6 | router.post('/', verifyToken, (req, res) => { 7 | res.clearCookie('token') 8 | res.json({ 9 | isSuccess: true, 10 | token: '', 11 | }) 12 | }) 13 | module.exports = router 14 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/api/tasks.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const verifyToken = require('../middlewares/verifyToken') 3 | const promisePool = require('../config/db') 4 | const { 5 | validator, 6 | checkTasksTitleIsEmpty, 7 | checkTasksTaskIsEmpty, 8 | checkTasksStatusIsEmpty, 9 | } = require('../middlewares/validator') 10 | 11 | router 12 | .route('/') 13 | .get(verifyToken, async (req, res) => { 14 | const [rows, fields] = await promisePool.query( 15 | 'select a.id,a.title,a.task,a.status,b.name as status_name from tasks a inner join task_status b on a.status = b.id where a.user_id = ?', 16 | [req.decoded.id] 17 | ) 18 | return res.json(rows) 19 | }) 20 | .post( 21 | [checkTasksTitleIsEmpty, checkTasksTaskIsEmpty, checkTasksStatusIsEmpty], 22 | validator, 23 | verifyToken, 24 | async (req, res) => { 25 | try { 26 | const ret = await promisePool.query( 27 | 'insert into tasks(title,task,status,user_id) values(?,?,?,?)', 28 | [ 29 | req.body.task.title, 30 | req.body.task.task, 31 | req.body.task.status, 32 | req.decoded.id, 33 | ] 34 | ) 35 | return res.json({ isSuccess: true, message: 'ok' }) 36 | } catch (err) { 37 | console.log(err) 38 | return res.json({ isSuccess: false, message: 'INSERT ERROR' }) 39 | } 40 | } 41 | ) 42 | 43 | router 44 | .route('/:id') 45 | .delete(verifyToken, async (req, res) => { 46 | // 自分のレコード以外は削除できない処理を今後追加 47 | try { 48 | const ret = await promisePool.query('delete from tasks where id = ?', [ 49 | req.params.id, 50 | ]) 51 | console.log(ret) 52 | } catch (err) { 53 | return res.json({ isSuccess: false, message: '削除エラー' }) 54 | } 55 | return res.json({ isSuccess: true, message: 'ok' }) 56 | }) 57 | .put(verifyToken, async (req, res) => { 58 | // 自分のレコード以外は削除できない処理を今後追加 59 | console.log('task update start') 60 | try { 61 | console.log(req.body.task, req.params.id) 62 | const ret = await promisePool.query( 63 | 'update tasks set title = ? ,task = ? ,status = ? where id = ?', 64 | [ 65 | req.body.task.title, 66 | req.body.task.task, 67 | parseInt(req.body.task.status), 68 | req.params.id, 69 | ] 70 | ) 71 | console.log(ret) 72 | } catch (err) { 73 | console.log(err) 74 | return res.json({ isSuccess: false, message: '更新エラー' }) 75 | } 76 | return res.json({ isSuccess: true, message: 'ok' }) 77 | }) 78 | 79 | module.exports = router 80 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/api/users.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const verifyToken = require('../middlewares/verifyToken') 3 | const promisePool = require('../config/db') 4 | const bcrypt = require('bcrypt') 5 | const { 6 | validator, 7 | checkNameIsEmpty, 8 | checkEmailIsEmpty, 9 | checkEmailIsEmail, 10 | checkPasswordIsEmpty, 11 | } = require('../middlewares/validator') 12 | const registerErrorMessage = { isSuccess: false, message: '登録エラー' } 13 | router 14 | .route('/') 15 | .get(verifyToken, async (req, res) => { 16 | // usersのエンドポイントだが自身しか参照させないためwhere句で絞り込み 17 | try { 18 | const [rows, fields] = await promisePool.query( 19 | 'select id,name,email from users where id = ?', 20 | [req.decoded.id] 21 | ) 22 | 23 | // verifiTokeでdecodedの設定確認用(処理では利用していない) 24 | console.log(req.decoded.id) 25 | if (req.decoded.email == 'taro@example.com') { 26 | results = { 27 | userId: req.decoded.userId, 28 | name: '太郎', 29 | } 30 | } 31 | 32 | return res.json(rows) 33 | } catch { 34 | return res.json({ isSuccess: false, message: 'error' }) 35 | } 36 | }) 37 | .post( 38 | [ 39 | checkNameIsEmpty, 40 | checkEmailIsEmpty, 41 | checkEmailIsEmail, 42 | checkPasswordIsEmpty, 43 | ], 44 | validator, 45 | async (req, res) => { 46 | // sigUp を作成したらそちらに移行する(以下でhashを作成しusersのpasswordにinsertする) 47 | const hash = new Promise((resolve) => 48 | bcrypt.hash(req.body.password, 10, (err, hash) => { 49 | if (err) res.json(registerErrorMessage) 50 | resolve(hash) 51 | }) 52 | ) 53 | const password = await hash 54 | try { 55 | const ret = await promisePool.query( 56 | 'insert into users(name,email,password) values(?,?,?)', 57 | [req.body.name, req.body.email, password] 58 | ) 59 | res.json({ isSuccess: true, message: 'insertId:' + ret[0].insertId }) 60 | } catch (err) { 61 | if (err.errno === -3008) { 62 | return res.json({ isSuccess: false, message: 'データベースエラー' }) 63 | } 64 | return res.json(registerErrorMessage) 65 | } 66 | } 67 | ) 68 | 69 | router.get('/:id', verifyToken, async (req, res) => { 70 | const [rows, fields] = await promisePool.query( 71 | 'select id,name,email,password from users where id = ?', 72 | [req.params.id] 73 | ) 74 | 75 | // verifiTokeでdecodedの設定確認用(payloadにemailが含まれていることの確認) 76 | console.log( 77 | 'payload id:', 78 | req.decoded.id, 79 | 'payload email:', 80 | req.decoded.email 81 | ) 82 | 83 | let results = { name: rows[0].name } 84 | return res.json(results) 85 | }) 86 | 87 | module.exports = router 88 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/config/db.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql2') 2 | 3 | const pool = mysql.createPool({ 4 | connectionLimit: 5, 5 | host: 'db', 6 | user: 'appuser', 7 | password: 'mysql', 8 | database: 'test', 9 | }) 10 | 11 | module.exports = promisePool = pool.promise() 12 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/config/index.js: -------------------------------------------------------------------------------- 1 | const ORIGIN_URL = 'http://localhost:3000' 2 | 3 | module.exports = ORIGIN_URL 4 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/config/jwt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jwt: { 3 | secret: 4 | "f719aca24638b8127c55dcc2b92a9ba2fdd42dda5e612c7978821b99ee689a60866d28f9b91ef64129734c3ab757d5d865f1a34c1894fd6400faa831955c38d6", 5 | options: { 6 | algorithm: "HS256", 7 | expiresIn: "20m", 8 | }, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const cors = require('cors') 4 | const http = require('http') 5 | const csrf = require('csurf') 6 | const cookieParser = require('cookie-parser') 7 | const server = http.createServer(app) 8 | const promisePool = require('./config/db.js') 9 | const ORIGIN_URL = require('./config/index.js') 10 | 11 | const csrfProtection = csrf({ 12 | cookie: true, 13 | }) 14 | 15 | app.use(cookieParser()) 16 | 17 | app.use( 18 | express.urlencoded({ 19 | extended: true, 20 | }) 21 | ) 22 | 23 | app.use(express.json()) 24 | 25 | app.set('view engine', 'pug') 26 | 27 | app.use( 28 | cors({ 29 | origin: ORIGIN_URL, 30 | credentials: true, 31 | optionsSuccessStatus: 200, 32 | }) 33 | ) 34 | 35 | app.use(csrfProtection) 36 | 37 | app.get('/', async (_, res) => { 38 | try { 39 | const [rows, fields] = await promisePool.query('select 1 as num') 40 | res.render('index', { 41 | title: 'Hey', 42 | message: `Hello there!num is ${rows[0].num}`, 43 | }) 44 | } catch (err) { 45 | console.log('error:', err) 46 | res.render('index', { 47 | title: 'Hey', 48 | message: `Hello there!`, 49 | }) 50 | } 51 | }) 52 | 53 | app.use( 54 | '/api/v1', 55 | (() => { 56 | const router = express.Router() 57 | router.use('/login', require('./api/login.js')) 58 | router.use('/logout', require('./api/logout.js')) 59 | router.use('/users', require('./api/users.js')) 60 | router.use('/users/:id', require('./api/users.js')) 61 | router.use('/csrf-token', require('./api/csrfToken.js')) 62 | router.use('/tasks', require('./api/tasks.js')) 63 | return router 64 | })() 65 | ) 66 | 67 | server.listen(5000, () => { 68 | console.log('listening on *:5000') 69 | }) 70 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/middlewares/validator.js: -------------------------------------------------------------------------------- 1 | const { check, validationResult } = require('express-validator') 2 | 3 | // users validator 4 | const checkNameIsEmpty = check('name').not().isEmpty().withMessage('name empty') 5 | const checkEmailIsEmpty = check('email') 6 | .not() 7 | .isEmpty() 8 | .withMessage('email empty') 9 | const checkEmailIsEmail = check('email') 10 | .isEmail() 11 | .withMessage('email not format') 12 | const checkPasswordIsEmpty = check('password') 13 | .not() 14 | .isEmpty() 15 | .withMessage('password empty') 16 | 17 | // tasks validator 18 | const checkTasksTitleIsEmpty = check('task.title') 19 | .not() 20 | .isEmpty() 21 | .withMessage('task.title empty') 22 | const checkTasksTaskIsEmpty = check('task.task') 23 | .not() 24 | .isEmpty() 25 | .withMessage('task.task empty') 26 | const checkTasksStatusIsEmpty = check('task.status') 27 | .not() 28 | .isEmpty() 29 | .withMessage('task.status empty') 30 | 31 | function validator(req, res, next) { 32 | const errors = validationResult(req) 33 | if (!errors.isEmpty()) { 34 | const messages = errors.array() 35 | return res.status(422).json({ isSuccess: false, message: messages[0].msg }) 36 | } 37 | next() 38 | } 39 | 40 | exports.checkNameIsEmpty = checkNameIsEmpty 41 | exports.checkEmailIsEmpty = checkEmailIsEmpty 42 | exports.checkEmailIsEmail = checkEmailIsEmail 43 | exports.checkPasswordIsEmpty = checkPasswordIsEmpty 44 | 45 | exports.checkTasksTitleIsEmpty = checkTasksTitleIsEmpty 46 | exports.checkTasksTaskIsEmpty = checkTasksTaskIsEmpty 47 | exports.checkTasksStatusIsEmpty = checkTasksStatusIsEmpty 48 | 49 | exports.validator = validator 50 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/middlewares/verifyToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const config = require('../config/jwt.config') 3 | 4 | function verifyToken(req, res, next) { 5 | const authHeader = req.headers.authorization 6 | const token = req.cookies.token 7 | ? req.cookies.token 8 | : authHeader && authHeader.split(' ')[1] 9 | if (token) { 10 | jwt.verify(token, config.jwt.secret, function (error, decoded) { 11 | if (error) { 12 | return res.status(403).send({ 13 | isSuccess: false, 14 | message: 'トークンの認証に失敗しました。', 15 | }) 16 | } else { 17 | req.decoded = decoded 18 | next() 19 | } 20 | }) 21 | } else { 22 | return res.status(401).send({ 23 | isSuccess: false, 24 | message: 'トークンがありません。', 25 | }) 26 | } 27 | } 28 | module.exports = verifyToken 29 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cookie-parser": "^1.4.5", 14 | "cors": "^2.8.5", 15 | "csurf": "^1.11.0", 16 | "express": "^4.17.1", 17 | "jsonwebtoken": "^8.5.1", 18 | "mysql": "^2.18.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /recipe-2-2/sample/express-app/views/index.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title= title 4 | body 5 | h1= message 6 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/Index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './components/App' 4 | import './index.css' 5 | import { Provider } from 'react-redux' 6 | import { store } from './app/store' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ) 16 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import authReducer from '../features/auth/authSlice' 3 | import tasksReducer from '../features/tasks/tasksSlice' 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | auth: authReducer, 8 | tasks: tasksReducer, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | 3 | import Layout from './Layout' 4 | import { useDispatch, useSelector } from 'react-redux' 5 | import { 6 | fetchCsrfTokenAsync, 7 | fetchTokenAsync, 8 | selectCsrfTokenState, 9 | } from '../features/auth/authSlice' 10 | 11 | const App = () => { 12 | const dispatch = useDispatch() 13 | const csrfToken = useSelector(selectCsrfTokenState) 14 | useEffect(() => { 15 | dispatch(fetchCsrfTokenAsync()) 16 | dispatch(fetchTokenAsync()) 17 | }, [dispatch]) 18 | 19 | console.log('app csrfToken', csrfToken) 20 | 21 | return ( 22 | <> 23 | 24 | 25 | ) 26 | } 27 | 28 | export default App 29 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import * as C from '../config/index' 3 | 4 | const NoMemoFooter = () => { 5 | return ( 6 | <> 7 | 11 | 12 | ) 13 | } 14 | 15 | export const Footer = memo(NoMemoFooter) 16 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import { memo, useState } from 'react' 2 | import { LogoutIcon } from '@heroicons/react/outline' 3 | import { selectIsAuthentication } from '../features/auth/authSlice' 4 | import { useSelector } from 'react-redux' 5 | import * as C from '../config/index' 6 | import Modal from './Modal' 7 | 8 | const NoMemoHeader = () => { 9 | const [modalOn, setModalOn] = useState(false) 10 | const isLogin = useSelector(selectIsAuthentication) 11 | 12 | return ( 13 | <> 14 |
15 | 37 |
38 | 39 | ) 40 | } 41 | 42 | export const Header = memo(NoMemoHeader) 43 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import Main from './Main' 4 | import { Header } from './Header' 5 | import { Footer } from './Footer' 6 | import { Login } from './Login' 7 | import { SignUp } from './SignUp' 8 | import { 9 | selectIsAuthentication, 10 | selectIsSignUp, 11 | } from '../features/auth/authSlice' 12 | 13 | const Layout = () => { 14 | const isLogin = useSelector(selectIsAuthentication) 15 | const isSignUp = useSelector(selectIsSignUp) 16 | const [user, setUser] = useState({ 17 | name: 'taro', 18 | email: 'taro@example.com', 19 | password: 'abcd', 20 | }) 21 | return ( 22 |
23 |
24 | {isLogin ? null : isSignUp ? ( 25 | 26 | ) : ( 27 | 28 | )} 29 | {isLogin ?
: null} 30 |
31 |
32 | ) 33 | } 34 | 35 | export default Layout 36 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Login.js: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { 4 | selectCsrfTokenState, 5 | postAuthenticationAsync, 6 | toggleSignUp, 7 | } from '../features/auth/authSlice' 8 | 9 | const NoMemoLogin = ({ user, setUser, setIsSignUp }) => { 10 | const dispatch = useDispatch() 11 | const csrfToken = useSelector(selectCsrfTokenState) 12 | 13 | return ( 14 | <> 15 |
16 | 22 | setUser({ ...user, email: e.target.value })} 30 | /> 31 |
32 | 38 | setUser({ ...user, password: e.target.value })} 45 | /> 46 |

Please choose a password.

47 |
48 |
49 | 65 |
66 | { 68 | e.preventDefault() 69 | dispatch(toggleSignUp()) 70 | }} 71 | > 72 | Switch SignUp?(CLICK!!) 73 | 74 |
75 |
76 |
77 | 78 | ) 79 | } 80 | 81 | export const Login = memo(NoMemoLogin) 82 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Main.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { 4 | selectTokenState, 5 | selectCsrfTokenState, 6 | } from '../features/auth/authSlice' 7 | import { 8 | fetchTasksAsync, 9 | selectTasksState, 10 | selectDeleteTaskState, 11 | selectUpdateTaskState, 12 | selectInsertTaskState, 13 | } from '../features/tasks/tasksSlice' 14 | import Task from './Task' 15 | import TaskInsertModal from './TaskInsertModal' 16 | 17 | const Main = () => { 18 | const dispatch = useDispatch() 19 | const token = useSelector(selectTokenState) 20 | const csrfToken = useSelector(selectCsrfTokenState) 21 | const tasks = useSelector(selectTasksState) 22 | const deleteTaskState = useSelector(selectDeleteTaskState) 23 | const updateTaskState = useSelector(selectUpdateTaskState) 24 | const insertTaskState = useSelector(selectInsertTaskState) 25 | const [insertModalOn, setInsertModalOn] = useState(false) 26 | 27 | useEffect(() => { 28 | dispatch(fetchTasksAsync({ token: token })) 29 | }, [dispatch, token, deleteTaskState, updateTaskState, insertTaskState]) 30 | 31 | return ( 32 |
33 | {tasks.length ? ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {tasks.map((task) => ( 46 | 47 | ))} 48 | 49 |
タスク名内容ステータス修正削除
50 | ) : null} 51 |
52 | 58 |
59 | {insertModalOn ? ( 60 | 64 | ) : null} 65 |
66 | ) 67 | } 68 | 69 | export default Main 70 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Modal.js: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from 'react' 2 | import { useSelector, useDispatch } from 'react-redux' 3 | import { Dialog, Transition } from '@headlessui/react' 4 | import { LogoutIcon } from '@heroicons/react/outline' 5 | import { selectCsrfTokenState, logout } from '../features/auth/authSlice' 6 | 7 | const Modal = ({ setModalOn }) => { 8 | const [open, setOpen] = useState(true) 9 | const cancelButtonRef = useRef(null) 10 | const csrfToken = useSelector(selectCsrfTokenState) 11 | const dispatch = useDispatch() 12 | 13 | return ( 14 | 15 | { 20 | setOpen(false) 21 | setModalOn(false) 22 | }} 23 | > 24 |
25 | 34 | 35 | 36 | 37 | {/* This element is to trick the browser into centering the modal contents. */} 38 | 44 | 53 |
54 |
55 |
56 |
57 |
62 |
63 | 67 | Logout account 68 | 69 |
70 |

71 | ログアウトしますか? 72 |

73 |
74 |
75 |
76 |
77 |
78 | 90 | 101 |
102 |
103 |
104 |
105 |
106 |
107 | ) 108 | } 109 | 110 | export default Modal 111 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/SignUp.js: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { signUp, toggleSignUp } from '../features/auth/authSlice' 4 | import { selectCsrfTokenState } from '../features/auth/authSlice' 5 | const NoMemoSignUp = ({ user, setUser }) => { 6 | const dispatch = useDispatch() 7 | const csrfToken = useSelector(selectCsrfTokenState) 8 | 9 | return ( 10 | <> 11 |
12 | 18 | setUser({ ...user, name: e.target.value })} 26 | /> 27 | 33 | setUser({ ...user, email: e.target.value })} 40 | /> 41 |
42 | 48 | setUser({ ...user, password: e.target.value })} 55 | /> 56 |

Please choose a password.

57 |
58 |
59 | 76 |
77 | { 79 | e.preventDefault() 80 | dispatch(toggleSignUp()) 81 | }} 82 | > 83 | Switch Login?(CLICK!!) 84 | 85 |
86 |
87 |
88 | 89 | ) 90 | } 91 | 92 | export const SignUp = memo(NoMemoSignUp) 93 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/Task.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { selectCsrfTokenState } from '../features/auth/authSlice' 4 | import TaskDeleteModal from './TaskDeleteModal' 5 | import TaskUpdateModal from './TaskUpdateModal' 6 | 7 | const Task = ({ task }) => { 8 | const csrfToken = useSelector(selectCsrfTokenState) 9 | const [updateModalOn, setUpdateModalOn] = useState(false) 10 | const [deleteModalOn, setDeleteModalOn] = useState(false) 11 | 12 | return ( 13 | <> 14 | 15 | 16 |

{task.title}

17 | 18 | 19 |

{task.task}

20 | 21 | 22 |

{task.status_name}

23 | 24 | 25 | 33 | 34 | 35 | 43 | 44 | 45 | {updateModalOn ? ( 46 | 50 | ) : null} 51 | {deleteModalOn ? ( 52 | 56 | ) : null} 57 | 58 | 59 | ) 60 | } 61 | 62 | export default Task 63 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/TaskDeleteModal.js: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Dialog, Transition } from '@headlessui/react' 4 | import { TrashIcon } from '@heroicons/react/outline' 5 | import { deleteTaskAsync } from '../features/tasks/tasksSlice' 6 | 7 | const TaskDeleteModal = ({ setDeleteModalOn, credentials }) => { 8 | const [open, setOpen] = useState(true) 9 | const cancelButtonRef = useRef(null) 10 | const dispatch = useDispatch() 11 | 12 | return ( 13 | 14 | { 19 | setOpen(false) 20 | setDeleteModalOn(false) 21 | }} 22 | > 23 |
24 | 33 | 34 | 35 | 36 | {/* This element is to trick the browser into centering the modal contents. */} 37 | 43 | 52 |
53 |
54 |
55 |
56 |
61 |
62 | 66 | Task Record Dlete ? 67 | 68 |
69 |

70 | レコードを削除しますか? 71 |

72 |
73 |
74 |
75 |
76 |
77 | 94 | 105 |
106 |
107 |
108 |
109 |
110 |
111 | ) 112 | } 113 | 114 | export default TaskDeleteModal 115 | -------------------------------------------------------------------------------- /recipe-2-2/sample/react-app/src/components/TaskInsertModal.js: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Dialog, Transition } from '@headlessui/react' 4 | import { RefreshIcon } from '@heroicons/react/outline' 5 | import { insertTaskAsync } from '../features/tasks/tasksSlice' 6 | 7 | const TaskInsertModal = ({ setInsertModalOn, credentials }) => { 8 | const [open, setOpen] = useState(true) 9 | const [taskState, setTaskState] = useState({ 10 | title: '', 11 | task: '', 12 | status: 1, 13 | }) 14 | const cancelButtonRef = useRef(null) 15 | const dispatch = useDispatch() 16 | 17 | return ( 18 | 19 | { 24 | setOpen(false) 25 | setInsertModalOn(false) 26 | }} 27 | > 28 |
29 | 38 | 39 | 40 | 41 | {/* This element is to trick the browser into centering the modal contents. */} 42 | 48 | 57 |
58 |
59 |
60 |
61 |
66 |
67 | 71 | Task Record Insert ? 72 | 73 |
74 | 80 | 86 | setTaskState({ ...taskState, title: e.target.value }) 87 | } 88 | /> 89 | 95 |