3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【未経験・独学】教員と学生のための振り返り学習プラットフォームを開発しました【Nuxt / Rails / Tailwind CSS / Firebase Authentication / GitHub Actions】

Last updated at Posted at 2024-12-22

はじめに

初めまして。

未経験から独学でWebエンジニアを目指している、

かせい(@kaseispace)と申します。

この度、対話型振り返りシステム「ReCap」を開発しましたので、ご紹介させていただきます。

興味を持っていただけたら、開発したアプリを触っていただけると嬉しいです。また、簡単なアンケートにもご協力いただければさらに感謝いたします。アンケートは以下のリンクからご意見をお寄せください。

皆様のフィードバックをお待ちしております。

ポートフォリオの紹介

サイトのOGP画像

サービス概要

「ReCap」は、教員と学生のための振り返り学習プラットフォームです。
教育現場での利用を想定しており、教員が作成したプロンプトに基づき、学生はチャット形式で対話を通じて振り返りを行います。対話型の振り返りは、学生が自分の考えを自由に表現しやすいという特徴があります。振り返りの内容がすべて提出された後には、個別のフィードバックを受け取ることができます。これにより、学びを深め、新たな気づきを得るサポートをしています。

サービスURL

推奨端末について
ReCapはレスポンシブ対応しておりますが、システムの特性上、PCでの利用を推奨しております。

システムの特性上、教員と学生がいて初めて成り立ちます。
これに伴い、開発者側であらかじめ「フルスタック大学」で教員アカウントを作成し、いくつかの授業を登録しておきました。

振り返りだけを試してみたい方は、「フルスタック大学」で学生アカウントを作成し、以下の中から好きな授業に参加してください。
ただし、授業が開講されている曜日でないと振り返りができないので、その点にご注意ください。

現在、ReCapの登録は大学に限定されています。そのため、大学以外の学校(中学校、高校、専門学校など)は登録できませんので、ご了承ください。

授業名 クラスコード 開講日 
ソフトウェア開発概論 OJ7jpW9
セキュリティ対策の基本 chmzLAo
フロントエンド基礎 PTiKY63
Java入門 Bt0z5E5
API設計の基本 Bg6lZNa
TypeScript入門 EwPBXjR
Ruby入門 DswJDSU

Figmaで体験する「ReCap」

「ReCap」の使い勝手を簡単に体験していただくために、Figmaを使用したプロトタイプを作成しました。アカウント作成の手間を省き、まずは気軽に操作感を試したい方に最適です。このプロトタイプで「ReCap」の魅力を感じたら、ぜひ実際のシステムも試してみてください。

GitHubリポジトリ

フロントエンド

バックエンド

開発背景

私が初めて振り返りの大切さを実感したのは、学生時代の授業で振り返りをする機会があったときです。それまでは、勉強したことをそのままにして、振り返ることなんてほとんどありませんでした。

でも、その授業では、授業後に振り返りをすることが求められていて、私は言われるがままに振り返りを行っていました。当時はその重要性をあまり感じていませんでしたが、後でその振り返りを見返すと、自分の学びをもう一度確認することができ、深く理解する助けとなっていることに気づきました。

特に、テスト対策のときに振り返りがすごく役立ちました。自分ができなかったことに対して振り返ることで、理解が足りなかった部分をはっきりさせて、効率よく復習することができました。

この経験から、振り返りは学習効果を高めるためにとても重要な手法であることを痛感しました。

振り返り学習は、学んだことをもう一度見直して、次の学習に活かす大切なプロセスです。でも、多くの学生は勉強したことをそのままにしてしまい、振り返りをすることが少ないのが現実です。

「ReCap」の目的

「ReCap」は、振り返りの導入や改善を考えている教員の皆様に向けて開発しました。このアプリは、学生たちが自分の学びを振り返り、より効果的に活用できるよう支援することを目的としています。

「ReCap」の特徴

  • チャット形式の振り返り:学生が自由に考えを表現しやすい対話型の振り返り
  • 個別フィードバック:振り返り内容に基づくフィードバックで学びを深める
  • クラスの振り返り共有:クラス全体の振り返りを閲覧し、他の学生の意見や気づきを共有
  • 新たな気づきを得る:他人の振り返りから学び、自己成長を促進

