0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LEMP環境で、PHPとReact(TypeScript)を使って、初心者でもできる簡単な掲示板アプリの作り方

Last updated at Posted at 2024-11-26

プログラミングの勉強方法として、自分で、簡単なアプリを作成しようと考えている方がいるかと思います。

しかし、どんなアプリを作れば良いか分からない方も多いかと思います。

今回は、そんな方の力になれれば良いと思い、

筆者が、作成したphpやTypeScriptを使って、簡単な掲示板アプリを作る方法を公開します。

作成する掲示板は掲示板は、データベースを利用する簡単な掲示板

  使用言語  バックエンド:PHP  ※フレームワークは使用しない
フロントエンド: React、typescript

  データベース:MySQL
 
 ▼WEBシステム
  投稿内容表示ページ
   Reactのsrc/App.tsx
   
開発環境は、Dockerを使い、LEMP環境で行う。

まずは、ターミナルで、

スクリーンショット 2024-11-26 17.20.03.png

keijibanというディレクトリを作成。

$mkdir keijiban
$cd keijiban

※ $マークは、無視していいです。(ターミナルで入力するという意味です。)

でkeijibanディレクトリに移動

frontディレクトリにReactをインストール

事前準備

# インストール
$ brew install nodebrew
# nodebrewがインストールされたか確認
$ nodebrew -v
# npmも一緒にインストールされているか確認
$ npm -v
# セットアップ
$ nodebrew setup
# 以下のexport文を~/.zshrcもしくは~/.bash_profileに追記
$ vim ~/.zshrc
# export PATH=$HOME/.nodebrew/current/bin:$PATH
# 反映
$ source ~/.zshrc

nodeのインストール

$ nodebrew install stable
# インストール済みのnodeバージョンの一覧を表示
$ nodebrew ls
$ nodebrew use 使用するnodeのバージョン
# ex) nodebrew use v18.8.0

まずは、nodeのバージョンの確認

$ node -v

yarnをインストール

$ npm install --global yarn
$ yarn --version

React.jsの場合はこちら↓

$ yarn create react-app frontend

React + TypeScriptの場合はこちら↓

$ yarn create react-app frontend --template typescript

作成したプロジェクト配下に移動し、下記のstartコマンドを実行します。

$ cd  frontend
$ yarn start

実行後、ブラウザが起動し、以下の画面が表示されていればOKです。

新規メモ.jpeg

dockerのインストール

こちらのサイトを参考にしてみるといいです。↓

Dockerが、ちゃんとインストールできているかは、

$docker --version

で確認

1.フロントエンド

APIを利用するコンポーネント

1-1. 入力フォームを実装するために使う関数から書いていきます。

keijiban/frontend/src/App.tsx
import './App.css'; 
import React, { useEffect, useState } from 'react'; 
// Postインターフェースの定義 
interface Post {
post_id: number;  // 投稿のID 
author_name: string;  // 投稿者の名前 
title: string;  // 投稿のタイトル
 content: string;  // 投稿の内容 
created_at: string;  // 投稿日時
 } 
