本記事は 書籍管理システム設計(全4回)シリーズの ④ です。
📚 シリーズ全記事(全4回)
- 【ASP.NET Core Web API + React + TypeScript(vite)】書籍管理システム設計 ① 環境構築手順書
- 【ASP.NET Core Web API + React + TypeScript(vite)】書籍管理システム設計 ② 要件定義書
- 【ASP.NET Core Web API + React + TypeScript(vite)】書籍管理システム設計 ③ 基本設計書(外部設計)
- 【ASP.NET Core Web API + React + TypeScript(vite)】書籍管理システム設計 ④ 詳細設計書(内部設計)(この記事)
| 項目 |
内容 |
| 文書名 |
詳細設計書 |
| システム名 |
書籍管理システム(Book Manager) |
| 版数 |
1.0 |
| 作成日 |
2026-07-24 |
| 関連文書 |
01_要件定義書.md / 02_基本設計書.md |
改訂履歴
| 版 |
日付 |
改訂内容 |
作成者 |
| 1.0 |
2026-07-24 |
初版作成(実装済みソースに基づく) |
— |
1. はじめに
本書は基本設計書に基づき、書籍管理システムの内部構造(クラス・メソッド・DB物理仕様・処理フロー)を実装レベルで定義する。
2. 全体構成
2.1 レイヤ構成
[ブラウザ] ─ App.tsx(表示・状態) ─ api.ts(HTTPクライアント)
│ /api/*(Viteプロキシ)
▼
[API] BooksController(コントローラ) ─ AppDbContext(EF Core) ─ SQLite
2.2 ファイル構成と責務
| 区分 |
ファイル |
責務 |
| API |
api/Models/Book.cs |
書籍エンティティ定義 |
| API |
api/Data/AppDbContext.cs |
DbContext・DbSet・シード定義 |
| API |
api/Controllers/BooksController.cs |
REST エンドポイント実装 |
| API |
api/Program.cs |
DI 登録・ミドルウェア・起動 |
| API |
api/Migrations/* |
スキーマ・シードのマイグレーション |
| Web |
web/src/api.ts |
型定義・API 呼び出し関数 |
| Web |
web/src/App.tsx |
画面コンポーネント・状態管理 |
| Web |
web/vite.config.ts |
開発サーバ・プロキシ設定 |
3. バックエンド詳細設計
3.1 クラス構成図
3.2 Book モデル(api/Models/Book.cs)
| プロパティ |
型 |
初期値 |
説明 |
| Id |
int |
0 |
主キー(EF 規約により自動採番) |
| Title |
string |
"" |
タイトル |
| Author |
string |
"" |
著者 |
| Year |
int |
0 |
出版年 |
| IsBorrowed |
bool |
false |
貸出状態 |
public class Book
{
public int Id { get; set; }
public string Title { get; set; } = "";
public string Author { get; set; } = "";
public int Year { get; set; }
public bool IsBorrowed { get; set; }
}
3.3 AppDbContext(api/Data/AppDbContext.cs)
-
DbSet<Book> Books を公開。
-
OnModelCreating にて HasData でシードデータ5件を定義。
- コンストラクタで
DbContextOptions<AppDbContext> を受け取る(DI)。
public DbSet<Book> Books => Set<Book>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Book>().HasData(
new Book { Id = 1, Title = "吾輩は猫である", Author = "夏目漱石", Year = 1905, IsBorrowed = false },
new Book { Id = 2, Title = "羅生門", Author = "芥川龍之介", Year = 1915, IsBorrowed = true },
new Book { Id = 3, Title = "人間失格", Author = "太宰治", Year = 1948, IsBorrowed = false },
new Book { Id = 4, Title = "雪国", Author = "川端康成", Year = 1937, IsBorrowed = true },
new Book { Id = 5, Title = "こころ", Author = "夏目漱石", Year = 1914, IsBorrowed = false }
);
}
3.4 BooksController(api/Controllers/BooksController.cs)
- 属性:
[ApiController] / [Route("api/[controller]")] → 実パスは /api/books。
- 依存:コンストラクタ注入で
AppDbContext _db を受け取る。
| メソッド |
HTTP |
ルート |
引数 |
戻り値 |
処理概要 |
ステータス |
| GetAll |
GET |
/api/books |
なし |
IEnumerable<Book> |
Books.ToListAsync() |
200 |
| Get |
GET |
/api/books/{id} |
int id |
ActionResult<Book> |
FindAsync(id)。無ければ NotFound |
200 / 404 |
| Create |
POST |
/api/books |
Book book |
ActionResult<Book> |
Add → SaveChangesAsync → CreatedAtAction
|
201 |
| Update |
PUT |
/api/books/{id} |
int id, Book book |
IActionResult |
id 不一致は BadRequest。Entry(book).State = Modified → 保存 |
204 / 400 |
| Delete |
DELETE |
/api/books/{id} |
int id |
IActionResult |
FindAsync → 無ければ NotFound、あれば Remove → 保存 |
204 / 404 |
[HttpGet]
public async Task<IEnumerable<Book>> GetAll() => await _db.Books.ToListAsync();
[HttpGet("{id}")]
public async Task<ActionResult<Book>> Get(int id) =>
await _db.Books.FindAsync(id) is { } b ? b : NotFound();
[HttpPost]
public async Task<ActionResult<Book>> Create(Book book)
{
_db.Books.Add(book);
await _db.SaveChangesAsync();
return CreatedAtAction(nameof(Get), new { id = book.Id }, book);
}
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, Book book)
{
if (id != book.Id) return BadRequest();
_db.Entry(book).State = EntityState.Modified;
await _db.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
if (await _db.Books.FindAsync(id) is not { } b) return NotFound();
_db.Books.Remove(b);
await _db.SaveChangesAsync();
return NoContent();
}
3.5 Program.cs(起動・DI・ミドルウェア)
| 処理 |
内容 |
AddControllers() |
コントローラを登録 |
AddDbContext<AppDbContext>(UseSqlite("Data Source=books.db")) |
EF Core + SQLite を DI 登録 |
AddOpenApi() |
OpenAPI 定義生成を登録 |
MapOpenApi() / MapScalarApiReference()
|
開発環境のみ OpenAPI・Scalar を公開 |
UseAuthorization() |
認可ミドルウェア(現状はポリシーなし) |
MapControllers() |
ルーティング割当 |
※ HTTP 運用のため UseHttpsRedirection() は使用しない。
3.6 処理シーケンス
登録(POST)
更新(PUT)/削除(DELETE)
4. データベース詳細設計
4.1 物理テーブル定義(Books)
| カラム |
型(SQLite) |
NULL |
制約 |
説明 |
| Id |
INTEGER |
NOT NULL |
PRIMARY KEY / AUTOINCREMENT |
主キー |
| Title |
TEXT |
NOT NULL |
− |
タイトル |
| Author |
TEXT |
NOT NULL |
− |
著者 |
| Year |
INTEGER |
NOT NULL |
− |
出版年 |
| IsBorrowed |
INTEGER |
NOT NULL |
− |
貸出状態(0/1) |
4.2 DDL(Init マイグレーション生成)
CREATE TABLE "Books" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Books" PRIMARY KEY AUTOINCREMENT,
"Title" TEXT NOT NULL,
"Author" TEXT NOT NULL,
"Year" INTEGER NOT NULL,
"IsBorrowed" INTEGER NOT NULL
);
4.3 マイグレーション一覧
| 順 |
マイグレーション名 |
内容 |
| 1 |
Init |
Books テーブル作成 |
| 2 |
SeedBooks |
初期データ5件の投入(HasData) |
4.4 初期データ(シード)
| Id |
Title |
Author |
Year |
IsBorrowed |
| 1 |
吾輩は猫である |
夏目漱石 |
1905 |
false |
| 2 |
羅生門 |
芥川龍之介 |
1915 |
true |
| 3 |
人間失格 |
太宰治 |
1948 |
false |
| 4 |
雪国 |
川端康成 |
1937 |
true |
| 5 |
こころ |
夏目漱石 |
1914 |
false |
5. フロントエンド詳細設計
5.1 コンポーネント構成
- エントリ:
main.tsx が App を描画(StrictMode)。
- 画面:
App.tsx の単一コンポーネント。
- 通信:
api.ts に集約。
5.2 api.ts(型・関数仕様)
| 名称 |
シグネチャ |
説明 |
| Book |
interface |
書籍の型(id, title, author, year, isBorrowed) |
| getBooks |
() => Promise<Book[]> |
一覧取得。!ok 時に例外 |
| createBook |
(b: Omit<Book,"id">) => Promise<Book> |
登録。登録済み Book を返す |
| updateBook |
(b: Book) => Promise<void> |
更新(PUT /api/books/{b.id}) |
| deleteBook |
(id: number) => Promise<void> |
削除 |
- ベースパス定数:
const BASE = "/api/books";(相対パス=プロキシ経由)。
- 各関数はレスポンス
!r.ok の場合に Error を送出する。
5.3 App.tsx(状態・関数)
状態(useState)
| 状態 |
型 |
用途 |
| books |
Book[] |
一覧データ |
| form |
FormState |
新規登録フォーム(title/author/year を文字列で保持) |
| editingId |
number | null |
編集中の行 ID(null は非編集) |
| editForm |
FormState |
編集中の入力値 |
| loading |
boolean |
読み込み中フラグ |
| error |
string | null |
エラーメッセージ |
関数
| 関数 |
処理 |
| load |
一覧取得。取得失敗時は error を設定 |
| add |
タイトル必須チェック→登録→フォーム初期化→再取得。年未入力は当年 |
| startEdit(b) |
対象行を編集モードにし editForm を初期化 |
| cancelEdit |
編集モード解除 |
| saveEdit(b) |
既存 Book に編集値を上書きして更新→再取得 |
| toggleBorrow(b) |
isBorrowed を反転して更新→再取得 |
| remove(id) |
削除→再取得 |
- 初期表示:
useEffect(() => { load(); }, []) で一覧取得。
5.4 編集モードの状態遷移
一覧表示(editingId=null)
│ startEdit(b):editingId=b.id, editForm=bの値
▼
行編集モード
│ saveEdit:PUT→cancelEdit→load
│ cancelEdit:editingId=null
▼
一覧表示
5.5 vite.config.ts(プロキシ)
server: {
proxy: {
'/api': 'http://localhost:5000',
},
}
-
/api 始まりのリクエストを API(5000)へ転送。これにより CORS 不要。
6. 設定・環境詳細
6.1 ポート・起動プロファイル
| 対象 |
ポート |
設定 |
| API |
5000(HTTP) |
Properties/launchSettings.json の http プロファイル(http 単一) |
| Web |
5173(HTTP) |
Vite 既定 |
6.2 使用パッケージ
| 区分 |
パッケージ |
バージョン |
| NuGet |
Microsoft.AspNetCore.OpenApi |
10.0.10 |
| NuGet |
Microsoft.EntityFrameworkCore.Sqlite |
10.0.10 |
| NuGet |
Microsoft.EntityFrameworkCore.Design |
10.0.10 |
| NuGet |
Scalar.AspNetCore |
2.16.16 |
| npm |
react / react-dom |
19.2.8 |
| npm |
typescript |
6.0.3 |
| npm |
vite |
8.1.5 |
| npm |
@vitejs/plugin-react |
6.0.4 |
| npm |
@types/react / @types/react-dom |
19.2.17 / 19.2.3 |
6.3 起動手順
# API
cd api && dotnet run --launch-profile http # http://localhost:5000 , UI: /scalar
# Web
cd web && npm run dev # http://localhost:5173
6.4 DB 再構築手順
cd api && dotnet ef database update # Init→SeedBooks を適用(シード自動投入)
7. エラーハンドリング詳細
| 層 |
事象 |
挙動 |
| API |
該当データなし(Get/Delete) |
404 Not Found |
| API |
更新時 id 不一致 |
400 Bad Request |
| Web |
一覧取得の HTTP エラー/通信失敗 |
error 状態にメッセージを設定し画面表示 |
| Web |
タイトル未入力 |
追加ボタンを非活性化し登録を抑止 |
8. 留意事項・既知の警告
- ビルド時に
NU1903 警告が出力される(推移的依存 Microsoft.OpenApi 2.0.0、SQLitePCLRaw.lib.e_sqlite3 の脆弱性告知)。動作には影響しないが、必要に応じ該当パッケージを更新する。
- 更新(PUT)は
EntityState.Modified による全項目上書き方式であり、部分更新(PATCH)は未対応。
- 単一利用者前提のため、同時更新の競合制御(楽観ロック等)は未実装。