ReactとNode.jsとMySQLで作る基本のWebメモアプリ

Linux ※Web開発
ReactとNode.jsとMySQLで作る基本のWebメモアプリ
本記事の目標 ゼロからWebアプリを作成し、データの入力・保存・検索・表示ができるようになる 本記事の対象読者 Webアプリがどういう仕組みで動いているのか知りたい人 ReactとNode.jsで
  • 本記事の目標

ゼロからWebアプリを作成し、データの入力・保存・検索・表示ができるようになる

  • 本記事の対象読者
  • Webアプリがどういう仕組みで動いているのか知りたい人
  • ReactとNode.jsでWebアプリを作ってみたい人
  • (WSL2を使ってWindows環境でアプリ開発を行いたい人)

こんにちは、mayaです。今回は以下の構成で基本のWebアプリを作成していきたいと思います。

  • フロントエンドをReact
  • バックエンドをNode.js
  • データベースをMySQL

今回作るアプリの完成形としては「アプリから入力したデータを保存、必要に応じて検索・表示できるメモアプリ」のようなものをイメージしてください。

全体を通して完全初心者向けの解説となっているため、随所で解説がクドイですが、何卒ご了承ください。

初心者の方へ


環境

今回使用している環境は以下の通りです。

  • Windows11
  • WSL2
  • Ubuntu 20.04.4 LTS
  • Node.js 16.14.2
  • MySQL 333
  • npm 8.5.0

環境構築

本章では、WSL、Ubuntu、Node.js、MySQLの環境をそれぞれ整えていきます。

  1. WSLを導入する
  2. Ubuntuを導入する
  3. Node.jsとnpmを導入する
  4. MySQLを導入する

※1~3については、Microsoftの公式ドキュメントを参照することで、より正確かつ詳しい説明を確認できます。

Node.jsをWSL2にインストール|Microsoft Docs

1.WSLを導入する

前提条件として、Windows10 バージョン2004以降(ビルド 19041 以降)またはWindows11を実行している必要があります。バージョンとビルド番号を確認するには、[Windowsロゴキー+Rキー]を押して、「winver」と入力してOKを押します。

問題がなければWSLを導入していきます。

PowerシェルまたはWindowsコマンドプロントに次のコマンドを入力してください。

wsl --install

このコマンドを実行すると、必要なオプションコンポーネントの有効化、既定の設定、Linuxディストリビューション(規定ではUbuntu)のインストールなどが自動で行われます。

WSLの導入はこれで完了です。簡単でしたね。

もし上記のコマンドを実行しても機能しない場合は、すでにWSLがインストールされている可能性があります。次章を参考に解決してください。

また、より正確な説明・解説についてはMicrosoftの公式ドキュメントをご覧ください。

》WSLのインストール|Microsoft Docs

WSLとは何か?なぜ必要か?

WSL(Windows Subsystem for Linux)とは、Linux環境をWindows上で”直接”利用するための機能です。

なぜWindows上でわざわざLinux環境を利用する必要があるかというと、PythonやPHPやRubyなどの人気プログラミング言語のほとんどは、Linux上で動作することを前提として作られているからです。今回使用するNode.jsも例外ではありません。

要するに、Windowsユーザーがアプリ開発をしたいなら、WSLを導入したほうがいいですよ※1。というお話です。

※1 WSL以外にも方法はありますが、WSLの利用を推奨します

2.Ubuntuを導入する

ほとんどの方は、先ほどwsl --installのコマンドを実行した際に、規定のLinuxディストリビューションであるUbuntuがインストールされているため、本章は読み飛ばして構いません。

しかし、コマンドを実行しても機能しなかった場合は、既にWSLがインストールされている可能性があります。

次のコマンドをPowerシェルまたはWindowsコマンドラインで実行し、既に使用可能なディストリビューションがあるか確認してください。

wsl --listt --online

一覧に「Ubuntu」がない場合は、次のコマンドを実行してUbuntuをインストールしてください。

wsl --install -d ubuntu

Windowsターミナルを使ってみよう

現段階では、WindowsコマンドプロントやPowerシェルで作業している方が大半だと思いますが、より便利な「Windowsターミナル」の利用も検討してみてください。

Windowsターミナルを使うと、一度に複数のタブを実行できたり、Linuxコマンドライン、Windowsコマンドプロント、Powerシェル、Azure CLIなどを同時に起動してすばやく切り替えることができますよ。

また、以降の章ではWindowsターミナルを利用している前提でお話していきます。(Windowsコマンドプロント、Powerシェルのままでも支障はありません)

Microsoft Store(Windows Terminal)

3.Node.jsとnpmを導入する

本章ではNode.jsとnpmを導入していきますが、まずはその前に「nvm(node version manager)」をインストールします。

nvmとは?

nvmはNode.jsのバージョンを管理するためのツールです。これを導入することで、必要に応じてNode.jsを最新あるいは過去のバージョンに適宜切り替えることができるようになります。