const App: React.FC = () => { 
// ステートの定義 
const [posts, setPosts] = useState<Post[]>([]); // 投稿リストの状態 
const [page, setPage] = useState<number>(1);  // 現在のページ番号 
const[message, setMessage] = useState<string>(''); // メッセージの状態 
const [newPost, setNewPost] = useState<{ author_name: string; title: string; content: string}>({ author_name: '', title: '', content: ''}); // 新しい投稿の状態

 // コンポーネントがマウントされたときにデータをフェッチするためのuseEffect 

useEffect(() => { const fetchData = async () => { try { const response = await fetch(`http://localhost:8080/index.php?page=${page}`); 
// 投稿を取得するためのリクエスト
 if (!response.ok) throw new Error('ネットワークエラー');
 // エラーチェック 
const data = awaitresponse.json(); 
// JSONデータをパース 
setPosts(data.posts || []); 
// 投稿データを設定
 setMessage(data.message); 
// サーバーからのメッセージを設定 
} catch (error) { console.error(error); 
// エラーをコンソールに表示setMessage('データの取得に失敗しました。'); 
// エラーメッセージを設定 } }; fetchData(); // データのフェッチを実行 }, [page]); // pageが変更されたときに再実行

1-2.投稿を送信する関数(handleSubmit)を作成

keijiban/frontend/src/App.tsx
// 投稿の送信
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const response = await fetch('http://localhost:8080/post.php', {
        method: 'POST',
        headers: {'Content-Type':  'application/x-www-form-urlencoded' },
        body: new URLSearchParams(newPost).toString(),
      });
      if (!response.ok) throw new Error('送信エラー');
      const data =await response.json();
      if (data.message) {
          setPosts(prevPosts => [
              ...prevPosts,
              {
                  post_id: Date.now(),
                  author_name:  newPost.author_name,
                  title: newPost.title,
                  content: newPost.content,
                  created_at: new Date(). toISOString(), // 現在の日時を設定
              },
          ]);
          setNewPost({ author_name: '', title: '', content: ''}); // 入力フォームをリセット
      }
      
      setMessage(data.message);
      setPage(1);
    } catch (error) {
      console.error('送信するのにエラーが発生しました。', error);
      setMessage('投稿に失敗しました。');
    }
  };

1-3. handleSubmit関数のあとに、投稿を削除する関数(handleDelete)を作成

keijiban/frontend/src/App.tsx
const handleDelete = async (postId: number) => {
    try {
      const response = await fetch('http://localhost:8080/delete.php', {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ post_id: postId.toString() }).toString(),
      });
      const data = await response.json();
      setMessage(data.message); // メッセージを設定
      // 投稿一覧を更新
      setPosts(prevPosts => prevPosts.filter(post => post.post_id !== postId));
    } catch (error) {
      console.error('削除中にエラーが発生しました。', error);
      setMessage('削除に失敗しました。');
    }

1-4. タイトルと投稿内容、「投稿する」ボタン、投稿一覧までのデザイン、ページネーションまでを作成します。

keijiban/frontend/src/App.tsx
return (
    <div>
        <h1>掲示板</h1>
        <form onSubmit={handleSubmit}>
         <div className="form-container">
            <input type="text" value={newPost.author_name} onChange={e => setNewPost({ ...newPost, author_name: e.target.value })} placeholder="投稿者" required />
            <input type='text' value={newPost.title} onChange={e => setNewPost({ ...newPost, title: e.target.value})} placeholder="タイトル" required />
            <textarea value={newPost.content} onChange={e => setNewPost({ ...newPost, content: e.target.value})} placeholder="ここにメッセージを入力してください。" required />
            </div>
            <button type="submit" className="submit-button">投稿する</button>
        </form>
        
        {message && <div className="error">{message}</div> }
        <h2>投稿一覧</h2>
        {posts.map(post => (
            <div className="post" key={post.post_id} >
                <h3>{post.title}</h3>
                <p>{post.content}</p>
                <p>投稿者: {post.author_name} | 投稿日時: {post.created_at}</p>                
                    <input type="hidden" name="post_id" value={post.post_id} />
                    <button onClick={() => handleDelete(post.post_id)}  type="submit" className='delete-button'>削除</button>
            </div>
        ))}
        <div className="button-container">
          {page > 1 && (
            <button onClick={() => setPage(prev => Math.max(prev - 1, 1))} className="prev-button">前へ</button> 
            )}
          {page < totalPages && (
            <button onClick={() => setPage(prev => prev + 1)} className="next-button">次へ</button>
          )}
        </div>
      </div>
  );

これらを繋ぎ合わせると、

Reactのsrc/App.tsxの全体のコードはこちらになります

keijiban/frontend/src/App.tsx
import './App.css';
import React, { useEffect, useState }from 'react';
interface Post {
  post_id: number;
  author_name: string;
  title: string;
  content: string;
  created_at: string;
}
const App: React.FC = () => {
  const [posts, setPosts] = useState<Post[]>([]);
  const [page, setPage] = useState<number>(1);
  const [message, setMessage] = useState<string>('');
  const [newPost, setNewPost] = useState<{ author_name: string; title: string; content: string}>({ author_name: '', title: '', content: ''});
  // 総ページ数の状態を追加
  const [totalPages, setTotalPages] = useState<number>(1);
  useEffect(() => {
  
  console.log('Current Page:', page);
  console.log('Total Pages:', totalPages);
  console.log('Show Previous:', page > 1);
  console.log('Show Next:', page < totalPages);
    
    const fetchData = async () => {
      console.log('データを送信しました。');
      try {
        const response = await fetch(`http://localhost:8080/index.php?page=${page}`);
        if (!response.ok) throw new Error('ネットワークエラー');
        const data = await response.json();
        
        setPosts(data.posts || []);
        setMessage(data.message);
        setTotalPages(data.total_pages); // 総ページ数を設定
        console.log('Total Page :', data.total_pages);
      } catch (error) {
        console.error(error);
        setMessage('データの取得に失敗しました。');
      }
    };
    fetchData();
  }, [page]);
  
  // 投稿の送信
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const response = await fetch('http://localhost:8080/post.php', {
        method: 'POST',
        headers: {'Content-Type':  'application/x-www-form-urlencoded' },
        body: new URLSearchParams(newPost).toString(),
      });
      if (!response.ok) throw new Error('送信エラー');
      const data =await response.json();
      if (data.message) {
          setPosts(prevPosts => [
              ...prevPosts,
              {
                  post_id: Date.now(),
                  author_name:  newPost.author_name,
                  title: newPost.title,
                  content: newPost.content,
                  created_at: new Date(). toISOString(), // 現在の日時を設定
              },
          ]);
          setNewPost({ author_name: '', title: '', content: ''}); // 入力フォームをリセット
      }
      
      setMessage(data.message);
      setPage(1);
    } catch (error) {
      console.error('送信するのにエラーが発生しました。', error);
      setMessage('投稿に失敗しました。');
    }
  };
  // 投稿の削除
  const handleDelete = async (postId: number) => {
    try {
      const response = await fetch('http://localhost:8080/delete.php', {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ post_id: postId.toString() }).toString(),
      });
      const data = await response.json();
      setMessage(data.message); // メッセージを設定
      // 投稿一覧を更新
      setPosts(prevPosts => prevPosts.filter(post => post.post_id !== postId));
    } catch (error) {
      console.error('削除中にエラーが発生しました。', error);
      setMessage('削除に失敗しました。');
    }
  };
  
  return (
    <div>
        <h1>掲示板</h1>
        <form onSubmit={handleSubmit}>
         <div className="form-container">
            <input type="text" id="author_name" value={newPost.author_name} onChange={e => setNewPost({ ...newPost, author_name: e.target.value })} placeholder="投稿者" required />
            <input type='text' id="title" value={newPost.title} onChange={e => setNewPost({ ...newPost, title: e.target.value})} placeholder="タイトル" required />
            <textarea id="content" value={newPost.content} onChange={e => setNewPost({ ...newPost, content: e.target.value})} placeholder="ここにメッセージを入力してください。" required />
            </div>
            <button type="submit" className="submit-button">投稿する</button>
        </form>
        
        {message && <div className="error">{message}</div> }
        <h2>投稿一覧</h2>
        {posts.map(post => (
            <div className="post" key={post.post_id} >
                <h3>{post.title}</h3>
                <p>{post.content}</p>
                <p>投稿者: {post.author_name} | 投稿日時: {post.created_at}</p>                
                    <input type="hidden" name="post_id" value={post.post_id} />
                    <button onClick={() => handleDelete(post.post_id)}  type="submit" className='delete-button'>削除</button>
            </div>
        ))}
        <div className="button-container">         
        {page > 1 && (
            <button onClick={() => setPage(prev => Math.max(prev - 1, 1))
              
            } className="prev-button">前へ</button> 
            )}
          {/*ページ番号の表示*/}
          {Array.from({ length: totalPages}, (_, index) => (
            <button
            key={index + 1}
            onClick={() => setPage(index + 1)}
            className={`page-button ${page === index + 1 ? 'active-page' : ''}`}
            >
              {index + 1}
            </button>
          ))}
          
        {page < totalPages && (
            <button onClick={() => setPage(prev => prev + 1)
            } className="next-button">次へ</button>
        )}          
        </div>
      </div>
  );
};
export default App;

