ポートフォリオ:学食管理アプリ
1. 作成したアプリについて
アプリ概要
- アプリ名: 学食スマートオーダー
- コンセプト: 学生や教職員が学食のメニューを事前に閲覧し、スムーズに注文できるWebアプリケーションです。
- GitHub: https://github.com/Naru9595/CafeManageApp
アプリの特徴
- メニュー閲覧機能: 学食で提供されるメニューの一覧を確認できます。
- 注文機能: ユーザーは希望のメニューを選択し、注文することができます。
- 認証機能: ユーザー登録、ログイン、ログアウト機能を備えています。
- 管理者機能: 管理者はメニューの追加、編集、削除(CRUD操作)が可能です。
要件定義
2.1 概要
本システムは、飲食店の注文から受け取りまでをデジタル化し、顧客の待ち時間短縮と店舗側の業務効率化を実現する注文管理アプリケーションである。
顧客(ユーザー)と店舗スタッフ(管理者)の双方に専用のインターフェースを提供し、リアルタイムでの情報連携を可能にすることを目的とする。
2.2 機能要件
システムの利用者に合わせて、以下の機能を提供する。
2.2.1 ユーザー向け機能
| 機能名 | 機能概要 |
|---|---|
| 認証機能 | ユーザーアカウントでのログイン・ログアウト機能。 |
| メニュー閲覧機能 | 提供されているメニューの一覧と、リアルタイムの在庫状況を確認できる。 |
| 商品注文機能 | メニューから商品を選択し、注文を確定する。注文確定後、ユニークな注文IDが発行される。 |
| 注文ステータス確認機能 | 自身の注文状況(受付、調理中、完成)をリアルタイムで確認できる。 |
| 呼び出し通知機能 | 注文した商品が完成すると、プッシュ通知や画面表示で呼び出し通知を受け取る。 |
| 商品受け取り確認機能 | 商品受け取り後、ユーザーが操作することでステータスを「受け取り済」に変更する。 |
2.2.2 管理者向け機能
| 機能名 | 機能概要 |
|---|---|
| 認証機能 | 管理者専用アカウントでのログイン・ログアウト機能。 |
| メニュー管理機能 | メニューの新規追加、内容の編集(価格、説明等)、削除(CRUD)を行える。 |
| 在庫管理機能 | メニューごとの在庫数をリアルタイムで更新・管理できる。 |
| 注文管理機能 | 現在の注文一覧をリアルタイムで確認し、調理状況(受付→調理中→完成)を更新する。 |
| ユーザー呼び出し機能 | 調理状況を「完成」に更新した際、対象のユーザーへ呼び出し通知を送信する。 |
| 売上管理機能 | 日次、月次などの期間で売上を集計・確認できる。また、売上データをCSV形式で出力できる。 |
2.2.3 非機能要件
機能要件以外に、システムが満たすべき品質や制約を定義する。
| 項目 | 内容 |
|---|---|
| ユーザビリティ |
UI: ユーザー・管理者ともに、マニュアルなしで直感的に操作できるシンプルなデザインであること。 操作性: 管理者側の操作は、店舗の業務負担を軽減するため、少ないステップで完結するように設計すること。 |
| セキュリティ |
認証: パスワードはハッシュ化して保存し、セキュアな認証プロセスを実装すること。 権限管理: ユーザーと管理者の権限を明確に分離し、相互に権限を越えた操作ができないように制御すること。 |
| パフォーマンス | 応答性: 在庫や注文状況の更新は、遅延なく画面に反映されること。リアルタイム性を確保する。 |
3. 使用技術
- バックエンド: NestJS (TypeScript)
- データベース: MySQL
- O/Rマッパー: TypeORM
- インフラ/開発環境: Docker
なぜこの技術選定なのか
NestJS (TypeScript):
サーバーサイド開発の経験があるNode.js環境で開発を進めたかったため、NestJSを採用しました。また、TypeScriptを導入することで、静的型付けによるコードの品質向上や、開発時のエラー検出を容易にできる点に魅力を感じました。
Docker:
開発環境の構築を迅速かつ簡単にするためにDockerを利用しました。docker-composeを使うことで、誰でも同じコマンドでアプリケーション、データベース(MySQL)、管理ツール(Adminer)を一度に立ち上げることができ、開発の属人性を排除しました。
4. 基本設計
ER図
このアプリケーションのデータベースは、主にユーザー (User)、注文 (Order)、メニュー (Menu) の3つの要素で構成されています。
関係性を文章で表すと以下のようになります。
- 一人の
Userは、複数のOrder(注文)を持つことができます。(一対多) - 一つの
Order(注文)には、複数のMenu(メニュー品目)を含めることができます。 - 一つの
Menu(メニュー品目)は、複数のOrder(注文)に含まれる可能性があります。
この Order と Menu の関係性は 多対多(many-to-many) となります。
ER図
User と Order が「1対多」、Order と Menu が「多対多」の関係にあり、中間テーブル(order_menus_menu)が自動的に作成されます。
テーブル定義
users テーブル
| カラム名 | データ型 | 説明 |
|---|---|---|
id |
INT | 主キー |
email |
VARCHAR | メールアドレス(重複不可) |
password_hash |
VARCHAR | ハッシュ化されたパスワード |
role |
ENUM | ユーザー権限('CUSTOMER', 'ADMIN'など) |
point |
INT | 保有ポイント(デフォルト: 0) |
createdAt |
DATETIME | 作成日時 |
updatedAt |
DATETIME | 更新日時 |
orders テーブル
| カラム名 | データ型 | 説明 |
|---|---|---|
id |
INT | 主キー |
userId |
INT |
usersテーブルへの外部キー
|
status |
ENUM | 注文状況('PENDING', 'COMPLETED'など) |
totalPrice |
INT | 合計金額 |
menuIds |
TEXT | 注文されたメニューIDの配列 |
createdAt |
DATETIME | 作成日時 |
updatedAt |
DATETIME | 更新日時 |
menus テーブル
| カラム名 | データ型 | 説明 |
|---|---|---|
id |
INT | 主キー |
name |
VARCHAR(100) | 商品名 |
description |
TEXT | 商品説明(NULL許容) |
price |
INT | 価格 |
stock |
INT | 在庫数(注文可能かどうかの指標) |
createdAt |
DATETIME | 作成日時 |
updatedAt |
DATETIME | 更新日時 |
4. このアプリを作成した背景と目的
昼休み時間の学食は券売機システムの影響で大変混雑しており、注文するまでに長い列に並ばなければなりませんでした。さらに、学食の従業員の方たちが管理しきれずにタスク処理がうまくいっていませんでした。この待ち時間を少しでも短縮し、学生や教職員が快適に昼食をとれるだけでなく従業員の方たちの仕事を行いやすく改善させる目的でこの事前注文アプリを開発しました。
5. 開発の振り返り
5.1 メニュー閲覧機能
ユーザーができること
ユーザーはアプリを開くと、学食で現在提供されているメニューの一覧を閲覧できます。各メニューには名前、価格、簡単な説明が表示されており、ユーザーは直感的に食べたいものを選べます。
実装のポイント
-
バックエンド (NestJS):
/menusというAPIエンドポイントを作成。リクエストを受けるとMenusServiceがTypeORMを通じてデータベースから全てのメニュー情報を取得し、JSON形式でフロントエンドに返します。 -
フロントエンド (React): ページ読み込み時にバックエンドの
/menusへリクエストを送信。受け取ったメニューデータの配列を元に、map関数を使って各メニューをカード形式のUIコンポーネントとして一覧表示します。
5.2 注文機能
ユーザーができること
ユーザーはメニュー一覧から好きな商品を複数選択し、「注文する」ボタンを押すことで注文を確定できます。注文後は、自分の注文履歴を確認することも可能です。
実装のポイント
-
バックエンド (NestJS): 注文処理を行う
/ordersエンドポイントは、認証済みのユーザーのみがアクセスできるようJWT認証ガードで保護します。フロントエンドから送られてきたメニューIDの配列 (menuIds) を受け取り、OrdersServiceが以下の処理を実行します。- メニューIDを元にデータベースからメニュー情報を取得。
- 取得したメニューの価格を合計して、注文の合計金額を算出。
- 「誰が」「何を」「いくらで」注文したかという情報を
Orderエンティティとしてデータベースに保存します。
-
フロントエンド (React): ユーザーが選択したメニューのIDをstateで管理。注文ボタンが押されたら、ログイン時に取得したJWTをAuthorizationヘッダーに付与し、選択したメニューIDの配列をリクエストボディに含めて
/ordersエンドポイントにPOSTリクエストを送信します。
5.3 認証機能
ユーザーができること
アプリを利用するために、ユーザーはメールアドレスとパスワードで新規登録ができます。登録後はログインして注文などの機能を利用でき、使い終わったらログアウトも可能です。
実装のポイント
- バックエンド (NestJS): ユーザー登録時にはパスワードをハッシュ化して安全にデータベースへ保存します。ログイン時には、入力されたパスワードとハッシュ化されたパスワードを比較して認証。認証が成功すると、ユーザー情報を含んだ**JWT(JSON Web Token)**を生成してフロントエンドに返します。
-
フロントエンド (React): ログイン成功時にバックエンドから受け取ったJWTを安全な場所に保管。以降、注文機能など認証が必要なAPIへのリクエストを行う際には、必ずこのJWTをリクエストヘッダーに含めて送信します。ログアウト時には保管していたJWTを削除します。
5.4 管理者機能
ユーザーができること
管理者権限を持つユーザーは、一般ユーザーには見えない専用の管理画面にアクセスできます。この画面から、メニューの新規追加、既存メニューの情報(価格、在庫など)の編集、提供終了したメニューの削除といったCRUD操作(作成、読み取り、更新、削除)を行えます。
実装のポイント
-
バックエンド (NestJS): メニューを操作する各APIエンドポイント(例:
POST /menus,PATCH /menus/:id)に、管理者専用の認証ガードを設置。このガードは、リクエストに含まれるJWTのペイロードを解析し、ユーザーのroleがadminであるかを確認します。管理者でないユーザーからのアクセスは、この時点でブロックされます。 -
フロントエンド (React): ログインしたユーザーのロール情報をもとに、管理者であれば「管理画面へ」のボタンを表示するなどの出し分けを行います。管理画面では、メニュー情報を入力するフォームや、編集・削除ボタンを備えたUIを提供します。
苦労したこと
-
Dockerコンテナ間の連携:
開発当初、NestJSのコンテナからMySQLのコンテナへ接続する際にエラーが発生しました。原因は、docker-compose.yml内でのコンテナの起動順序やネットワーク設定の不備でした。depends_onを使ってコンテナの依存関係を定義し、正しいネットワーク設定を行うことで解決しました。この経験を通じて、Dockerのネットワーキングに関する理解を深めることができました。 -
非同期処理の実装:
データベースへの問い合わせなど、時間のかかる処理は非同期で行う必要がありました。NestJSとTypeORMにおける非同期処理(Promise/async/await)の扱いに慣れるまで試行錯誤しましたが、公式ドキュメントを読み込むことで正しく実装できるようになりました。
6. 今後の課題
- 決済機能の導入: 現在は注文機能のみですが、将来的にはキャッシュレス決済を導入し、注文から支払いまでをアプリ内で完結させたいです。
- UI/UXの改善: 画像などを見られるようするなど、より直感的で使いやすい画面を作成したいです。
- モバイルアプリ化:ユーザがより使いやすくするためにモバイルアプリに移行させたいです。