システムの利用イメージ

利用イメージ画像

1. 授業開始前(教員向け)
まず、教員は授業前に以下の準備を行います。

  • システムに授業の日付を登録
  • 授業内容に合わせた振り返り用のプロンプト(質問)を作成し、生徒に公開

2. 授業中(学生向け)
授業が進行する中で、学生は以下の活動を行います。

  • 授業中、教員が振り返りの時間を指示します。そのタイミングで、学生は自分の学びを振り返り、システムに記録します。このとき、教員が提示したプロンプト(質問)に沿って入力

3. 振り返り終了後(教員・学生向け)
振り返りが完了した後、以下の活動を実施します。

  • 教員
    • システムを通じて学生の振り返り状況を確認
  • 学生
    • システムからのフィードバックを確認し、必要に応じて振り返り内容を修正
    • 他の学生の振り返りを参考にし、自分の学びを深める

機能紹介 - 教員ページ

担当授業登録機能 振り返りプロンプト作成機能
担当授業登録 振り返りプロンプト作成
担当授業の登録を行ってください。
登録後、授業ページにてクラスコードが発行されます。 このクラスコードを学生に共有してください。
学生に振り返らせたいプロンプトの登録をしてください。
プロンプトは複数登録可能です。
振り返りプロンプト公開機能 振り返りプレビュー機能
振り返りプロンプト公開 振り返りプレビュー
作成したプロンプトを有効化すると、学生に公開されます。
有効化後、チャットアイコンが表示されます。
チャットアイコンをクリックすると、プレビューを確認できます。
授業日登録機能 振り返り履歴閲覧機能
授業日登録 振り返り履歴閲覧
授業日の登録を行ってください。
現在の授業日が終了するまで、次の授業日の登録はできません。
編集・削除は可能です。
学生が各授業回で行った振り返り内容を閲覧できます。

機能紹介 - 学生ページ

授業参加機能 振り返り登録・閲覧機能
授業参加 振り返り登録・閲覧
教員から共有されたクラスコードを入力してください。
クラスコードが一致すると、そのクラスへの参加が完了します。
チャットアイコンをクリックします。
振り返りが開始され、指示に従って振り返りを行います。
全ての振り返りが終了後、振り返り履歴から登録内容を確認できます。また、振り返りに対してフィードバックが返されます。
振り返り編集機能 みんなの振り返り機能
振り返り編集 みんなの振り返り
登録済みの振り返りをフィードバックや他の学生の振り返りを参考にして編集できます。 他の学生の振り返りを閲覧できます。
振り返りは内容のみが表示されますが、カードをクリックすると質問を含めた全文や省略された部分が表示されます。

使用技術

カテゴリ 技術
フロントエンド Vue 3.5.11 / Nuxt 3.13.2 / TypeScript 5.6.2
バックエンド Ruby 3.3.4 / Ruby on Rails 7.1.3(APIモード)
データベース PostgreSQL 16
認証 Firebase Authentication
環境構築 Docker
CI/CD Github Actions
インフラ Vercel / Render
UIライブラリ Tailwind CSS
外部API 学校コード検索 API / OpenAI API
その他 VueUse / Nuxt Icon / Nuxt Image / vue-chartjs /
Nuxt ESLint / Vitest / Nuxt Test Utils / Rubocop / RSpec

技術選定の理由

フロントエンド

フロントエンドには Vue.js と Nuxt3 を採用しました。React / Next.js も検討しましたが、初学者として継続的に学習・開発を進めるためには、学習コストが低く、直感的に理解しやすい技術が適切だと判断しました。

Vue.js は JavaScript に近い記法で記述でき、コンポーネントベースの設計も自然に習得できました。さらに Nuxt3 を組み合わせることで、ルーティングやディレクトリ構成が自動化されており、設計の指針が明確なため、迷いなく機能追加や画面設計に集中できると考えました。

特に Nuxt3 の以下の機能は、開発体験を大きく向上させました

  • 自動ルーティング(pages ディレクトリ):直感的にページ構成を管理でき、ナビゲーションも柔軟に実装可能

  • Auto Import 機能:useFetch や useState などを import なしで利用でき、コードの可読性と生産性が向上

  • TypeScript との親和性:型安全にデータ受け渡しや状態管理を行え、バグの予防にもつながる

