Nextron + Prisma + SQLiteでノートアプリを作成
Nextron(Next.js + Electron)とPrisma、SQLiteを組み合わせてデスクトップノートアプリケーションを開発しました。データベース設計、API Routes、美しいUIデザインなど、実践的な技術を学びながらデスクトップアプリを作成する過程を紹介します。
目次
プロジェクト概要
Nextron + Prisma + SQLite を組み合わせて、美しいデスクトップノートアプリケーションを開発しました。このプロジェクトでは、Web技術(Next.js、React)とデスクトップ技術(Electron)、そしてデータベース技術(Prisma、SQLite)を統合して、実用的なアプリケーションを作成しました。
主な機能
- 📝 ノート作成・編集: タイトルと内容を持つノートの作成
- 💾 永続化: SQLiteデータベースでのデータ保存
- 🎨 美しいUI: モダンなデザインとアニメーション
- 📱 レスポンシブ: 様々な画面サイズに対応
- ⚡ 高速: ローカルデータベースによる高速な操作
技術スタック
フロントエンド
- Next.js 14: React フレームワーク
- React Hooks: 状態管理(useState, useEffect)
- TypeScript: 型安全性の確保
デスクトップ
- Electron: クロスプラットフォームデスクトップアプリ
- Nextron: Next.js + Electron の統合フレームワーク
データベース
- Prisma: 型安全なORM
- SQLite: 軽量なローカルデータベース
スタイリング
- Tailwind CSS: ユーティリティファーストCSS
- レスポンシブデザイン: モバイル・デスクトップ対応
環境構築
1. Nextron プロジェクトの作成
# 新しいNextronプロジェクトを作成
npx create-nextron-app . --example with-tailwindcss
npm install
2. Prisma のセットアップ
# Prisma CLI のインストール
npm install prisma --save-dev
npm install @prisma/client
# Prisma の初期化
npx prisma init
3. SQLite データベースの設定
# schema.prisma ファイルを編集
# SQLite プロバイダーを設定
4. 開発サーバーの起動
npm run dev
データベース設計
Prisma Schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model Note {
id Int @id @default(autoincrement())
title String
content String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
設計のポイント
- シンプルな構造: 必要最小限のフィールド
- 自動タイムスタンプ: 作成日時と更新日時の自動管理
- オプショナルコンテンツ: 内容は空でもOK
- SQLite: 軽量で高速なローカルデータベース
データベースの初期化
# マイグレーションの作成
npx prisma migrate dev --name init
# Prisma Client の生成
npx prisma generate
実装
1. API Routes の実装
// renderer/pages/api/notes.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {
const notes = await prisma.note.findMany({
orderBy: { createdAt: 'desc' },
})
res.json(notes)
} else if (req.method === 'POST') {
const { title, content } = req.body
const newNote = await prisma.note.create({
data: { title, content },
})
res.status(201).json(newNote)
} else {
res.status(405).json({ message: 'Method not allowed' })
}
}
2. フロントエンドの実装
// renderer/pages/home.tsx
import { useEffect, useState } from 'react'
type Note = {
id: number
title: string
content?: string
createdAt: string
}
export default function Home() {
const [notes, setNotes] = useState<Note[]>([])
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const fetchNotes = async () => {
const res = await fetch('/api/notes')
const data = await res.json()
setNotes(data)
}
const addNote = async () => {
if (!title.trim()) return
await fetch('/api/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content }),
})
setTitle('')
setContent('')
fetchNotes()
}
useEffect(() => {
fetchNotes()
}, [])
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
{/* UI コンポーネント */}
</div>
)
}
3. 設定ファイルの修正
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// output: 'export' を削除(API Routes を使用するため)
}
module.exports = nextConfig
tailwind.config.js
const colors = require('tailwindcss/colors')
module.exports = {
content: [
'./renderer/pages/**/*.{js,ts,jsx,tsx}',
'./renderer/components/**/*.{js,ts,jsx,tsx}',
],
theme: {
colors: {
white: colors.white,
gray: colors.gray,
blue: colors.blue,
},
extend: {},
},
plugins: [],
}
UI/UX デザイン
デザインコンセプト
- ダークテーマ: 目に優しいダークグラデーション
- ガラス効果: 半透明カードによるモダンな見た目
- アニメーション: スムーズなホバーエフェクト
- レスポンシブ: 様々な画面サイズに対応
主要なUIコンポーネント
ヘッダー
<div className="text-center mb-8">
<h2 className="text-4xl font-bold text-white mb-2">📝 My Notes</h2>
<p className="text-gray-300">シンプルで美しいノートアプリ</p>
</div>
入力フォーム
<div className="max-w-2xl mx-auto bg-white/10 backdrop-blur-sm rounded-xl p-6 mb-8 border border-white/20">
<h2 className="text-xl font-semibold text-white mb-4">新しいノートを追加</h2>
<div className="space-y-4">
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="タイトルを入力..."
className="w-full px-4 py-3 bg-white/20 border border-white/30 rounded-lg text-white placeholder-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
/>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="内容を入力..."
rows={4}
className="w-full px-4 py-3 bg-white/20 border border-white/30 rounded-lg text-white placeholder-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all resize-none"
/>
<button
onClick={addNote}
disabled={!title.trim()}
className="w-full px-6 py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-lg hover:from-blue-600 hover:to-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 transform hover:scale-105"
>
✨ ノートを追加
</button>
</div>
</div>
ノートカード
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{notes.map(note => (
<div key={note.id} className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20 hover:bg-white/15 transition-all duration-200 transform hover:scale-105">
<h3 className="text-lg font-semibold text-white mb-2 line-clamp-2">
{note.title}
</h3>
{note.content && (
<p className="text-gray-300 text-sm line-clamp-3 mb-3">
{note.content}
</p>
)}
<div className="text-xs text-gray-400">
{new Date(note.createdAt).toLocaleDateString('ja-JP', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div>
))}
</div>
デザインの特徴
- グラデーション背景: 美しいダークグラデーション
- 半透明カード: ガラス効果によるモダンな見た目
- ホバーエフェクト: インタラクティブな要素
- レスポンシブグリッド: 画面サイズに応じたレイアウト
- 日本語対応: 日時表示の日本語化
学んだこと
🎯 Nextron の活用
- Electron + Next.js: Web技術でデスクトップアプリ開発
- API Routes: サーバーサイド機能の実装
- 開発体験: ホットリロードとデバッグの快適さ
🗄️ Prisma + SQLite の威力
- 型安全: TypeScriptとの完全な統合
- マイグレーション: データベーススキーマの管理
- 軽量: SQLiteによる高速なローカルデータベース
- 開発効率: Prisma Clientによる簡単なデータ操作
🎨 Tailwind CSS の活用
- ユーティリティファースト: 高速なスタイリング
- レスポンシブデザイン: 簡単なレスポンシブ対応
- カスタマイズ性: 豊富なカスタマイズオプション
- ガラス効果: モダンなUIデザインの実現
⚡ パフォーマンス最適化
- ローカルデータベース: 高速なデータアクセス
- 効率的なレンダリング: React の最適化
- 軽量な依存関係: 必要最小限のパッケージ
🔧 開発体験の向上
- 型安全性: TypeScriptによるエラー防止
- ホットリロード: リアルタイムでの変更確認
- デバッグ: Chrome DevTools でのデバッグ
- エラーハンドリング: 適切なエラー処理
今後の拡張案
🚀 機能拡張
- ノート編集: 既存ノートの編集機能
- ノート削除: ノートの削除機能
- カテゴリ: ノートの分類機能
- 検索機能: タイトル・内容での検索
- エクスポート: Markdown/PDF形式でのエクスポート
🎨 UI/UX 改善
- ダーク/ライトモード: テーマ切り替え機能
- ドラッグ&ドロップ: ファイルのドラッグ&ドロップ
- キーボードショートカット: 効率的な操作
- アニメーション: より滑らかなトランジション
🔧 技術的改善
- オフライン対応: 完全なオフライン機能
- データ同期: クラウドとの同期機能
- バックアップ: 自動バックアップ機能
- パフォーマンス: 大きなデータセットの最適化
📱 プラットフォーム拡張
- Web版: ブラウザ版の開発
- モバイル: React Native版の開発
- PWA: Progressive Web App対応
開発で遭遇した問題と解決方法
1. API Routes と output: export の競合
問題: next.config.js
で output: 'export'
を設定していると、API Routes が使えない
解決方法:
// next.config.js から output: 'export' を削除
const nextConfig = {
// output: 'export', // ← これを削除
}
2. Tailwind CSS の line-clamp プラグイン
問題: @tailwindcss/line-clamp
プラグインが見つからないエラー
解決方法: 最新のTailwind CSSでは line-clamp
は標準機能なので、プラグインを削除
3. ファイル名の不一致
問題: APIファイル名が note.ts
(単数形)で、フロントエンドが /api/notes
(複数形)にリクエスト
解決方法: ファイル名を notes.ts
に変更
まとめ
Nextron + Prisma + SQLite を使ったノートアプリの開発を通じて、以下のことを学びました:
- Nextron の基本: Electron + Next.js の組み合わせ
- Prisma ORM: 型安全なデータベース操作
- SQLite: 軽量なローカルデータベース
- API Routes: Next.js のサーバーサイド機能
- モダンなUI: Tailwind CSS による美しいデザイン
- 状態管理: React Hooks による効率的な状態管理
このプロジェクトは、Web技術とデスクトップ技術、データベース技術を統合した実践的なアプリケーション開発の良い例です。既存のWeb開発スキルを活かしながら、ネイティブアプリのような体験を提供できます。
次回は、より高度な機能(ノート編集、検索、カテゴリなど)を追加して、さらに実用的なアプリケーションにしていきたいと思います!
技術スタック: Nextron, React, Next.js, Electron, Prisma, SQLite, Tailwind CSS
開発期間: 1日
難易度: 中級
データベース: SQLite (ローカル)
UI/UX: モダンなダークテーマ、レスポンシブデザイン