npmとは?

npm(node package manager)とは、ライブラリ間の依存関係を管理するためのパッケージ管理システムです。もともとはNode.jsのためのシステムでしたが、現在ではフロントエンド用のパッケージの方が多く公開されています。(Reactのライブラリもnpmを使ってインストールする)
アプリを作る場合は通常それ単独で完結せず、サードパーティ(外部)のライブラリをいくつも読み込んで、それらの特定のバージョンが相互に依存し合って成り立っています。(これをライブラリの「依存関係」という)
それらのバージョンの依存関係を管理するのがnpmの役割の”1つ”です。

1.それでは、先ほど導入したUbuntuのコマンドライン(WSL)を開いてください。

WSLを起動するには、Windowsターミナルにて次のコマンドを入力します。

wsl

2.既にNode.jsやnpmがインストールされていないか確認します。

もしインストールされていた場合は、以下のサイトを参考に、既存のNode.jsおよびnpmを削除することをおススメします。(インストールの種類が異なると、予期せぬトラブルが行る可能性があるため)

node -v
npm -v

Node.jsを削除する方法|askubuntu.com

3.cURLというツールをインストールします。

cURLは、コマンドラインを使ってインターネットからコンテンツをダウンロードするために使用するツールです。

sudo apt-get install curl

4.次のコマンドを実行して、nvmをインストールします。

この際、あらかじめGithubのプロジェクトページを確認し、コマンドがnvmの最新バージョンを指定しているか確かめてください。(2022年6月現在ではv0.39.1が最新でした)

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

5.nvmのインストールを確認します。

次のコマンドを実行して、コマンドが見つかりませんと表示される場合は、一度Windowsターミナルを閉じてから4.の作業をやり直してください。

command -v nvm

ちなみに、次のコマンドを実行することで、現在インストールされているNode.jsのバージョンを確認できます。無論、現段階では何も表示されないはずです。

nvm ls

6.今回は、最新バージョンと安定バ―ション(LTS)の2種類のNode.jsをインストールしてみます。

アプリ開発で実際に使用するのは安定バージョンですが、もう片方は、この後の手順で「Node.jsのバージョンを切り替える方法」について学ぶために使用します。

nvm install --lts
nvm install node

なお、上記コマンドを実行することで、npmやnpxもセットでインストールされます。

LTS(安定版)とは何か?

LTS(Long Term Support)とは、通常よりも長期間のサポートを保証するという意味です。
UbuntuなどのOSをインストールする際、最新版とLTS版のどちらかを選ぶ場合、通常はLTSを選ぶべきでしょう。LTS版は最新版に比べてバグが少なく、安定した運用を実現できます。

7.次のコマンドで、先ほどインストールした2つのバージョンを確認します。

nvm ls

僕の場合は、v16.14.2とv18.0.0がインストールされているのが分かります。

また、次のコマンドでnpmがインストールされていることも確認できます。

npm -v

8.最後にNode.jsのバージョンを切り替える方法について紹介します。(試さなくても大丈夫です)

Node.jsのバージョンを切り替えたいときは、当該プロジェクトディレクトリに移動して、次のコマンドを実行することで最新バージョンに切り替えることができます。

nvm use node

あるいは、次のコマンドを実行することで安定バージョンに切り替えたり、バージョンを指定して切り替えることができます。

nvm use --lts
nvm use v8.2.1

使用可能なバージョンは、次のコマンドで確認できます。

nvm ls-remote

4.MySQLを導入する

ここでは、MySQLのインストールと初期設定を行います。

インストールについては、Microsoftの公式ドキュメントを参照することでより正確かつ詳細な情報を確認できます。

データベースの概要|Microsoft Docs

MySQLのインストール

1.WSLターミナル(Ubuntu)を開きます。

wsl

2.Ubuntuパッケージを更新します。

sudo apt update

3.MySQLをインストールします。

sudo apt install mysql-server

4.MySQLのバージョンを確認します。

mysql --version

MySQLの初期設定

1.次のコマンドを実行して、MySQLサーバーを起動します。

sudo /etc/init.d/mysql start

2.セキュリティスクリプトプロンプトを開始します。

sudo mysql_secure_installation

このコマンドを実行すると、いくつかの質問が表示されますので適宜答えてください。

質問内容は次の通りです。(カッコ内は僕の回答例です)

  1. MySQLのパスワードの強度をテストするために使用できる、パスワードの検証プラグインを設定するか?(No)
  2. MySQLルートユーザーのパスワードを設定し、匿名ユーザーを削除するか?(No)
  3. ルートユーザーがローカルとリモートの両方でログインできるようにするか?(Yes)
  4. テストデータベースを削除するかどうか(No)
  5. 特権テーブルを直ちに再読み込みするかどうか(No)

3.MySQLプロンプトを開きます。

sudo mysql -u root -p

初期のrootユーザーのパスワードは空白です。

