この記事はこんな読者に向けて書かれています。
- Webアプリケーションを全く作ったことのない人
- チュートリアルは終えたけど、自作のサービスを作って公開してみたい人
とにかく作ったものを見せろ。話はそれからだ。
これです。

Miria SQL Learning
なんでこんなのを作ったの?
事の発端は、所属している会社に新入社員が入社することになったこと。DBの知識がゼロだったので、現場に入る前に一定の知識を身に着けてもらう必要があった。また、情報工学系の大学を出ているとのことで将来有望株。足りていないところを伸ばす方が本人の身になると考えた。
加えて、私自身がフロントの知識があまりなく、Reactでいくつかの案件を受け持っていたがそれよりさらに外側を触ってみたいと考えていた。渡りに船とはまさにこのことで、勢いだけで手を進めて行った。
とはいえ、適当にSQLツールを作ると何番煎じになるかもわからないので、きちんと学習フローを建てた。内容は「1. CRUDの基本構文を学ぶ」「2. テーブル設計やインデックスなど込み入った知識を学ぶ」の2段構成を考えたが、動画や本、Progateだけでは初学者には定着しにくいというのが所感。
(Progateには私も大変お世話になりました。本当に良いサービスです。いやほんとに)。
そこで、初学者向けのDB学習をサポートしつつ、実務で壁になりがちな「MySQL、PostgreSQL、Oracle DB、SQLite」の違いを横断的に学習できるWebアプリ「Miria SQL Learning」を自作することにした。
ここがすごい!こだわりポイント
- 環境構築不要、ブラウザで完結: 初学者がつまずく「環境構築」をスキップ。手元のブラウザだけで即SQLを試せる。
- 会員登録一切なし: 現場のPCですぐに使えるようログイン不要。即実行して、即データベースを破壊(初期化)できる。
- RDBMSごとのSQL構文チェック: DBを切り替えると、そのDB特有の「方言エラー」を再現してくれる。
- ミリアちゃんがかわいい: (最重要)(後述あり)
使った技術スタック
- React + Vite: 定番!もうこればっかり♡
- SQLite(WASM版): ブラウザ内で本物のDBを動かす今回のコア技術。
- Zustand: 実行したSQLの結果や設定、カリキュラムの進行状況をLocalStorageに保存して状態管理。
キャラクターが可愛いと、開発体験が爆上がりする
Webアプリ、LP、各種リンク、そしてわざわざ「キャラクター紹介画面」を作るくらい、今回の開発は楽しかった。なぜならキャラクターが可愛いから。