このままでは、デザインが無茶苦茶な状態なので、

frontendディレクトリ内にある
srcディレクトリ内のApp.cssを書き換えて、

デザインを整えていきます。

デザインの整理

keijiban/frontend/src/App.css
form-container {
  align-items: center;
  display: flex;
  flex-direction: column; /* 縦に並べるための設定 */
  margin-bottom: 20px;
}
h1, h2, h3 {
  text-align: center;
}
textarea {
  margin:  10px 0; /* 上下に余白を追加 */
  width: 100%;
  height: 80px;
  padding: 8px;
  border: 1px solid #ccc; /* ボーダーの設定 */
  border-radius:  4px; /* 角を丸める */
}
input {
  margin:  10px 0; /* 上下に余白を追加 */
  width: 50%;
  padding: 8px;
  border: 1px solid #ccc; /* ボーダーの設定 */
  border-radius:  4px; /* 角を丸める */
}
.post {
  max-width: 600px;  /* 投稿の最大幅を設定 */
  margin:  20px auto; /* 上下の余白を20px、左右の余白を自動にして中央揃え */
  padding:  20px; /* 内側の余白 */
  border: 1px solid #ccc; /* 境界線 */
  border-radius:  8px; /* 角を丸める */
  background-color:  #f9f9f9; /* 背景色 */
  box-shadow: 0 2px 4px rgba(0,0,0, 0.1) /* 影を追加 */
}
form {
  display: flex;
  flex-direction: column; /* 縦に並べる */
  align-items: center; /* 中央揃え */
}
button.submit-button {
  text-align:center;
  width: 100px;
  background-color: green;
  font-weight: bold;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 20px;
}
button.submit-button:hover {
  background: darkgreen;
}
.button-container {
  display: flex;
  justify-content: center;
  margin-top: 20px;
}
button.prev-button {
  width: 80px;
  font-weight: bold;
  background-color: gray;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}