4.他プログラム(例えばNode.jsなど)からデータにアクセスするための、接続設定を行います。

 ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '<パスワード>';

<パスワード>の部分は任意の文字列に置き換えてください。設定したパスワードは今後必ず使用するので忘れないようにしてください。

MySQLの基本操作

上から順に、起動~停止までの流れです。

  • 状態確認:sudo service mysql status
  • 起動:sudo service mysql start
  • MySQLを開く:sudo mysql
  • MySQLを閉じる:exit
  • 停止:sudo service mysql stop

MySQLにデータを挿入する

この後に使用するデータを挿入していきます。

手順は次の通りです。

  1. sudo service mysql startでMySQLを起動
  2. sudo mysqlでMySQLを開く
  3. SHOW databases;でデータベースの一覧確認
  4. CREATE database memo_app;でデータベースを作成
  5. GRANT ALL ON memo_app.* TO 'root'@'localhost';で権限設定
  6. USE memo_app;でデータベースを選択
  7. SHOW tables;でテーブル確認
  8. CREATE TABLE users (id INT AUTO_INCREMENT, name TEXT,email varchar(255), pass varchar(255), PRIMARY KEY (id));でusersテーブル作成
  9. DESCRIBE usersでテーブル構成確認
  10. INSERT into users(name,email,pass) values('maya','maya@gmail.com','password');でデータ挿入
  11. SELECT * FROM users;でデータ確認
  12. 9の手順を何度か繰り返し、異なるデータをいくつか挿入してください。

さらっと紹介しましたが、5番の権限設定は忘れずに必ず行ってください。権限設定ができていないと、外部のプログラムからMySQLに接続することができず、後々困りますので。

小まとめ

以上で環境構築は完了です。

次ページからは、Webアプリの作成に入ります。

次ページ:バックエンドサーバーの構築(Node.js)

Page: 2

前ページで環境構築が終わったので、いよいよWebアプリの作成に入ります。

本章ではフロントエンドのReact、バックエンドのNode.js、データベースのMySQLの基本設定と構築を行い、3方を相互に接続するところまでが目標です。

具体的には、フロントエンドからバックエンドを通じてデータベースにアクセスし、取得したデータを表示することができるようにしていきます。

  1. バックエンドサーバーの構築(Node.js)
  2. APIエンドポイントの作成(Node.js)
  3. フロントエンドの構築(React)
  4. フロントエンドとバックエンドを接続(React+Node.js)
  5. バックエンドとデータベースを接続(Node.js+MySQL)
  6. フロントとバックとデータベースを接続(React+Node.js+MySQL)

※1~3については、Microsoftの公式ドキュメントを参照することで、より正確かつ詳しい説明を確認できます。

Node.jsをWSL2にインストール|Microsoft Docs

バックエンドサーバーの構築(Node.js)

最初にプロジェクトフォルダを作成し、作成したフォルダに移動します。フォルダ名は何でも構いませんが、僕は「memo-app」という名前にしました。

#フォルダ(ディレクトリ)を作成
mkdir memo-app
#作成したフォルダに移動
cd memo-app

続いて、npm initを実行してpackage.jsonファイルを作成します。

lsコマンドを実行すると、package.jsonファイルが作成されていることが分かります。

npm init -y

package.jsonとはなにか?

package.jsonは、npmでインストールしたパッケージを管理するためのファイルです。
雑で申し訳ないですが、package.jsonやnpmについて深堀すると長くなりすぎるので、以下のサイトを参考に理解を深めてください。

npm initしないとどうなる?
【初心者向け】NPMとpackage.jsonを概念的に理解する

今回は、Node.jsの最も一般的なフレームワークである「express」を使用してバックエンドサーバーを構築します。

よってまずは、expressのインストールを行います。

npm install express

次にbackendフォルダを作成し、その中にindex.jsファイルを作成します。

mkdir backend
cd backend
#index.jsファイルを作成
touch index.js

続いて、index.jsファイルにExpressサーバーを稼働させるために必要なコードを記述していきます。

//requireでexpressモジュールを読み込む
const express = require('express');
//expressモジュールを実体化して、定数appに代入
const app = express();
//ポート番号を指定
const port = 3000;

//'/'パスにGET要求があった際に実行する処理
app.get('/', (req, res) => {
  res.send('Hello World!');
});

//3000ポートでlisten
app.listen(port, () => {
  console.log(`listening on *:${port}`);
})

requireとはなにか?

requireは外部ファイルを読み込む際に使われる文法です。requireの他にもimportなどをよく見かけますが、2つの違いは何でしょうか?
下記サイトでわかりやすく解説してあるので、興味のある方は見てみてください。
jsのimportとrequireの違い

expressの記法について

expressには独自の記法が多くあるため、戸惑う初心者も多いでしょう。
下記サイトでは、expressのディレクトリ構成や記法などについて、初心者向けにわかりやすく解説しているので、一度目を通してみると良いでしょう。
expressは一体何をしとるんじゃ・・・