バックエンド

バックエンドには Ruby on Rails を採用しました。プログラミングを始めた当初、私は「Railsチュートリアル」を活用して学習を進めました。初心者向けに体系的な構成がされており、Webアプリ開発の流れを挫折せずに理解できたことから、最初に習得したフレームワークが Rails でした。

ポートフォリオ開発においても、すでに習熟していた技術を活用することで、学習コストを抑えながら設計・実装に集中できると判断しました。

UI

UI には Tailwind CSS を採用しました。最大の理由は、クラス名の命名に悩む必要がないことです。従来の CSS 設計では、構造や意味を考慮した命名が求められ、開発の負担になっていました。Tailwind CSS ではユーティリティクラスを直接記述できるため、命名の手間を省きながら、UI をスピーディに構築できると考えました。

また、ユーティリティクラスだけでなく、hover: などの疑似クラスバリアントを容易に適用できる点や、ブレイクポイントを指定するだけでレスポンシブデザインを実現できる点も、開発効率を高める上で大きなメリットでした。

インフラ

ポートフォリオのインフラ構成には、フロントエンドに Vercel、バックエンドに Render を採用しました。

Vercel は特別な設定をせずにゼロコンフィグで GitHub リポジトリと連携でき、リポジトリを選んでボタンを押すだけでデプロイ環境が整い、以降は push するだけで自動的に更新が反映される点が魅力でした。

また、GitHub Actions を組み合わせて Vitest や ESLint によるテスト・静的解析をデプロイ前に自動実行することで、開発効率と品質管理を両立させています。

バックエンドには、Rails チュートリアルで利用経験のある Render を選択し、慣れた環境でスムーズに API と PostgreSQL データベースの運用構成を整えることができました。

画面遷移図

画面遷移図.png

ER図

ER図

インフラ構成図

インフラ構成図

こだわった実装

こだわった実装は以下になります。

  • みんなの振り返り機能
  • 振り返り登録機能
  • レスポンシブ対応

みんなの振り返り機能

「みんなの振り返り一覧」画面では、学生の振り返り内容を付箋風のカードとして表示しています。 このUIは、教室で学生がそれぞれの意見を付箋に書いて、模造紙や黒板、ホワイトボードなどに貼り付けて共有するという教育現場の情景を再現することを意図したものです。

カードの背景色や角度をランダムに変えることで、視覚的な多様性と楽しさを演出し、一覧画面にありがちな無機質さを避け、温かみのある空間をつくることを目指しました。 学生がクラスメートの振り返りを“眺めて回る”ような体験を促すことで、他者の視点に触れ、自分の学びを深めるきっかけになるよう設計しています。

みんなの振り返り画像

技術的な課題

Tailwind CSS は、ビルド時に未使用のクラスを自動的に削除(パージ)する仕様があります。 そのため、動的に組み立てたクラス名は、静的解析では使用されていないと判断され、ビルド後に削除されてしまいます。 この結果、背景色が正しく適用されないという問題が発生しました。

解決アプローチ

tailwind.config.tsにsafelistを設定することで、使用する可能性のある色クラスを事前にすべて列挙して保持しています。
今回は、17色 × 4パターンの組み合わせで、計68種類のクラスをsafelistに登録しました。
これにより、ランダムにクラスを指定しても、ビルド時に削除されることなく、安定して背景色が適用されるようになっています。

Tailwind CSS の safelist 設定

背景色のバリエーション(色 × 濃度)を関数でまとめて管理し、safelistに展開しています。このように関数化することで、色の追加や削除が柔軟に行えるようになり、保守性が高まります

tailwind.config.ts
function generateSafelist(color: string) {
  return [
    `bg-${color}-100`, `bg-${color}-200`, `bg-${color}-600/70`, `bg-${color}-100/70`
  ]
}

module.exports = {
  safelist: [
    ...generateSafelist('red'),
    ...generateSafelist('orange'),
    ...generateSafelist('amber'),
    ...generateSafelist('yellow'),
    ...generateSafelist('lime'),
    ...generateSafelist('green'),
    ...generateSafelist('emerald'),
    ...generateSafelist('teal'),
    ...generateSafelist('cyan'),
    ...generateSafelist('sky'),
    ...generateSafelist('blue'),
    ...generateSafelist('indigo'),
    ...generateSafelist('violet'),
    ...generateSafelist('purple'),
    ...generateSafelist('fuchsia'),
    ...generateSafelist('pink'),
    ...generateSafelist('rose')
  ]
}