button.prev-button:hover {
  background-color: darkgray;
}
button.next-button {
  width: 80px;
  font-weight: bold;
  background-color: blue;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}
button.next-button:hover {
  background-color: darkblue;
}
button.delete-button {
  display: block; /* ボタンをブロック要素に */
  margin: 0 auto; /* 自動余白で中央揃えで */
  width: 80px;
  font-weight: bold;
  background-color: red;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}
button.delete-button:hover {
  background-color: darkred;
}
.submit-button, .prev-button, .next-button, .delete-button {
  align-items: center;
  margin: 0 20px; /* ボタン同士の間隔を調整 */
}
.error {
  margin-top: 20px;
  text-align: center;
  color: red;
}
.active-page {
  font-weight: bold;
  color: blue;
  border-bottom: 2px solid blue;
}
.page-button {
  font-size: 18px;
  background-color: transparent; /* 背景を透明にする */
  border: none; /* ボーダーをなくす */
  color: blue;
  cursor: pointer;
  margin: 0 15px; /* 左右に10pxの余白を追加 */
}
.page-button:hover {
  text-decoration: underline;
}

すると、図のようなデザインになります。

掲示板.jpg

2.Docker環境の準備

Dockerを起動させただけで、バックエンドであるPHPとフロントエンドであるReact両方を立ち上げるようにします。

DockerコンテナのIPアドレスの調べ方

1.コンテナのIDまたは名前を取得します。

$ docker ps
  1. 特定のコンテナの詳細を表示
$ docker inspect <コンテナIDまたは名前>
  1. 出力の中から、"Networks"セクションを探し、"IPAddress"の値を確認する。

ディレクトリの構造

keijiban/
├── docker-compose.yml
├── Dockerfile
├── nginx/
│   └── default.conf
├── php/
│   ├── index.php
│   ├── post.php
│   └── delete.php
├── mysql/
│   └── init.sql
└── frontend/
    ├──(Reactアプリケーション)
    ├──Dockefile

2-1.docker-compose.ymlの作成

keijiban/docker-compose.yml
version : '3.8'
services:
  nginx:
    image: nginx:alpine
    ports:
     - "8080:80"
    volumes:
     - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
     - ./php:/var/www/html
    depends_on:
     - php
  php:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./php:/var/www/html
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: keijiban
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "3306:3306"
  frontend:
    build:
      context: ./frontend
    ports:
      - "3000:80"

2-2.Dockerfileの作成

keijiban/Dockerfile
FROM php:8.3-fpm
# PHP拡張をインストール
RUN docker-php-ext-install mysqli pdo pdo_mysql

frontend ディレクトリ内にも Dockerfile を作成

frontend/Dockerfile

keijiban/frontend/Dockerfile
# Node.jsの公式イメージを使用
FROM node:16
# アプリケーションの作業ディレクトリを作成
WORKDIR /app
# 依存関係をインストールするためにpackage.jsonをコピー
COPY package*.json ./
# 依存関係をインストール
RUN npm install
# 残りのアプリケーションコードをコピー
COPY . .
# アプリケーションをビルド
RUN npm run build
# ビルドされたアプリケーションを提供するためにnginxを使用
FROM nginx:alpine
COPY --from=0 /app/build /usr/share/nginx/html
# ポート80を開放
EXPOSE 80
# Nginxを起動
CMD ["nginx", "-g", "daemon off;"]