ここで一度ターミナルに戻り、cdコマンドでmemo-appディレクトリに戻ります。

#一つ上(親)ディレクトリに移動する
cd ..

memo-appディレクトリに移動したら、そこでExpressサーバーを起動します。nodeコマンドの後ろでは、先ほど作成したbackendフォルダ内のindex.jsファイルを指定してください。

node backend/index.js

nodeコマンドの使い方

node <jsファイル名>を実行すると、そのJSファイルのコードが実行されます。
また、nodeと単体で実行するとREPLが実行される。

ターミナルにlistening on *:3000と表示されたら起動完了です。ブラウザでhttp://localhost:3000/にアクセスすると、「Hello World」と表示されているはずです。

手軽にサーバーを起動する

今回はnode backend/index.jsを実行することでサーバーを起動しましたが、毎回ファイル名を指定するのは面倒です。
package.jsonファイルにscriptsを追加すれば、もっと手軽にサーバーを起動できるようになります。1.package.jsonファイルを開く

2.scriptsにstart-nodeを追加する

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start-node": "node backend/index.js"
},

2.追加後は、次のコマンドでExpressサーバーを起動できる

npm run start-node

APIエンドポイントの作成(Node.js)

フロントエンド(React)からバックエンド(Express.js)にアクセスする際の、アクセス先(エンドポイント)をExpressサーバー側に準備します。

index.jsを開いて、/apiパスにアクセスした際の処理を記述します。

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello World');
}
//'/api'パスにGET要求があった際に実行する処理
app.get('/api', (req, res) => {
  res.json({message: "Hello World"});
});

app.listen(port, () => {
  console.log(`listening on *:${port}`);
})

試しにhttp://localhost:3000/apiにアクセスしてみて下さい。既述したjsonが表示されていれば成功です。

(うまくいかない場合は、Expressサーバーを再起動してからもう一度試してください)

サーバーを自動で再起動する方法

jsファイルを変更した際には、必ずサーバーを再起動してファイルの変更を反映させる必要があります。
しかし、都度手動で再起動するのは手間なので、nodemonというパッケージの利用をおススメします。nodemonは、ファイルの変更を検知すると自動でサーバーを再起動してくれるツールです。1.ターミナルで、memo-appディレクトリに移動
2.npm install -g nodemonを実行
3.package.jsonを開く
4.以下のように記述を変更

#変更前
"start-node": "node backend/index.js"

#変更後
"start-node": "nodemon backend/index.js"

以上の手順を踏めば、今まで通りのnpm run start-nodeコマンドでnodemonが立ち上がり、ファイル変更時には自動で再起動するようになります。

フロントエンドの構築(React)

次にフロントエンド(React)を構築していきます。

ターミナルにもどってmemo-appディレクトリに移動したら、npxコマンドでcreate-react-appを実行します。

フォルダ(ディレクトリ)名は、frontendとしています。

npx create-react-app frontend

処理が完了したらfrontendフォルダに移動して、npm startコマンドでReactを起動してみてください。

cd frontend
npm start

すると次のようなエラーが表示されるはずです。

これは要するに「3000番ポートがNode.jsによって使用中だから、同じポートでReactを開けませんよ」というメッセージです。

? Something is already running on port 3000. Probably(以下略)

また、エラーメッセージの終わりでは「他のポートでReactを開きますか?」と聞かれています。Yを選択するとReactが3001番ポートで開かれるのですが、今回は3000番ポートでReactを、3001番ポートでNode.jsを開きたいのでNo(n)を選択してください。

Would you like to run the app on another port instead?(Y/n)

次に、backend/index.jsファイルを開いてポート番号を3001に変更します。

#変更前
const port = 3000;

#変更後
const port = 3001;

続いて、ターミナルに戻ってfrontendディレクトリに移動し、npm startコマンドを実行するとReactを3000番ポートで起動できます。

npm start

最後にReactの初期画面を変更します。

frontend/src/App.jsを開いて次のように変更します。

import './App.css';

function App() {
  return (
    <div className="App">
      <h1>フロントエンド</h1>
    </div>
  );
}

export default App;

Reactは非同期処理なので、変更を保存した瞬間に(ブラウザを更新しなくても)、変更が反映されて以下のような画面が表示されます。

バックエンドとフロントエンドを接続(React+Node.js)

続いては、フロントエンド(React)からバックエンド(Express)にアクセスしてデータを取得できるようにしていきます。

具体的には、React側からNode.js側のエンドポイント(localhost:3001/api)にアクセスして、当該ページが返しているJSONデータ({message: "Hello World"})を取得し、React側で表示します。

データの保持・取得にはReactのHookであるuseStateとuseEffect、およびfetchメソッドを利用します。

早速用語がわからない人へ

お察しの通り、本章のキーワードは「useState」「useEffect」「fetch」の3つですが、意味を知らない方がほとんどだと思います。がしかし、1つずつ掘り下げて解説すると長くなるのでそれぞれ別個で解説記事を用意する予定です。