ランダム生成のロジック

カード本体(付箋)とテープ部分の背景色をランダムに生成するためのComposableです。
17色 × 2通りの濃度の組み合わせからランダムに選び、Tailwind CSS のクラス文字列を生成します。
ロジックをコンポーネントから切り出すことで、責務を分離し、再利用やテストも容易にしました。

useGenerateRandomColor.ts
export const useGenerateRandomColor = () => {
  const cardBgColor = ref('')
  const sealBgColor = ref('')
  const rotationAngle = ref('')
  const colorPalette = [
    'red',
    'orange',
    'amber',
    'yellow',
    'lime',
    'green',
    'emerald',
    'teal',
    'cyan',
    'sky',
    'blue',
    'indigo',
    'violet',
    'purple',
    'fuchsia',
    'pink',
    'rose'
  ]
  const colorSaturationLevels = [
    { cardSaturation: 100, sealSaturation: 600 },
    { cardSaturation: 200, sealSaturation: 100 }
  ]
  const rotationAngles = [
    '-rotate-[3deg]', '-rotate-[5deg]', '-rotate-[7deg]', '-rotate-[10deg]',
    'rotate-[3deg]', 'rotate-[5deg]', 'rotate-[7deg]', 'rotate-[10deg]'
  ]

  const createRandomColor = () => {
    const selectedColor = colorPalette[Math.floor(Math.random() * colorPalette.length)]
    const selectedSaturation = colorSaturationLevels[Math.floor(Math.random() * colorSaturationLevels.length)]
    rotationAngle.value = rotationAngles[Math.floor(Math.random() * rotationAngles.length)]

    cardBgColor.value = `bg-${selectedColor}-${selectedSaturation.cardSaturation}`
    sealBgColor.value = `bg-${selectedColor}-${selectedSaturation.sealSaturation}/70`
  }

  return { cardBgColor, sealBgColor, rotationAngle, createRandomColor }
}

PostItコンポーネントでのランダムクラスの適用

マウント時にランダム生成を呼び、得られたクラス文字列をそのまま :class へバインドしています。

PostIt.vue
<script setup lang="ts">
const { cardBgColor, sealBgColor, createRandomColor } = useGenerateRandomColor()

onMounted(() => {
  createRandomColor()
})
</script>

<template>
  <div>
    <div
      class="cursor-pointer duration-300 ease-in-out hover:scale-110"
    >
      <div
        class="relative flex h-60 min-w-60 max-w-96 items-center justify-center text-gray-800"
        :class="cardBgColor"
      >
        <div class="mx-6 my-12 line-clamp-[8] w-full">
          <slot />
        </div>

        <div class="absolute bottom-[-5px] right-[7px] -z-10 h-1/2 w-[70%] rotate-[5deg] bg-[#d0d0d0] blur-sm" />
        <div
          class="absolute -top-3 left-1/2 h-8 w-20 -translate-x-1/2 rotate-3"
          :class="sealBgColor"
        />
      </div>
    </div>
  </div>
</template>

振り返り登録機能

授業の振り返りを、ユーザーとシステム(bot)の会話形式で記録できる仕組みを実装しました。複数のメッセージを1つの振り返りとして登録できるようにすることで、学習の流れや思考の過程を自然に残せることを目的としました。

フロントエンドからは以下のような形式でデータが送信されます。

sample_reflections.ts
reflections: [
  { message_type: "bot", message: "こんにちは!" },
  { message_type: "bot", message: "振り返りを始めましょう!" },
  { message_type: "bot", message: "今日の授業で新しく学んだことや印象に残った内容は何ですか?" },
  { message_type: "user", message: "JavaScriptの条件分岐について学びました。if文の使い方が少しずつ分かってきました。" },
  { message_type: "bot", message: "実際にコードを書いてみて、うまく動いた場面はありましたか?" },
  { message_type: "user", message: "「もし〇〇なら〜」という処理を作って、ボタンを押すとメッセージが変わるようにできました。" }
  { message_type: "bot", message: "次に挑戦してみたいことはありますか?" },
  { message_type: "user", message: "次は複数の条件を組み合わせて、もっと複雑な分岐を作ってみたいです。" },
  { message_type: "bot", message: "お疲れ様でした!" },
]