2-3.Nginx設定ファイル

keijiban/nginx/default.conf
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html index.htm;
    location / {
        # CORSの設定
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type' always;
        # OPTIONSメソッドの処理
        if ($request_method = 'OPTIONS') {
            add_header 'Content-Length' 0;
            return 204; #Contentがない場合
        }
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    location ~ /\.ht {
        deny all;
    }
}

2-4.掲示板アプリケーションで必要なテーブルを作成するためのSQLコードを記述(MySQL初期化スクリプト)

keijiban/mysql/init.sql
-- データベースを使用
CREATE DATABASE IF NOT EXISTS keijiban;
USE keijiban;

-- 投稿内容テーブルの作成
CREATE TABLE IF NOT EXISTS posts (
    post_id INT AUTO_INCREMENT PRIMARY KEY,
    author_name VARCHAR(50) NOT NULL,
    title VARCHAR(100) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 初期データの挿入
INSERT INTO posts (author_name, title, content) VALUES
('テストユーザー', '初投稿', 'これはテストメッセージです。'),
('ユーザー1', '投稿1', 'これは投稿1の内容です。'),
('ユーザー2', '投稿2', 'これは投稿2の内容です。');

3.バックエンド

3-1.データベースと連携し、 Reactにページネーションを反映させるための処理

keijiban/php/index.php
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
$mysqli = new mysqli("mysql", "user", "password", "keijiban");
if ($mysqli->connect_error) {
    die("Connection failed: " . $mysqli->connect_error);
}
// ページネーションの作成
$limit = 20;
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($page - 1) * $limit;
// 投稿の取得
$result = $mysqli->query("SELECT * FROM posts ORDER BY created_at DESC LIMIT $limit OFFSET $offset");
if (!$result) {
    die("クエリエラー: " . $mysqli->error);
}
$total_posts_result = $mysqli->query("SELECT COUNT(*) FROM posts");
$total_posts = $total_posts_result->fetch_row()[0];
$total_pages = ceil($total_posts / $limit);
// 取得したデータを配列に格納
$posts = [];
while ($row = $result->fetch_assoc()) {
    // post_idを整数に変換
    $row['post_id'] = (int)$row['post_id'];
    $posts[] = $row;
}
// JSON形式でレスポンスを返す
echo json_encode([
    'posts' => $posts,
    'message' => isset($_GET['message']) ? htmlspecialchars($_GET['message']) : '',
    'total_pages' => max(1, $total_pages),// 総ページを追加
])
?>

3-2. データベースに投稿者やタイトル、コメントや内容などを送り、その内容をReactに反映するための処理

keijiban/php/post.php
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
// MySQL接続
$mysqli = new mysqli("mysql", "user", "password", "keijiban");
if ($mysqli->connect_error) {
    die("Connection failed: " . $mysqli->connect_error);
}
// OPTIONSリクエストの処理
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header("HTTP/1.1 204 No Content");
    exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    header('Content-Type: application/json');
    //入力値の取得
    $author_name = $_POST['author_name'] ?? '';
    $title = $_POST['title'] ?? '';
    $content = $_POST['content'] ?? '';
    // ブレースホルダーを使ったクエリの準備
    $stmt = $mysqli->prepare("INSERT INTO posts (author_name, title, content) VALUES (?, ?, ?)");
    if (!$stmt) {
      echo json_encode(['message' => '処理できませんでした。: ' . $mysqli->error]);
      exit;
    }
    $stmt->bind_param("sss", $author_name, $title, $content);
    if($stmt->execute()){
      echo json_encode(['message' => 'メッセージを送信しました。']);  
    } else {
      echo json_encode(['message' => 'メッセージが送れませんでした。', 'error' => $stmt->error]);
    }
    
    $stmt->close();
}
$mysqli->close();
?>

3-3.データベース内に残った投稿内容や Reactで表示されている投稿内容の削除を反映するための処理

keijiban/php/delete.php
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");
$mysqli = new mysqli("mysql", "user", "password", "keijiban");
if ($mysqli->connect_error) {
    echo json_encode(["error" => "Connection failed: " . $mysqli->connect_error]);
    exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0;
    
    if ($post_id > 0) {
        if ($mysqli->query("DELETE FROM posts WHERE post_id = $post_id")) {
            echo json_encode(["message"=> "削除成功"]);
        } else {
            echo json_encode(["error" => "削除失敗"]);
        }
    } else {
        echo json_encode(["error" => "無効なID"]);
    }
} else {
    echo json_encode(["error" => "無効なリクエスト"]);
}
$mysqli->close();
?>