よって本記事ではそれぞれの詳細な説明は省きますが、コードを追っていけば(コピペでも)アプリは完成しますし使い方もなんとなく分かると思いますので、このまま読み進めてOKです。

まず、初めにコードの完成系を見てください。frontend/srcディレクトリ内のApp.jsを次のように変更します。

import './App.css';
//1.useStateとuseEffectをインポート
import { useState,useEffect } from 'react';

function App() {
  //useStateの初期値(空)を設定
  const [message, setMessage] = useState('');
  
  useEffect(() => {
    //fetchでバックエンドExpressのサーバーを指定
    fetch('http://localhost:3001/api')
      //レスポンスをjsonとして受け取りjsオブジェクトを生成
      .then((res) => res.json())
      //生成したjsオブジェクトをdataに代入
      //data.messageで取り出したデータをuseStateに保存
      .then((data) => setMessage(data.message));
  },[])

  return (
    <div className="App">
      <h1>フロントエンド</h1>
      //useStateに保存した値を表示
      <p>{ message }</p>
    </div>
  );
}

export default App;

現時点では、変更を反映してもブラウザには何も表示されません。なぜなら、CORSポリシーによってlocalhost:300からlocalhost:3001/apiへのアクセス(fetch)がブロックされているからです。

F12ボタンを押してデベロッパーツールを確認してみると、お怒りメッセージが届いているはずです。

CORSポリシーとは?

CORSポリシーとは、異なるオリジン(ドメイン、プロトコル、ポート番号)間でのHTTPリクエスト(情報の取得・送信など)を制限するための、ブラウザのセキュリティ機能の1つです。

より詳細かつわかりやすい説明は、以下のサイト・動画を見てみてください。
Mozilla.org|オリジン間リソース共有(CROS)
CORSの原理を知って正しく使おう|徳丸浩のウェブセキュリティ講座

今回はlocalhost:3000から、別オリジンであるlocalhost:3001へfetchしようとしたためCROSポリシーさんに怒られてしましました。

この問題を回避するために、/frontendディレクトリ直下のpackage.jsonファイルにproxyを設定します。

{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:3001",
  //以下略

proxyを設定したので、App.jsのfetchメソッドで指定しているURLもhttp:localhost:3001/apiから/apiに変更します。

useEffect(() => {
  fetch('/api')
    .then((res) => res.json())
    .then((data) => setMessage(data.message));
},[])

再度ブラウザを読み込むと、「フロントエンド」の下に「Hello World」の文字が表示されているはずです。

※表示されない場合はReactサーバーを再起動(CTRL+Cで停止、npm run startで起動)してみてください。

なぜproxyでCORSを回避できるのか?

変更前、すなわちproxyを設定しない場合、ブラウザはApp.jsを読み込むと直接localhost:3001/apiへfetchしようとします。(実際fetch('http:localhost/api')というコードを書いていた)

変更後、すなわちproxyを設定した場合、ブラウザはApp.jsを読み込むとlocalhost:3000/apiへfetchし、それを受け取ったlocalhost:3000/apiは、さらにlocalhost:30001/apiへfetchします。(fetch('http:localhost:3001/api')fetch('/api')に変更したのは、ブラウザをlocalhost:3000/apiに誘導するため)

要するに、バックエンド(localhost:3001/api)に直接アクセスせず、proxy(localhost:3000/api)を中継することで、CORSを回避したというわけです。
CORSはブラウザレベルのセキュリティ機能であるため、ブラウザの外でどのようなデータ転送が行われても規制することはできません。

バックエンドとデータベースを接続(Node.js+MySQL)

続いてはバックエンド(express)からデータベース(MySQL)にアクセスして、データを取得・操作できるようにしていきます。

Node.jsからMySQLのデータを操作するには、専用のパッケージが必要になります。今回は最も有名な「mysql2」を利用します。

まずは、ターミナルにてmemo-appディレクトリに移動して、次のコマンドを実行します。

npm install mysql2

mysql2のインストールが終わったら、MySQLに接続できるようにbackendディレクトリのindex.jsファイルを次のように書き換えます。

const express = require('express');
//mysql2の読み込む
const mysql = require('mysql2');
const app = express();
const port = 3001;

//mysqlと接続するための設定
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'memo_app'
});

app.get('/', (req,res) => {
    res.send('Hello World');
})

app.get('/api', (req, res) => {
    // /apiにアクセスした際に、MySQLに対して行う処理
    connection.query(
        //usersテーブルからデータを取得する処理
        'SELECT * FROM users',
        function(err, results, fields){
            if(err){
                console.log('接続エラー');
                throw err;
            }
            res.json({message: results[0].name});
        }
    )
});

app.listen(port, () => {
    console.log(`listening on *:${port}`);
})

「mysqlと接続するための設定」におけるpasswordは、前頁の「4.MySQLを導入する」にてご自身が設定したものと適宜置き換えてください。

