{"version":"https://jsonfeed.org/version/1","title":"mejiro877のブログ","home_page_url":"https://mejiro877.github.io/lume-blog/","feed_url":"https://mejiro877.github.io/lume-blog/feed.json","description":"プログラミングに関する技術ブログ","items":[{"id":"https://mejiro877.github.io/lume-blog/posts/rest-api-design/","url":"https://mejiro877.github.io/lume-blog/posts/rest-api-design/","title":"REST API 設計の実践原則","content_html":"<p>REST API は一度公開すると変更しにくいため、設計段階での判断が重要です。後悔しないための設計原則をまとめます。</p>\n<!-- more -->\n<h2 id=\"url-%E8%A8%AD%E8%A8%88%E3%81%AF%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%A7%E8%80%83%E3%81%88%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/rest-api-design/#url-%E8%A8%AD%E8%A8%88%E3%81%AF%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%A7%E8%80%83%E3%81%88%E3%82%8B\" class=\"header-anchor\">URL 設計はリソースで考える</a></h2>\n<p>URL はアクションではなくリソース（名詞）を表します。</p>\n<pre><code># 悪い例: 動詞を使っている\nGET  /getUsers\nPOST /createUser\nPOST /deleteUser?id=123\n\n# 良い例: リソース + HTTP メソッドで表現\nGET    /users          # 一覧取得\nPOST   /users          # 作成\nGET    /users/123      # 1件取得\nPUT    /users/123      # 全体更新\nPATCH  /users/123      # 部分更新\nDELETE /users/123      # 削除\n</code></pre>\n<p>階層関係は URL で表現します。</p>\n<pre><code>GET /users/123/orders        # ユーザー123の注文一覧\nGET /users/123/orders/456    # 特定の注文\n</code></pre>\n<h2 id=\"http-%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%AD%A3%E3%81%97%E3%81%8F%E4%BD%BF%E3%81%86\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/rest-api-design/#http-%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%AD%A3%E3%81%97%E3%81%8F%E4%BD%BF%E3%81%86\" class=\"header-anchor\">HTTP ステータスコードを正しく使う</a></h2>\n<table>\n<thead>\n<tr>\n<th>コード</th>\n<th>意味</th>\n<th>使いどころ</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>200</td>\n<td>OK</td>\n<td>取得・更新成功</td>\n</tr>\n<tr>\n<td>201</td>\n<td>Created</td>\n<td>リソース作成成功</td>\n</tr>\n<tr>\n<td>204</td>\n<td>No Content</td>\n<td>削除成功（ボディなし）</td>\n</tr>\n<tr>\n<td>400</td>\n<td>Bad Request</td>\n<td>バリデーションエラー</td>\n</tr>\n<tr>\n<td>401</td>\n<td>Unauthorized</td>\n<td>未認証</td>\n</tr>\n<tr>\n<td>403</td>\n<td>Forbidden</td>\n<td>認可エラー（ログイン済みだが権限なし）</td>\n</tr>\n<tr>\n<td>404</td>\n<td>Not Found</td>\n<td>リソースが存在しない</td>\n</tr>\n<tr>\n<td>409</td>\n<td>Conflict</td>\n<td>重複など競合</td>\n</tr>\n<tr>\n<td>422</td>\n<td>Unprocessable Entity</td>\n<td>意味的に不正なリクエスト</td>\n</tr>\n<tr>\n<td>500</td>\n<td>Internal Server Error</td>\n<td>サーバー側の予期しないエラー</td>\n</tr>\n</tbody>\n</table>\n<p><code>200</code> をすべてに使ってエラーをボディで返すのはやめましょう。</p>\n<h2 id=\"%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E7%B5%B1%E4%B8%80%E3%81%99%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/rest-api-design/#%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E7%B5%B1%E4%B8%80%E3%81%99%E3%82%8B\" class=\"header-anchor\">エラーレスポンスを統一する</a></h2>\n<p>エラー時のレスポンス形式を統一しておくと、クライアント側の実装が楽になります。</p>\n<pre><code class=\"language-json\">{\n  &quot;error&quot;: {\n    &quot;code&quot;: &quot;VALIDATION_ERROR&quot;,\n    &quot;message&quot;: &quot;入力値が不正です&quot;,\n    &quot;details&quot;: [\n      { &quot;field&quot;: &quot;email&quot;, &quot;message&quot;: &quot;有効なメールアドレスを入力してください&quot; },\n      { &quot;field&quot;: &quot;age&quot;, &quot;message&quot;: &quot;18歳以上である必要があります&quot; }\n    ]\n  }\n}\n</code></pre>\n<p><code>code</code> にはマシンリーダブルな文字列を入れると、クライアントがプログラムで判定できます。</p>\n<h2 id=\"%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%8B%E3%83%B3%E3%82%B0\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/rest-api-design/#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%8B%E3%83%B3%E3%82%B0\" class=\"header-anchor\">バージョニング</a></h2>\n<p>API を公開した後に破壊的変更をする場合はバージョンを切ります。</p>\n<pre><code># URL パスにバージョンを含める（最も一般的）\n/v1/users\n/v2/users\n\n# ヘッダーでバージョン指定（URL をきれいに保ちたい場合）\nAccept: application/vnd.myapp.v2+json\n</code></pre>\n<h2 id=\"%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/rest-api-design/#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3\" class=\"header-anchor\">ページネーション</a></h2>\n<p>大量データの取得にはページネーションを必ず実装します。</p>\n<pre><code># カーソルベース（SNS のような無限スクロールに向く）\nGET /posts?cursor=eyJpZCI6MTAwfQ&amp;limit=20\n\n# オフセットベース（ページ番号指定に向く）\nGET /products?page=3&amp;per_page=20\n</code></pre>\n<p>レスポンスには次のページの情報を含めます。</p>\n<pre><code class=\"language-json\">{\n  &quot;data&quot;: [...],\n  &quot;pagination&quot;: {\n    &quot;next_cursor&quot;: &quot;eyJpZCI6MTIwfQ&quot;,\n    &quot;has_more&quot;: true\n  }\n}\n</code></pre>\n<h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/rest-api-design/#%E3%81%BE%E3%81%A8%E3%82%81\" class=\"header-anchor\">まとめ</a></h2>\n<ul>\n<li>URL はリソース（名詞）で設計する</li>\n<li>HTTP メソッドとステータスコードのセマンティクスに従う</li>\n<li>エラーレスポンスの形式を統一する</li>\n<li>公開後の変更にはバージョニングで対応</li>\n<li>一覧取得には必ずページネーションを</li>\n</ul>\n<p>設計に迷ったときは「クライアントを実装する人が直感的に使えるか」を基準にすると判断しやすいです。</p>\n","date_published":"Sun, 29 Mar 2026 00:00:00 GMT"},{"id":"https://mejiro877.github.io/lume-blog/posts/sql-performance/","url":"https://mejiro877.github.io/lume-blog/posts/sql-performance/","title":"SQL クエリのパフォーマンス改善","content_html":"<p>アプリケーションが遅い原因の多くはデータベースにあります。インデックスの使い方から、よくあるアンチパターンまで、クエリ改善の基本を整理します。</p>\n<!-- more -->\n<h2 id=\"%E3%81%BE%E3%81%9A-explain-%E3%81%A7%E5%AE%9F%E8%A1%8C%E8%A8%88%E7%94%BB%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E3%81%BE%E3%81%9A-explain-%E3%81%A7%E5%AE%9F%E8%A1%8C%E8%A8%88%E7%94%BB%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B\" class=\"header-anchor\">まず <code>EXPLAIN</code> で実行計画を確認する</a></h2>\n<p>チューニングの前に、データベースがどのようにクエリを実行しているか確認します。</p>\n<pre><code class=\"language-sql\">EXPLAIN ANALYZE\nSELECT * FROM orders\nWHERE user_id = 123 AND status = 'pending';\n</code></pre>\n<p>注目するポイント:</p>\n<ul>\n<li><code>Seq Scan</code> → テーブル全体を走査（遅い）</li>\n<li><code>Index Scan</code> → インデックスを使用（速い）</li>\n<li><code>rows</code> の見積もりと実際の差が大きい → 統計情報が古い</li>\n</ul>\n<h2 id=\"%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E6%AD%A3%E3%81%97%E3%81%8F%E4%BD%BF%E3%81%86\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E6%AD%A3%E3%81%97%E3%81%8F%E4%BD%BF%E3%81%86\" class=\"header-anchor\">インデックスを正しく使う</a></h2>\n<h3 id=\"%E8%A4%87%E5%90%88%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E9%A0%86%E5%BA%8F\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E8%A4%87%E5%90%88%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E9%A0%86%E5%BA%8F\" class=\"header-anchor\">複合インデックスのカラム順序</a></h3>\n<p>複合インデックスは左端のカラムから順に効きます。</p>\n<pre><code class=\"language-sql\">-- このインデックスは (user_id, status) の順\nCREATE INDEX idx_orders_user_status ON orders(user_id, status);\n\n-- 有効: user_id が先頭にある\nSELECT * FROM orders WHERE user_id = 123 AND status = 'pending';\nSELECT * FROM orders WHERE user_id = 123;\n\n-- 無効: user_id を含まない\nSELECT * FROM orders WHERE status = 'pending';\n</code></pre>\n<p>カーディナリティ（値の種類数）が高いカラムを先頭に置くのが基本です。</p>\n<h3 id=\"%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8B%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8B%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3\" class=\"header-anchor\">インデックスが使われなくなるパターン</a></h3>\n<pre><code class=\"language-sql\">-- 関数をかけるとインデックスが効かない\nWHERE UPPER(email) = 'USER@EXAMPLE.COM'  -- NG\nWHERE email = 'user@example.com'          -- OK\n\n-- 型が違うと暗黙変換でインデックスが効かないことがある\nWHERE user_id = '123'  -- user_id が INTEGER の場合は注意\n\n-- LIKE の前方一致はOK、後方一致はNG\nWHERE name LIKE 'John%'   -- OK\nWHERE name LIKE '%John'   -- NG（インデックス非効率）\n</code></pre>\n<h2 id=\"n%2B1-%E5%95%8F%E9%A1%8C%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#n%2B1-%E5%95%8F%E9%A1%8C%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B\" class=\"header-anchor\">N+1 問題を避ける</a></h2>\n<p>ループの中でクエリを発行するのは最も頻繁に見るパフォーマンス問題です。</p>\n<pre><code class=\"language-sql\">-- N+1: ユーザーごとに注文数を取得 (1 + N 回のクエリ)\nSELECT * FROM users;\n-- ↓ ループ内で N 回実行\nSELECT COUNT(*) FROM orders WHERE user_id = ?;\n\n-- 改善: JOIN で1回にまとめる\nSELECT u.id, u.name, COUNT(o.id) AS order_count\nFROM users u\nLEFT JOIN orders o ON o.user_id = u.id\nGROUP BY u.id, u.name;\n</code></pre>\n<h2 id=\"%E4%B8%8D%E8%A6%81%E3%81%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%AA%E3%81%84\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E4%B8%8D%E8%A6%81%E3%81%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%AA%E3%81%84\" class=\"header-anchor\">不要なデータを取得しない</a></h2>\n<pre><code class=\"language-sql\">-- SELECT * は避ける\nSELECT * FROM products;  -- NG\n\n-- 必要なカラムだけ取得\nSELECT id, name, price FROM products;  -- OK\n\n-- LIMIT を忘れない\nSELECT id, name FROM products\nORDER BY created_at DESC\nLIMIT 20;\n</code></pre>\n<h2 id=\"%E3%82%B5%E3%83%96%E3%82%AF%E3%82%A8%E3%83%AA%E3%82%88%E3%82%8A-join-%E3%82%92%E4%BD%BF%E3%81%86\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E3%82%B5%E3%83%96%E3%82%AF%E3%82%A8%E3%83%AA%E3%82%88%E3%82%8A-join-%E3%82%92%E4%BD%BF%E3%81%86\" class=\"header-anchor\">サブクエリより JOIN を使う</a></h2>\n<p>相関サブクエリは行ごとに実行されるため遅くなりがちです。</p>\n<pre><code class=\"language-sql\">-- 遅い: 相関サブクエリ\nSELECT name,\n  (SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS cnt\nFROM users u;\n\n-- 速い: JOIN で書き換え\nSELECT u.name, COUNT(o.id) AS cnt\nFROM users u\nLEFT JOIN orders o ON o.user_id = u.id\nGROUP BY u.id, u.name;\n</code></pre>\n<h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/sql-performance/#%E3%81%BE%E3%81%A8%E3%82%81\" class=\"header-anchor\">まとめ</a></h2>\n<ol>\n<li><code>EXPLAIN ANALYZE</code> で問題のあるクエリを特定する</li>\n<li>適切なインデックスを貼る（複合インデックスのカラム順に注意）</li>\n<li>N+1 は JOIN でまとめる</li>\n<li><code>SELECT *</code> をやめ、必要なカラムだけ取得する</li>\n</ol>\n<p>まずスロークエリログを有効にして、実際に遅いクエリを特定するところから始めましょう。</p>\n","date_published":"Sun, 22 Mar 2026 00:00:00 GMT"},{"id":"https://mejiro877.github.io/lume-blog/posts/typescript-tips/","url":"https://mejiro877.github.io/lume-blog/posts/typescript-tips/","title":"TypeScript 型安全テクニック","content_html":"<p>TypeScript は「型を書けばいい」だけではなく、型システムをうまく使うことでバグを設計レベルで防げます。日常的に使えるテクニックをまとめます。</p>\n<!-- more -->\n<h2 id=\"unknown-%E3%82%92-any-%E3%81%AE%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%AB%E4%BD%BF%E3%81%86\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/typescript-tips/#unknown-%E3%82%92-any-%E3%81%AE%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%AB%E4%BD%BF%E3%81%86\" class=\"header-anchor\"><code>unknown</code> を <code>any</code> の代わりに使う</a></h2>\n<p><code>any</code> は型チェックを完全に無効にしますが、<code>unknown</code> は「何か分からない値」として扱い、使う前に型チェックを強制します。</p>\n<pre><code class=\"language-typescript\">// 危険: any はどこでも使えてしまう\nfunction parseData(input: any) {\n  return input.name.toUpperCase(); // 実行時エラーの可能性\n}\n\n// 安全: unknown は型ガードが必要\nfunction parseData(input: unknown) {\n  if (typeof input === &quot;object&quot; &amp;&amp; input !== null &amp;&amp; &quot;name&quot; in input) {\n    return (input as { name: string }).name.toUpperCase();\n  }\n  throw new Error(&quot;Invalid input&quot;);\n}\n</code></pre>\n<p>外部 API のレスポンスや JSON パースの結果には <code>unknown</code> を使いましょう。</p>\n<h2 id=\"discriminated-union-%E3%81%A7%E7%8A%B6%E6%85%8B%E3%82%92%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/typescript-tips/#discriminated-union-%E3%81%A7%E7%8A%B6%E6%85%8B%E3%82%92%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B\" class=\"header-anchor\">Discriminated Union で状態を表現する</a></h2>\n<p>フラグのような <code>boolean</code> の組み合わせより、Union 型で状態を明示するほうが安全です。</p>\n<pre><code class=\"language-typescript\">// 問題: isLoading と error が同時に true になれる\ntype State = {\n  isLoading: boolean;\n  data: User | null;\n  error: string | null;\n};\n\n// 改善: 状態が排他的になる\ntype State =\n  | { status: &quot;idle&quot; }\n  | { status: &quot;loading&quot; }\n  | { status: &quot;success&quot;; data: User }\n  | { status: &quot;error&quot;; error: string };\n\nfunction render(state: State) {\n  switch (state.status) {\n    case &quot;success&quot;:\n      return state.data.name; // data が確実に存在する\n    case &quot;error&quot;:\n      return state.error;     // error が確実に存在する\n  }\n}\n</code></pre>\n<h2 id=\"satisfies-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%A7%E5%9E%8B%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%97%E3%81%AA%E3%81%8C%E3%82%89%E5%9E%8B%E6%8E%A8%E8%AB%96%E3%82%92%E4%BF%9D%E3%81%A4\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/typescript-tips/#satisfies-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%A7%E5%9E%8B%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%97%E3%81%AA%E3%81%8C%E3%82%89%E5%9E%8B%E6%8E%A8%E8%AB%96%E3%82%92%E4%BF%9D%E3%81%A4\" class=\"header-anchor\"><code>satisfies</code> 演算子で型チェックしながら型推論を保つ</a></h2>\n<p>TypeScript 4.9 で追加された <code>satisfies</code> は、型の整合性を確認しつつ推論された型を保持します。</p>\n<pre><code class=\"language-typescript\">type Config = {\n  port: number;\n  host: string;\n};\n\n// as を使うと推論が失われる\nconst config = { port: 3000, host: &quot;localhost&quot; } as Config;\nconfig.port; // number\n\n// satisfies なら具体的な型が残る\nconst config = { port: 3000, host: &quot;localhost&quot; } satisfies Config;\nconfig.port; // 3000 (リテラル型)\n</code></pre>\n<p>設定オブジェクトの定義によく使います。</p>\n<h2 id=\"%E5%9E%8B%E3%82%AC%E3%83%BC%E3%83%89%E9%96%A2%E6%95%B0%E3%81%A7%E3%83%8A%E3%83%AD%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%B0%E3%82%92%E5%86%8D%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/typescript-tips/#%E5%9E%8B%E3%82%AC%E3%83%BC%E3%83%89%E9%96%A2%E6%95%B0%E3%81%A7%E3%83%8A%E3%83%AD%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%B0%E3%82%92%E5%86%8D%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B\" class=\"header-anchor\">型ガード関数でナローイングを再利用する</a></h2>\n<p>同じ型チェックを複数箇所に書くなら、型ガード関数にまとめます。</p>\n<pre><code class=\"language-typescript\">type ApiResponse&lt;T&gt; =\n  | { success: true; data: T }\n  | { success: false; error: string };\n\nfunction isSuccess&lt;T&gt;(res: ApiResponse&lt;T&gt;): res is { success: true; data: T } {\n  return res.success === true;\n}\n\nconst response = await fetchUser();\nif (isSuccess(response)) {\n  console.log(response.data); // T 型として使える\n}\n</code></pre>\n<h2 id=\"readonly-%E3%81%A8-as-const-%E3%81%A7%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB%E3%81%AA%E3%83%90%E3%82%B0%E3%82%92%E9%98%B2%E3%81%90\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/typescript-tips/#readonly-%E3%81%A8-as-const-%E3%81%A7%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB%E3%81%AA%E3%83%90%E3%82%B0%E3%82%92%E9%98%B2%E3%81%90\" class=\"header-anchor\"><code>Readonly</code> と <code>as const</code> でミュータブルなバグを防ぐ</a></h2>\n<p>意図せず変更されては困るオブジェクトは読み取り専用にします。</p>\n<pre><code class=\"language-typescript\">// 定数オブジェクト\nconst ROLES = [&quot;admin&quot;, &quot;editor&quot;, &quot;viewer&quot;] as const;\ntype Role = typeof ROLES[number]; // &quot;admin&quot; | &quot;editor&quot; | &quot;viewer&quot;\n\n// 関数の引数を変更不可にする\nfunction process(config: Readonly&lt;Config&gt;) {\n  // config.port = 8080; // エラー\n}\n</code></pre>\n<h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/typescript-tips/#%E3%81%BE%E3%81%A8%E3%82%81\" class=\"header-anchor\">まとめ</a></h2>\n<ul>\n<li><code>any</code> より <code>unknown</code> — 型安全を保ちつつ柔軟に</li>\n<li>Discriminated Union — 状態管理を型で正確に表現</li>\n<li><code>satisfies</code> — 型チェックと型推論を両立</li>\n<li>型ガード関数 — ナローイングの再利用</li>\n<li><code>as const</code> / <code>Readonly</code> — 不変性をコンパイル時に保証</li>\n</ul>\n<p>型を「注釈」ではなく「設計ツール」として使うと、TypeScript の本領が発揮されます。</p>\n","date_published":"Sun, 15 Mar 2026 00:00:00 GMT"},{"id":"https://mejiro877.github.io/lume-blog/posts/git-workflow/","url":"https://mejiro877.github.io/lume-blog/posts/git-workflow/","title":"Git ブランチ戦略まとめ","content_html":"<p>個人開発では <code>main</code> ブランチだけで事足りますが、チームで開発するとブランチ管理が重要になります。代表的な戦略と、それぞれの使いどころを整理します。</p>\n<!-- more -->\n<h2 id=\"github-flow-%E2%80%94-%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%A7%E9%80%9F%E3%81%84\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/git-workflow/#github-flow-%E2%80%94-%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%A7%E9%80%9F%E3%81%84\" class=\"header-anchor\">GitHub Flow — シンプルで速い</a></h2>\n<p>最もシンプルな戦略です。ブランチは <code>main</code> と <code>feature/*</code> の2種類だけ。</p>\n<pre><code>main\n  └── feature/add-login\n  └── feature/fix-typo\n</code></pre>\n<p><strong>手順:</strong></p>\n<ol>\n<li><code>main</code> から <code>feature/xxx</code> を切る</li>\n<li>実装して PR を出す</li>\n<li>レビュー後に <code>main</code> へマージ</li>\n<li>すぐデプロイ</li>\n</ol>\n<p><strong>向いている場面:</strong> 継続的デプロイができるプロダクト、スタートアップ、小〜中規模チーム。</p>\n<pre><code class=\"language-bash\">git switch main\ngit pull origin main\ngit switch -c feature/add-login\n\n# 実装...\n\ngit push origin feature/add-login\n# → GitHub で PR を作成\n</code></pre>\n<h2 id=\"git-flow-%E2%80%94-%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E7%AE%A1%E7%90%86%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%AA%E3%81%A8%E3%81%8D\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/git-workflow/#git-flow-%E2%80%94-%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E7%AE%A1%E7%90%86%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%AA%E3%81%A8%E3%81%8D\" class=\"header-anchor\">Git Flow — リリース管理が必要なとき</a></h2>\n<p>バージョン管理が必要なアプリ（モバイルアプリ、パッケージなど）向けです。</p>\n<pre><code>main          ← 本番リリース済みコード\ndevelop       ← 開発統合ブランチ\n  └── feature/xxx\n  └── release/1.2.0\n  └── hotfix/crash-fix\n</code></pre>\n<p><strong>ブランチの役割:</strong></p>\n<table>\n<thead>\n<tr>\n<th>ブランチ</th>\n<th>役割</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>main</code></td>\n<td>リリース済みコード</td>\n</tr>\n<tr>\n<td><code>develop</code></td>\n<td>次のリリースへの統合</td>\n</tr>\n<tr>\n<td><code>feature/*</code></td>\n<td>機能開発</td>\n</tr>\n<tr>\n<td><code>release/*</code></td>\n<td>リリース前の最終調整</td>\n</tr>\n<tr>\n<td><code>hotfix/*</code></td>\n<td>本番の緊急修正</td>\n</tr>\n</tbody>\n</table>\n<p>複雑な分、厳密なリリース管理が可能です。</p>\n<h2 id=\"%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E7%B5%B1%E4%B8%80%E3%81%99%E3%82%8B\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/git-workflow/#%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E7%B5%B1%E4%B8%80%E3%81%99%E3%82%8B\" class=\"header-anchor\">コミットメッセージを統一する</a></h2>\n<p>どの戦略を採用しても、コミットメッセージのルールを決めておくと <code>git log</code> が読みやすくなります。</p>\n<pre><code>feat: ログイン機能を追加\nfix: パスワードリセット時のエラーを修正\ndocs: README にセットアップ手順を追記\nrefactor: 認証ロジックをサービス層へ移動\nchore: 依存ライブラリを更新\n</code></pre>\n<p><a href=\"https://www.conventionalcommits.org/\">Conventional Commits</a> に従うと、CHANGELOGの自動生成もできます。</p>\n<h2 id=\"pr-%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AF%E5%B0%8F%E3%81%95%E3%81%8F%E4%BF%9D%E3%81%A4\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/git-workflow/#pr-%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AF%E5%B0%8F%E3%81%95%E3%81%8F%E4%BF%9D%E3%81%A4\" class=\"header-anchor\">PR のサイズは小さく保つ</a></h2>\n<p>ブランチ戦略と同じくらい重要なのが PR のサイズです。</p>\n<ul>\n<li>1 PR = 1 つの目的</li>\n<li>変更行数は 400 行以内を目安に</li>\n<li>レビュアーが 15 分で読み切れるくらいが理想</li>\n</ul>\n<p>大きな機能はフラグで隠しながら小さく分割してマージするのが現実的です。</p>\n<h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/git-workflow/#%E3%81%BE%E3%81%A8%E3%82%81\" class=\"header-anchor\">まとめ</a></h2>\n<ul>\n<li>小〜中規模で CI/CD が整っているなら <strong>GitHub Flow</strong></li>\n<li>バージョン管理が必要なら <strong>Git Flow</strong></li>\n<li>いずれにせよコミットメッセージとPRサイズのルールは最初に決める</li>\n</ul>\n<p>ツールよりもチームの合意が大切です。</p>\n","date_published":"Sun, 08 Mar 2026 00:00:00 GMT"},{"id":"https://mejiro877.github.io/lume-blog/posts/docker-basics/","url":"https://mejiro877.github.io/lume-blog/posts/docker-basics/","title":"Docker 入門 — イメージとコンテナの基礎","content_html":"<p>Docker を使い始めたとき、「イメージ」と「コンテナ」の違いで混乱した経験はないでしょうか。この記事ではその概念を整理し、日常的な開発で使う基本コマンドをまとめます。</p>\n<!-- more -->\n<h2 id=\"%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%A8%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AE%E9%81%95%E3%81%84\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/docker-basics/#%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%A8%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AE%E9%81%95%E3%81%84\" class=\"header-anchor\">イメージとコンテナの違い</a></h2>\n<p><strong>イメージ</strong>はコンテナを起動するための設計図（テンプレート）です。読み取り専用で、<code>Dockerfile</code> から作られます。</p>\n<p><strong>コンテナ</strong>はイメージを実際に起動したものです。書き込み可能な実行環境で、何度でも起動・停止できます。</p>\n<pre><code>Dockerfile → (build) → イメージ → (run) → コンテナ\n</code></pre>\n<p>クラスとインスタンスの関係に近いイメージです。</p>\n<h2 id=\"%E3%82%88%E3%81%8F%E4%BD%BF%E3%81%86%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/docker-basics/#%E3%82%88%E3%81%8F%E4%BD%BF%E3%81%86%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89\" class=\"header-anchor\">よく使うコマンド</a></h2>\n<pre><code class=\"language-bash\"># イメージを取得\ndocker pull node:22\n\n# コンテナを起動（起動後すぐ削除）\ndocker run --rm node:22 node --version\n\n# バックグラウンドで起動\ndocker run -d -p 3000:3000 --name my-app my-image\n\n# 起動中のコンテナ一覧\ndocker ps\n\n# コンテナの中に入る\ndocker exec -it my-app bash\n\n# コンテナを停止・削除\ndocker stop my-app &amp;&amp; docker rm my-app\n</code></pre>\n<h2 id=\"dockerfile-%E3%81%AE%E5%9F%BA%E6%9C%AC%E6%A7%8B%E9%80%A0\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/docker-basics/#dockerfile-%E3%81%AE%E5%9F%BA%E6%9C%AC%E6%A7%8B%E9%80%A0\" class=\"header-anchor\">Dockerfile の基本構造</a></h2>\n<pre><code class=\"language-dockerfile\">FROM node:22-alpine\n\nWORKDIR /app\n\nCOPY package*.json ./\nRUN npm ci\n\nCOPY . .\n\nEXPOSE 3000\nCMD [&quot;node&quot;, &quot;server.js&quot;]\n</code></pre>\n<p><code>COPY</code> と <code>RUN</code> はキャッシュが効くので、変更頻度の低いものを上に書くのがポイントです。<code>package.json</code> を先にコピーして <code>npm ci</code> することで、ソースコードを変えても依存関係の再インストールが走りません。</p>\n<h2 id=\"docker-compose-%E3%82%92%E4%BD%BF%E3%81%86\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/docker-basics/#docker-compose-%E3%82%92%E4%BD%BF%E3%81%86\" class=\"header-anchor\">docker compose を使う</a></h2>\n<p>複数コンテナを管理するときは <code>docker compose</code> が便利です。</p>\n<pre><code class=\"language-yaml\">services:\n  app:\n    build: .\n    ports:\n      - &quot;3000:3000&quot;\n    depends_on:\n      - db\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_PASSWORD: password\n</code></pre>\n<pre><code class=\"language-bash\">docker compose up -d    # 起動\ndocker compose down     # 停止\ndocker compose logs -f  # ログ確認\n</code></pre>\n<h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\" tabindex=\"-1\"><a href=\"https://mejiro877.github.io/lume-blog/posts/docker-basics/#%E3%81%BE%E3%81%A8%E3%82%81\" class=\"header-anchor\">まとめ</a></h2>\n<p>Docker の基本は「イメージを作って、コンテナとして動かす」この一点です。最初は <code>docker run</code> と <code>docker compose up</code> だけ覚えておけば十分です。慣れてきたら <code>Dockerfile</code> の最適化（マルチステージビルドなど）に進みましょう。</p>\n","date_published":"Sun, 01 Mar 2026 00:00:00 GMT"}]}