4.Dockerの起動

$ docker-compose up -d –build

下の画像のようなメッセージが出れば成功です。

transferring.jpeg

4-1. PHPファイルの存在確認

以下のコマンドを実行して、Docker内にコンテナ内に post.php が存在することを確認します。

$ docker-compose exec php ls /var/www/html

4-2. MySQLに接続できているか確認するコマンド

$ docker exec -it keijiban-mysql-1 mysql -u root -p

パスワード名:password

  1. データベースを選択
    接続後、以下のコマンドでデータベースを選択します。
USE keijiban;
  1. テーブルの状態を確認
    次に、テーブルの一覧を表示するために以下のコマンドを実行します。
SHOW TABLES;

特定のテーブルの内容を確認したい場合は、次のようにします。

SELECT * FROM posts;

このような内容になっていたら、大丈夫です。

AD_4nXe04Qs-PEhat5C6gqI2iG7daTNdD3rZbw6jQvQe6Ip-ZDoecp9mNtKT5GQW_wa3tzOWZZj3xgE9fSMQ2YD30-ivTNj54eNMN-iOeaod6HiOOlqjVqSsXECF.jpg

5. アプリを起動

書いたコードをテストをする際は、

ブラウザで、http://localhost:3000と入力してください。

掲示板のフォームに以下の内容で入力

AD_4nXevP3vAB_CNdQg1t7QnHQN_1n9t63aXVMqIFT-V4KJz9xCgqN3szpetgYy6lcodYXke2wSnxfIkq1OyVg9SjIVNUF3QRnO26qH3EzI-kh5zipXkFpVuj3bH.jpg

「投稿する」ボタンを押すと、

AD_4nXc6xPM5LEStRdpe0yQTzVuQLWd_PAHyzrXKSd2KkUMUW90gNlAjFjJ5MN7ORgSsnNfH_sj7heBBG-6HsiQFsU6JV_H6bbwJhQj5PJEYZJ72cVZkXCzjUwop.jpg

ちゃんと反映されました。

連続で投稿しても投稿一覧に履歴が残っていってます。

AD_4nXegg16iyxAybXNpsio6Qv2UwegKXJ5Bhvxmq_Ts2CuWelIbuQMZuwmHUifcnIuOw-3iLVALzGIwyvcOnnAjkACX_MUOqx0uF_l_jBFTHIvEnL83-MIN0qul.jpg

これらの画面が表示されれば、ちゃんと実装できています。

そして、20件以上投稿すると、

ここにメッセージを入力してください。.jpg

AD_4nXcrBX2nCeAQs7mdvpabYJKz3IrU5EeD-8yFE5DBePLP2KQB12FeTUUrESp97K9UISP2Sr8ZMdhMTLDHpA-T_tW4oXGFx16M1Eagrqdx2kyWt9PgA7Fl3QB0.jpg

AD_4nXcnz81iDRnXiOyYnpYkM6H8YyeV42eYrc2WmBRdBx7KWR-RbDNhmrtbIBGle3WQR-gVR5bsik-LvIps1RXDPSVWEmSFXAQp7CSIk9kqB5O5kSxoPuGW_Kvv.jpg

ページネーションもちゃんと表示されています。

もし、20件以上投稿しても、ページネーションが表示されず、投稿が増えていく場合は、

サイトを出て、再度、http://localhost:3000と入力すると、

ページネーションがちゃんと反映されます。

※ ページネーションの反映には、15分ほどかかる場合があります。

データベースを利用した簡単な掲示板アプリは、これで完成です。
では、これをgithubに上げていきましょう。

エンジニア業界に就職するには、自分がどんなものが作れるか、どれくらいコードが書けるかなどスキルを証明する必要があります。

githubは、就職する際に、それらを示すための必要なツールです。

githubに上げるやり方は、こちらのサイトを参考にすると分かりやすいと思います。↓

下のような画面が出てきたら、成功です。

export.jpeg

ここまでの作業内容や掲示板アプリのディレクトリ構造が、この下のURLに記録されます。↓

次回は、この掲示板アプリを改良して、簡単な勤怠管理アプリを作成していきたいと思います。

0
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?