技術的な課題

複数メッセージを1つの振り返りとして保存する必要がありましたが、1件でも保存に失敗すると会話の一部だけが欠けてしまい、学習履歴の整合性を欠く可能性がありました。

解決アプローチ

Railsのトランザクションを用いて、すべての登録が成功した場合のみコミットされるように設計しました。失敗時にはロールバックしてエラーメッセージを返すことで、データの一貫性を担保しました。

reflections_controller.rb
def create
  user_course = UserCourse.joins(:course)
                .find_by(user_id: @user.id, 'courses.uuid' => params[:uuid])
  return render json: { error: { messages: ['あなたの所属情報が見つかりませんでした。'] } },
                status: :not_found unless user_course

  begin
    Reflection.transaction do
      new_reflections = reflection_params[:reflections].map do |reflection|
        Reflection.create!(
          reflection.merge(
            user_id: @user.id,
            course_id: user_course.course_id,
            course_date_id: reflection_params[:course_date_id]
          )
        )
      end

      reflections_with_course_dates = new_reflections.group_by(&:course_date).map do |course_date, reflections|
        course_date.as_json(only: %i[course_id course_number course_date])
                   .merge(reflections:)
      end

      render json: reflections_with_course_dates
    end
  rescue ActiveRecord::RecordInvalid
    render json: { error: { messages: ['振り返りを登録できませんでした。'] } },
           status: :unprocessable_entity
  end
end

レスポンシブ対応

「ReCap」では、どのデバイスでも快適に操作できるよう、各画面のUIを細部まで丁寧に作り込みました

画面サイズや操作環境に応じて、カードの配置・余白・コンポーネントの並び方などを一つひとつ調整し、妥協なく仕上げています

その一例として、授業一覧画面では、Tailwind CSS の grid レイアウトをベースに、grid-cols-1grid-cols-4 をブレイクポイント(xs, md, lg)ごとに指定し、スマートフォンでは1列、PCでは最大4列で表示されるよう設計しました。また、gap-xgap-y を使い、カード間の余白も画面幅に応じて最適化しています。

さらに、ナビゲーションの構成にも工夫を加え、PCでは常時表示されるサイドバーを、スマートフォンではハンバーガーメニューに切り替えることで、限られた画面スペースを有効に使いながら、操作性を損なわない設計を実現しました。

この構成により、狭い画面では縦スクロールで見やすく、広い画面では一覧性を高めることができました。

スマートフォン PC
スマホ版レイアウト.png PC版レイアウト.png

他の画面でも、ボタン配置・フォントサイズ・余白・コンポーネントの並び方などを細かく調整し、ユーザーがどの環境でも違和感なく使えるUIを目指して設計しています。

ポートフォリオを開発する際に学習・参考にした教材や記事

HTML・CSS / JavaScript・TypeScript

Vue / Nuxt

Rails

Docker

Figma

ポートフォリオ紹介記事

上記で紹介した以外にもQiitaZennなど、多くの記事やサイトに本当にお世話になりました。この場を借りて、心から感謝しています

おわりに

ここまで読んでいただき、心から感謝いたします。

ReCapの開発は、挑戦の連続でしたが、その分多くの学びを得ることができました。途中で何度も完成しないのではないか、これまでの努力が無駄になるのではないかと不安に感じましたが、それでも最後までやり遂げることができました。

まだまだ改善の余地はありますが、この経験を通じて得た知識とスキルを、今後も活かしていきたいと思います。この記事が、少しでも誰かの参考になれば幸いです。

ReCapへのアクセスは、下記リンクからご利用ください。

また、再度のお願いになりますが、アンケートにご協力いただけると非常に嬉しいです。アンケートは、以下のリンクからご意見をお寄せください。

最後に、システムの不具合やご質問等がございましたら、コメントや、X(@kaseispace)、アンケートからご連絡いただけますと幸いです。

最後までお読みいただき、ありがとうございました!

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?