以上で接続作業は完了です。http://localhost:3001/apiにアクセスすると、取得したデータが表示されるはずです。

フロントとバックとデータベースを接続(React+Node.js+MySQL)

続いては、フロントエンド(React)とバックエンド(express)とデータベース(MySQL)の接続確認を行います。

とはいえ特にやることはなく、http://localhost:3000にアクセスして、「フロントエンド」の下にデータベースから取得したデータが表示されていれば完了です。

これで、データベースに保存されているデータを、バックエンドを介してフロントエンドから取得・表示することが出来ました!

小まとめ

以上でフロント(React)とバック(express)とデータベース(MySQL)の接続確認は完了です。

最後のページでは、簡易UIの作成と検索機能の実装を行います。

次ページ:簡易UIの作成

Page: 3

前ページでフロント・バック・データベースの接続確認が完了したので、ここからはメモアプリのUIと機能を実装していきます。

本章ではフロントエンドの検索・追加フォームから、任意のデータを検索・表示あるいは追加できるようにするのが目標です。

  1. 簡易UIの作成
  2. 準備中
  3. 準備中


簡易UIの作成

まずは簡易的なUI(User Interface)を作っていきます。UIとはすなわちアプリの概観です。

今回作成するUIに必要なコンポーネントは、次の4つです。

  • Headerコンポーネント
  • Formコンポーネント
  • Listコンポーネント

完成イメージはこんな感じ。

まずは、コンポーネントファイルを格納するためのmodulesディレクトリと、CSSファイルを格納するためのcssディレクトリを作成しましょう。ターミナルにてmemo-app/frontend/srcディレクトリに移動したら、次のコマンドを実行してください。

mkdir modules
mkdir css

Headerコンポーネントを作る

ターミナルにてfrontend/modulesに移動したら、Header.jsxファイルを作成します。

import React from 'react';
import '../css/common.css';
import '../css/Header.css';

function Header(){
    return(
        <>
        <header className="Header">
            <ul>
                <li className="H_logo">
                    <p>Books</p>
                </li>
                <li>
                    aiueo
                </li>
            </ul>
        </header>
        </>
    );
}

export default Header;

次に、/frontend/cssディレクトリに移動して2つのCSSファイルを作成します。

common.css

/*setting*/
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,
blockquote,pre,abbr,address,cite,code,del,dfn,em,img,
ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,
ol,ul,li,fieldset,form,label,legend,table,caption,
tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,
figcaption,figure,footer,header,hgroup,menu,nav,section,
summary,time,mark,audio,video {
    font-size: 100%;
    margin: 0;
    padding: 0;
    vertical-align: baseline;
    border: 0;
    outline: 0;
    background: transparent;
}
body {
    line-height: 1;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
    display: block;
}
nav ul {
    list-style: none;
}
blockquote,
q {
    quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
    content: '';
    content: none;
}
a {
    font-size: 100%;
    margin: 0;
    padding: 0;
    vertical-align: baseline;
    background: transparent;
}
ins {
    text-decoration: none;
    color: #000;
    background-color: #ff9;
}
mark {
    font-weight: bold;
    font-style: italic;
    color: #000;
    background-color: #ff9;
}
del {
    text-decoration: line-through;
}
abbr[title],
dfn[title] {
    cursor: help;
    border-bottom: 1px dotted;
}
table {
    border-spacing: 0;
    border-collapse: collapse;
}
hr {
    display: block;
    height: 1px;
    margin: 1em 0;
    padding: 0;
    border: 0;
    border-top: 1px solid #ccc;
}
label{
	font-family:"Poppins";
	font-size:18px;
}
input{
	font-family:"Poppins";
	font-size:15px;
	color:#171717;
}
input{
	width:100%;
	height:40px;
	border-radius:3px;
	border:1px solid rgba(0, 0, 0, 0.1);
	background:#FCFCFC;
	margin:5px 0 0 0 ;
	padding:0 18px;
	box-sizing: border-box;
}
select{
	width:100%;
	height:40px;
	border-radius:3px;
	border:1px solid rgba(0, 0, 0, 0.1);
	background:#FCFCFC;
	display:inline-block;
    margin:5px 0 0 0 ;
	padding:0 18px;
	box-sizing: border-box;
	color:#161616;
	font-size:15px;
	appearance: none;
}
select::-ms-expand{
	display:none;
}
option{
	font-size:14px;
	font-family:"Poppins";
}
textarea{
	width:100%;
	height:200px;
	border-radius:3px;
	border:1px solid rgba(0, 0, 0, 0.1);
	background:#FCFCFC;
	margin:5px 0 0 0 ;
	padding:15px;
	box-sizing: border-box;
	color:#161616;
	font-family:"Poppins";
    font-size:15px;
}

