61
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

エニプラAdvent Calendar 2024

Day 9

飲食店評価アプリ作成(30分)

Last updated at Posted at 2024-12-08

はじめに

DB接続してデータを管理できる最低限のアプリを30分程度で作ります。
今回はいろいろあって、訪れた飲食店を評価し、身内におすすめするときの参考にできるアプリとしました。
あくまで骨組みまでなので、これを基に機能を追加していいものが作れたらと思います。

SQlite環境構築~テーブル作成まで

以下のURLにアクセスして環境にあったもの(今回はsqlite-tools-win32-x86-.zip)をダウンロードして、任意のフォルダに配置(今回はCドライブ直下にフォルダを作成)
https://sqlite.org/download.html
image.png

コマンドプロンプトでsqlite3.exe を置いたフォルダに移動し、以下のコマンドを順に実行

①sqlite3 restaurants.db

②CREATE TABLE IF NOT EXISTS restaurants (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, location TEXT, cuisine TEXT, rating INTEGER);

③.table

③を実行後restaurantsテーブルが作成されていることを確認する。
image.png

Node.jsでサーバ側の処理を作成

C:\eval_restaurantsフォルダを作成。以下のコマンドを実行。

npm init -y
npm install express sqlite3 cors

package.jsonファイルを作成+必要なパッケージのインストールを実施。

そしてC:\eval_restaurantsフォルダ直下にserver.jsファイルを作成して以下のコードを入力する。

const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const cors = require('cors');

const app = express();
const port = 5000;

// CORS設定
app.use(cors());
app.use(express.json());

// SQLiteデータベース接続
const db = new sqlite3.Database('./restaurants.db', (err) => {
    if (err) {
        console.error('データベース接続エラー:', err.message);
    } else {
        console.log('データベースに接続されました');
    }
});

// データベース初期設定
db.serialize(() => {
    db.run(`
        CREATE TABLE IF NOT EXISTS restaurants (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            location TEXT,
            cuisine TEXT,
            rating INTEGER
        )
    `);
});

// APIエンドポイント

// すべてのレストラン情報を取得
app.get('/api/restaurants', (req, res) => {
    db.all('SELECT * FROM restaurants', [], (err, rows) => {
        if (err) {
            res.status(500).json({ error: err.message });
            return;
        }
        res.json({ data: rows });
    });
});

// 新しいレストラン情報を追加
app.post('/api/restaurants', (req, res) => {
    const { name, location, cuisine, rating } = req.body;
    db.run(
        `INSERT INTO restaurants (name, location, cuisine, rating) VALUES (?, ?, ?, ?)`,
        [name, location, cuisine, rating],
        function (err) {
            if (err) {
                res.status(500).json({ error: err.message });
                return;
            }
            res.json({ id: this.lastID });
        }
    );
});

app.listen(port, () => {
    console.log(`サーバーがポート ${port} で起動しました`);
});

こちらを入力したそのままのパスで以下のコードを実行(cmd)するとローカルサーバが立つ。

node server.js

Reactのデフォルトアプリを作成してちょっといじる

以下のコマンドを実行してwebアプリテンプレートを作成する。

npx create-react-app restaurant-app

実行完了してhappy hacking!が表示されるとrestaurant-appフォルダが作成されるので、そのフォルダのパスに移動。
また以下のコードも実行しておく。

npm install axios

axiosはAPIへのアクセスを行うモジュール?のようです。

あとは一応見た目を気にするということでtailwindをインストールして必要な記述をする。

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

最後にApp.jsを以下のソースで書き換える。

App.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
    const [restaurants, setRestaurants] = useState([]);
    const [name, setName] = useState('');
    const [location, setLocation] = useState('');
    const [cuisine, setCuisine] = useState('');
    const [rating, setRating] = useState('');
    const [searchQuery, setSearchQuery] = useState(''); // 検索クエリを追加

    useEffect(() => {
        axios.get('http://localhost:5000/api/restaurants')
            .then((response) => {
                setRestaurants(response.data.data);
            })
            .catch((error) => {
                console.error("データの取得に失敗しました:", error);
            });
    }, []);

    const addRestaurant = () => {
        axios.post('http://localhost:5000/api/restaurants', {
            name,
            location,
            cuisine,
            rating: parseInt(rating),
        })
        .then((response) => {
            setRestaurants([...restaurants, { id: response.data.id, name, location, cuisine, rating }]);
            setName('');
            setLocation('');
            setCuisine('');
            setRating('');
        })
        .catch((error) => {
            console.error("レストランの追加に失敗しました:", error);
        });
    };

    // 検索結果の絞り込み
    const filteredRestaurants = restaurants.filter((restaurant) =>
        restaurant.name.toLowerCase().includes(searchQuery.toLowerCase())
    );

    return (
        <div className="min-h-screen bg-gray-100 p-8">
            <h1 className="text-3xl font-bold text-center mb-8 text-blue-600">訪問した飲食店の管理</h1>

            {/* 検索バー */}
            <div className="max-w-md mx-auto mb-6">
                <input 
                    type="text" 
                    placeholder="お店の名前で検索" 
                    value={searchQuery} 
                    onChange={(e) => setSearchQuery(e.target.value)} 
                    className="w-full p-2 border rounded-lg focus:outline-none focus:ring focus:ring-blue-300"
                />
            </div>

            <div className="max-w-md mx-auto bg-white shadow-md rounded-lg p-6 mb-8">
                <h2 className="text-xl font-semibold mb-4 text-gray-700">新しい飲食店を追加</h2>
                <input 
                    type="text" 
                    placeholder="店名" 
                    value={name} 
                    onChange={(e) => setName(e.target.value)} 
                    className="w-full mb-4 p-2 border rounded-lg focus:outline-none focus:ring focus:ring-blue-300"
                />
                <input 
                    type="text" 
                    placeholder="場所" 
                    value={location} 
                    onChange={(e) => setLocation(e.target.value)} 
                    className="w-full mb-4 p-2 border rounded-lg focus:outline-none focus:ring focus:ring-blue-300"
                />
                <input 
                    type="text" 
                    placeholder="料理の種類" 
                    value={cuisine} 
                    onChange={(e) => setCuisine(e.target.value)} 
                    className="w-full mb-4 p-2 border rounded-lg focus:outline-none focus:ring focus:ring-blue-300"
                />
                <input 
                    type="number" 
                    placeholder="評価 (1〜5)" 
                    value={rating} 
                    onChange={(e) => setRating(e.target.value)} 
                    className="w-full mb-4 p-2 border rounded-lg focus:outline-none focus:ring focus:ring-blue-300"
                />
                <button 
                    onClick={addRestaurant} 
                    className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition"
                >
                    追加
                </button>
            </div>

            <h2 className="text-2xl font-semibold mb-4 text-gray-700 text-center">飲食店一覧</h2>
            <ul className="max-w-md mx-auto space-y-4">
                {filteredRestaurants.map((restaurant) => (
                    <li key={restaurant.id} className="bg-white shadow-md rounded-lg p-4">
                        <h3 className="text-lg font-semibold text-gray-800">{restaurant.name}</h3>
                        <p className="text-gray-600">場所: {restaurant.location}</p>
                        <p className="text-gray-600">料理の種類: {restaurant.cuisine}</p>
                        <p className="text-gray-600">評価: {restaurant.rating}</p>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default App;

ここまで出来たら完成です。

C:\eval_restaurants\evaluate-app> npm start

npm startを実行するとアプリが立ち上がり、画面上で触ることができる。

image.png

61
6
0

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
61
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?