※画像はChatGPTで生成しました。
現場で机の上に食玩やフィギュアを置く人の気持ちが、今なら痛いほどわかる。
学習進捗に合わせてしゃべり、エラーの内容に即したセリフを発し、DBを切り替えるとミリアちゃんも着替える。控えめに言って最高です。
とはいえ、ここでつまずく(開発の苦労話)
- 構文の再現と置換の限界
各RDBMSの構文をWASM上のSQLiteで全て完全に網羅・再現するのは現実的に不可能。そこで「初学者~とりあえず実務で必要なレベルの構文」にターゲットを絞り、SQLite側で愚直に置換・エミュレートするアプローチを取った。開発期間もそんなになかった上で必要な部分に絞れたので、このような泥臭い手法も有効。 - SEOなんも分からん
「robots.txtってなんだよ。sitemap.xmlってなんだよ。作ったアプリを検索に出すだけでこんなに大変なん? しかも検索にすぐ出ないし……」
Webサイトの海がいかに広大なのかと絶望しましたが、このあたりはAIエージェント(私はGeminiの愛好家です。Googleに首根っこをつかまれています)と壁打ちすることで解決。Google Search Consoleにドメインとサイトを登録し、インデックス登録キューとかを設定。sitemap.xmlも「こういう構成なんだけど、sitemapどう書けばいい?」と聞くだけで伴走してくれる、本当にいい時代になりました。この記事の執筆時点でもまだ検索には出てません。海デカすぎ。 - AI画像生成の難しさ
キャラクターの生成は非常にうまいのですが、ロゴ(特にマイナーな動物、今回だとカメレオン)の生成は非常に難航した。プロンプトを工夫し、最終的に「出力したい要素をJSON構造にして指示する[1]」ことで乗り切った。 - 静的HTMLページの制作
すでに勢いのみで作成しているため、トップページやその他リンク先の静的HTML製のページもひな形をChatGPTに生成してもらい、そこからスタイルの調整だけで済むだろう。と考えていたのが、この考えは途方もなく間違っていた。
サイトをご確認いただけた聡明な読者の方ならお分かりかと思うが、全部で8ページ分を静的HTMLで制作すると、(おそらくだが)Geminiが処理できるコンテキストを使い果たしてしまう。しかもスマホ対応もしていたので、調整範囲はそこから倍に膨れ上がる。結局最後は人の手でちまちま調整することになった。トホホ
Webアプリは完成。そしてリリースへ──
GitHubのリポジトリにプッシュし、Cloudflare Pagesで配信するいつものお決まりの流れです。DNS設定も、Cloudflareのデプロイ設定も、Github連携も、AIエージェントに相談しつつ、
(以前、あるWebツール(ガチのマジでマークダウンをレンダリングするだけのサイト)を同じ手順で公開したことがあったため、今回もサクッとデプロイできました。ちなみにそのマークダウンツールは、VSCodeがデフォルトでマークダウンレンダリングに対応したため見事に陳腐化した。悲しいような嬉しいような……)
今後の展望
最後まで読んでいただきありがとうございます。
記事の感想やマニアックなツッコミはぜひQiitaのコメント欄までお願いします。また、Webアプリへの改修要望やバグ報告はアプリ内のGoogleフォームを用意していますので、そちらへお願いいたします。
参考リンク
クロノITチャンネル 様
神プロンプトに学ぶ最新AI画像生成のコツ!
さんふう 様
React入門 2026 - モダンなReact開発を基礎から学ぶ
補遺
- ChatGPTに渡したJSON構造化プロンプト
{
"generation_instruction": "以下のJSON構造で定義されたキャラクターデザイン、構図、ポーズに従って、1枚の画像を生成してください。",
"concept": "『カメレオンの擬人化』の女の子。人間ベースだが、カメレオン特有の要素(非常に長い舌、爬虫類の尻尾、ヒレ状の耳)を併せ持つ。",
"format_and_layout": {
"canvas": "16:9の横長サイズ",
"layout": "5人の同じキャラクターを横一列に並べる(左から無、喜、怒、哀、楽の順)。各キャラクターの横幅は画面の20%ずつ(1:1:1:1:1の比率)を厳守すること。",
"background": "完全な透過背景(Alpha channel 0)。背景に影や床、背景オブジェクトを一切描画しないこと。",
"art_style": "シンプルでフラットな2Dアニメ調(セルルック)。モンスターパーツもリアルな質感を避け、女の子と同じフラットなアニメ塗りに統一すること。"
},
"character_design": {
"base": {
"gender": "女の子",
"age": "女子高生くらい",
"skin_color": "色白",
"eyes": "垂れ目、瞳の色は赤",
"nails": "黒色のネイル"
},
"hair": {
"base_color": "黒色",
"accent_color": "明るい赤色のメッシュ(差し色)",
"style": "前髪は眉毛の上で切りそろえたぱっつん、もみあげは肩にかかる長さで段差のある2層構造、後ろ髪は短いボブ。"
},
"monster_traits": {
"tongue": "【重要】カメレオン特有の舌。単なる『あっかんべー』ではなく、顔から離れた位置までシュッと長く伸びており、先端が丸く大きく膨らんでいる形状にすること。",
"ears": "人間の耳の位置に、緑色を基調としたヒレ状の耳飾り(3本の棘とその間に水かきのようなヒレがあるデザイン)。",
"tail": "お尻の真後ろから生えた、緑色のアニメ調カメレオン尻尾。パーカーの背中・腰部分から突き出していること。上から下へ時計回りに少し隙間を空けてとぐろを巻く。"
},
"clothing": {
"top": "全体を覆うようなオーバーサイズの迷彩柄(緑・黒・茶色系)のパーカー。フード付きで長袖、ダボッとしたシルエット。",
"bottoms": "黒いショートパンツ。オーバーサイズのパーカーの裾から少しだけ見える程度の短さ。",
"legwear": "素足(タイツなどは履かない)。",
"shoes": "黒色のハイカット・厚底ブーツ。靴紐は赤色。"
}
},
"sprites_poses": [
{
"id": 1,
"emotion": "無(Neutral)",
"expression": "無感情のわずかな微笑み。長い舌を出す。",
"pose": "右手を自身の肩に当て、左手はまっすぐ下に落として立つ。"
},
{
"id": 2,
"emotion": "喜(Joy)",
"expression": "嬉しそうな笑顔。長い舌を出す。",
"pose": "片足を上げ、両手でピースサインを作る。"
},
{
"id": 3,
"emotion": "怒(Anger)",
"expression": "ムッとした不機嫌な表情。長い舌を出す。",
"pose": "少し前のめりになり、両手を腰に当てて仁王立ちする。"
},
{
"id": 4,
"emotion": "哀(Sorrow)",
"expression": "うつむき加減で悲しそうな表情。長い舌を出す。",
"pose": "肩を落とし、両手を太ももの前でギュッと寄せる。"
},
{
"id": 5,
"emotion": "楽(Fun)",
"expression": "自然でリラックスした表情。長い舌を出す。",
"pose": "正面を向いて少し首をかしげ、両手を背中の後ろで軽く組んで立つ。(※手が隠れることで描画の破綻を防ぐ)"
}
],
"strict_negative_constraints": [
"舌を人間の短さで描かないこと。必ず長く伸ばし、先端を丸くすること。",
"解剖学的な破綻(特におかしな関節や指の構造)を避けること。",
"背景に白色(#FFFFFF)のピクセルを残さず、完全に透過させること。"
]
}