/*setting more*/
body {
    font-family:"Noto Sans JP", fot-tsukubrdgothic-std, fot-tsukuardgothic-std, toppan-bunkyu-midashi-go-std, "Poppins","Yu Mincho", YuMincho, "YuMincho", "Yu Gothic", "YuGothic", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Robot", serif;
    width:100%;
    background:#F2F2F2;

    margin:0px;
	overflow-x: hidden;
}
ul{
    padding-left:0px;
    list-style-type: none;
    margin:0;
}
p,a,h1,h2,h3,h4,div{
	font-size:16px;
	color:#000;
	text-decoration: none;
    font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Noto Sans JP", "Yu Mincho", YuMincho, "YuMincho", "Yu Gothic", "YuGothic", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Robot", serif;
    
}
p,a,li{
    letter-spacing: 0.5px;
	font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Noto Sans JP", "Yu Mincho", YuMincho, "YuMincho", "Yu Gothic", "YuGothic", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Robot", serif;
}

/*scrollbar*/
::-webkit-scrollbar{
    width: 15px;
}
::-webkit-scrollbar-track{
    background: #fff;
    border: none;
    border-radius: 10px;
    box-shadow: inset 0 0 0px #333333;
}
::-webkit-scrollbar-thumb{
    background: #efefef;
    border-radius: 10px;
    box-shadow: none;
}

.common_frame{
    width:100%;
    max-width:900px;
    margin:30px auto;
    padding:30px;
    box-sizing: border-box;
}
.common_frame form{
    margin:10px 0;
}
.common_frame input{
    margin:10px 0;
}

Header.css

header{
    width:100%;
    height:80px;
    background:#e0e0e0;
}
header>ul{
    width:100%;
    height:100%;
    padding:0 30px;
    box-sizing:border-box;
    display:flex;
    justify-content:space-between;
}
header>ul>li{
    height:100%;
    display:flex;
    align-items:center;
    font-weight:bold;
}
header>ul>li>a>p{
    font-size:16px;
    font-weight:bold;
}
header>ul>li>ul{
    display:flex;
    justify-content: flex-end;
}
header>ul>li>ul>li{
    margin: 0 0 0 15px;
}
header>ul>li>ul>li>a>p{
    font-size:16px;
    font-weight:bold;
}

最後に、frontend/srcディレクトリ直下のApp.jsファイルを次のように更新します。

import React, { useState,useEffect } from 'react';

//Headerコンポーネントを読み込む(拡張子は省略可)
//import Header from './modules/Header';これでもOK
import Header from './modules/Header.jsx';

function App() {
  return (
    //ReactのStrictモードでHeaderコンポーネントを表示
    <React.StrictMode>
      <Header/>
    </React.StrictMode>
  );
}

export default App;

ブラウザを確認すると、次のような画面が表示されているはずです。

React.StrictModeとは?

ReactのStrictモードとは、コードの安全性を保つための機能を有効にするためのラッパーです。
タグで囲まれたコードの中で非推奨のAPIなどが使われていると、その問題点を検知・警告してくれます。
UIの表示には関係ありませんが、使用しておいた方が無難でしょう。

Formコンポ―ネントを作る

同じ要領で、Formコンポーネントを作成します。

/frontend/src/modulesディレクトリに移動して、Form.jsxファイルを作成します。

import React, { useState } from 'react';
import '../css/common.css';

function Form(){
    return(
        <>
        <div className="Form common_frame">
            <p>ユーザー名</p>
            <input type="text" name=""></input>
            <input type="submit" name="" value="検索する" className="submit" />
        </div>
        </>
    );
}

export default Form;

/frontend/srcディレクトリのApp.jsファイルを変更して、Formコンポーネントを読み込み・表示します。

import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';

function App() {
  return (
    <React.StrictMode>
      <Header/>
      <Form/>
    </React.StrictMode>
  );
}

export default App;

ブラウザを確認すると、次のような画面が表示されているはずです。

Listコンポーネントを作る

同じ要領で、Listコンポーネントを作成します。

/frontend/src/modulesディレクトリに移動して、List.jsxファイルを作成します。

import React from 'react';
import '../css/common.css';
import '../css/List.css';

function List() {

    return (
        <>
        <div className="List common_frame">
            <ul className="item">
                <li>Day</li>
                <li>Title</li>
                <li>Content</li>
            </ul>
            <ul className="result">
                <li>12/1</li>
                <li>欲しいモノリスト</li>
                <li>きつね、たぬき、ねこ、スイカ</li>
            </ul>
        </div>
        </>
    );
}

export default List;

次に、/frontend/cssディレクトリに移動してList.cssファイルを作成します。

.List>ul{
    width:100%;
    height:40px;
    display:flex;
    flex-wrap:wrap;
}
.List>ul>li{
    display:flex;
    align-items: center;
    padding:0 10px;
    box-sizing: border-box;
}
.List>.result>li{
    background:#fff;
}
.List>ul>li:nth-child(1){
    width:10%;
}
.List>ul>li:nth-child(2){
    width:30%;
}
.List>ul>li:nth-child(3){
    width:60%;
}

/frontend/srcディレクトリのApp.jsファイルを変更して、Listコンポーネントを読み込み・表示します。

import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
import List from './modules/List';

function App() {
  return (
    <React.StrictMode>
      <Header/>
      <Form/>
      <List/>
    </React.StrictMode>
  );
}

export default App;

以上でUIの作成は完了です。ブラウザを確認すると次のような画面が表示されるはずです。

コンポーネント間でデータを受け渡す方法

続いては、Reactのコンポーネント間でデータを受け渡す方法について解説します。コンポーネント間でデータ伝送を行うテクニックは、この後の「POST通信の実装」で活用します。

本章で行う作業はすべて、Reactの理解を深めるための実験ですので実際に試す必要はありません。(試してもいいけど、終わったらもとに戻すこと)

App.jsからForm.jsxへデータを渡す

App.jsファイルに、変数を保持するためのuseStateと初期値を設定します。

const [toform, setToform] = useState({
  test : "to Form.jsx"
});

次に、変数(useState)をFormコンポーネントに渡すため、return()内のコードを次のように変更します。

//変更前
<Form />

//変更後
<Form toform={toform} />

toformという新たな変数に、先ほど設定したuseStateの変数toformが代入されました。代入されたデータはそのままFormコンポ―ネントに渡されます。

次に、Form.jsxを開いてください。データの受け渡しにはpropsと呼ばれる仕組みを利用しているので、Form関数の引数にpropsを設定します。

//変更前
function Form(){

//変更後
function Form(props) {

Form関数内で定数testを定義し、props.toform.testを代入します。

const test = props.toform.test;

ちょっと一言

propsを利用して伝送したデータは、pops.変数名.オブジェクトkeyで取り出すことができます。
オブジェクトを利用していない場合は、props.変数名で取り出せます。

最後に、データを代入した定数testを表示します。return();内のinputタグの次に、以下のコードを追加してください。

<p>{test}</p>

ブラウザを確認すると、検索するボタンの下に「to form」と表示されるはずです。

上手くいかなかった人のために、一応App.jsとForm.jsxのコードを置いておきます。

App.js

import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
import List from './modules/List';

function App() {
  const [toform, setToform] = useState({
    test:"to Form.jsx"
  });
  return (
    <React.StrictMode>
      <Header/>
      <Form toform={toform}/>
      <List/>
    </React.StrictMode>
  );
}

export default App;

Form.jsx

import React, { useState } from 'react';
import '../css/common.css';

function Form(props){
    const test = props.toform.test;
    return(
        <>
        <div className="Form common_frame">
            <p>ユーザー名</p>
            <input type="text" name=""></input>
            <input type="submit" name="" value="検索する" className="submit" />
            <p>{test}</p>
        </div>
        </>
    );
}

export default Form;

Form.jsxからApp.jsにデータを渡す

さっきと逆のことをします。

Form.jsxからApp.jsにデータを渡す場合も、必ずApp.js側でデータを保持するuseStateを設置しておく必要があります。先ほどの手順通り作業した人は、App.js側に既にuseStateが設置してあるはずですが、すでに初期値が設定してあるのでリセットします。

先ほど設置したApp.jsのuseStateの値を空白にしてください。

const [toform, setToform] = useState({
    test:""
  });

次に、FormコンポーネントにuseStateのsetToform変数を渡します。useStateのtoform変数は「データを渡すため」に利用しましたが、setToform変数は「データを受け取るため」に利用します。

<Form toform={toform} setToform={setToform}/>

最後に、Form.jsx側でも変数を保持するuseStateを設置します。useState変数を設置したら先ほど渡したsetToform変数に、toapp変数を代入します。

//useStateを定義
const [toapp, setToapp] = useState({
  test:"to App.js"
});

//setToform変数にデータを代入
props.setToform(toapp);

以上で作業完了です。ブラウザを確認すると、「to App.js」と表示されているはずです。

コンポーネント間のデータ伝送の仕組みは、なんとなくイメージできたでしょうか?

App.js

import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
import List from './modules/List';

function App() {
  const [toform, setToform] = useState({
    test:""
  });
  return (
    <React.StrictMode>
      <Header/>
      <Form toform={toform} setToform={setToform}/>
      <List/>
    </React.StrictMode>
  );
}

export default App;

Form.jsx

import React, { useState } from 'react';
import '../css/common.css';

function Form(props){
    const test = props.toform.test;
    const [toapp, setToapp] = useState({
        test:"to App.js"
    });
    props.setToform(toapp);
    return(
        <>
        <div className="Form common_frame">
            <p>ユーザー名</p>
            <input type="text" name=""></input>
            <input type="submit" name="" value="検索する" className="submit" />
            <p>{test}</p>
        </div>
        </>
    );
}

export default Form;

POST通信の実装

本章では、検索フォームに入力したキーワードを使ってMySQLからデータを取得・表示できるようにしていきます。

タイトルとURLをコピーしました