<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:webfeeds="http://webfeeds.org/rss/1.0" version="2.0">
  <channel>
    <title>mejiro877のブログ</title>
    <link>https://mejiro877.github.io/lume-blog/</link>
    <atom:link href="https://mejiro877.github.io/lume-blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>プログラミングに関する技術ブログ</description>
    <lastBuildDate>Wed, 01 Apr 2026 11:10:27 GMT</lastBuildDate>
    <language>en</language>
    <generator>Lume 3.2.2</generator>
    <item>
      <title>REST API 設計の実践原則</title>
      <link>https://mejiro877.github.io/lume-blog/posts/rest-api-design/</link>
      <guid isPermaLink="false">https://mejiro877.github.io/lume-blog/posts/rest-api-design/</guid>
      <content:encoded>
        <![CDATA[<p>REST API は一度公開すると変更しにくいため、設計段階での判断が重要です。後悔しないための設計原則をまとめます。</p>
<!-- more -->
<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>
<p>URL はアクションではなくリソース（名詞）を表します。</p>
<pre><code># 悪い例: 動詞を使っている
GET  /getUsers
POST /createUser
POST /deleteUser?id=123

# 良い例: リソース + HTTP メソッドで表現
GET    /users          # 一覧取得
POST   /users          # 作成
GET    /users/123      # 1件取得
PUT    /users/123      # 全体更新
PATCH  /users/123      # 部分更新
DELETE /users/123      # 削除
</code></pre>
<p>階層関係は URL で表現します。</p>
<pre><code>GET /users/123/orders        # ユーザー123の注文一覧
GET /users/123/orders/456    # 特定の注文
</code></pre>
<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>
<table>
<thead>
<tr>
<th>コード</th>
<th>意味</th>
<th>使いどころ</th>
</tr>
</thead>
<tbody>
<tr>
<td>200</td>
<td>OK</td>
<td>取得・更新成功</td>
</tr>
<tr>
<td>201</td>
<td>Created</td>
<td>リソース作成成功</td>
</tr>
<tr>
<td>204</td>
<td>No Content</td>
<td>削除成功（ボディなし）</td>
</tr>
<tr>
<td>400</td>
<td>Bad Request</td>
<td>バリデーションエラー</td>
</tr>
<tr>
<td>401</td>
<td>Unauthorized</td>
<td>未認証</td>
</tr>
<tr>
<td>403</td>
<td>Forbidden</td>
<td>認可エラー（ログイン済みだが権限なし）</td>
</tr>
<tr>
<td>404</td>
<td>Not Found</td>
<td>リソースが存在しない</td>
</tr>
<tr>
<td>409</td>
<td>Conflict</td>
<td>重複など競合</td>
</tr>
<tr>
<td>422</td>
<td>Unprocessable Entity</td>
<td>意味的に不正なリクエスト</td>
</tr>
<tr>
<td>500</td>
<td>Internal Server Error</td>
<td>サーバー側の予期しないエラー</td>
</tr>
</tbody>
</table>
<p><code>200</code> をすべてに使ってエラーをボディで返すのはやめましょう。</p>
<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>
<p>エラー時のレスポンス形式を統一しておくと、クライアント側の実装が楽になります。</p>
<pre><code class="language-json">{
  &quot;error&quot;: {
    &quot;code&quot;: &quot;VALIDATION_ERROR&quot;,
    &quot;message&quot;: &quot;入力値が不正です&quot;,
    &quot;details&quot;: [
      { &quot;field&quot;: &quot;email&quot;, &quot;message&quot;: &quot;有効なメールアドレスを入力してください&quot; },
      { &quot;field&quot;: &quot;age&quot;, &quot;message&quot;: &quot;18歳以上である必要があります&quot; }
    ]
  }
}
</code></pre>
<p><code>code</code> にはマシンリーダブルな文字列を入れると、クライアントがプログラムで判定できます。</p>
<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>
<p>API を公開した後に破壊的変更をする場合はバージョンを切ります。</p>
<pre><code># URL パスにバージョンを含める（最も一般的）
/v1/users
/v2/users

# ヘッダーでバージョン指定（URL をきれいに保ちたい場合）
Accept: application/vnd.myapp.v2+json
</code></pre>
<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>
<p>大量データの取得にはページネーションを必ず実装します。</p>
<pre><code># カーソルベース（SNS のような無限スクロールに向く）
GET /posts?cursor=eyJpZCI6MTAwfQ&amp;limit=20

# オフセットベース（ページ番号指定に向く）
GET /products?page=3&amp;per_page=20
</code></pre>
<p>レスポンスには次のページの情報を含めます。</p>
<pre><code class="language-json">{
  &quot;data&quot;: [...],
  &quot;pagination&quot;: {
    &quot;next_cursor&quot;: &quot;eyJpZCI6MTIwfQ&quot;,
    &quot;has_more&quot;: true
  }
}
</code></pre>
<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>
<ul>
<li>URL はリソース（名詞）で設計する</li>
<li>HTTP メソッドとステータスコードのセマンティクスに従う</li>
<li>エラーレスポンスの形式を統一する</li>
<li>公開後の変更にはバージョニングで対応</li>
<li>一覧取得には必ずページネーションを</li>
</ul>
<p>設計に迷ったときは「クライアントを実装する人が直感的に使えるか」を基準にすると判断しやすいです。</p>
]]>
      </content:encoded>
      <pubDate>Sun, 29 Mar 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>SQL クエリのパフォーマンス改善</title>
      <link>https://mejiro877.github.io/lume-blog/posts/sql-performance/</link>
      <guid isPermaLink="false">https://mejiro877.github.io/lume-blog/posts/sql-performance/</guid>
      <content:encoded>
        <![CDATA[<p>アプリケーションが遅い原因の多くはデータベースにあります。インデックスの使い方から、よくあるアンチパターンまで、クエリ改善の基本を整理します。</p>
<!-- more -->
<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>
<p>チューニングの前に、データベースがどのようにクエリを実行しているか確認します。</p>
<pre><code class="language-sql">EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 123 AND status = 'pending';
</code></pre>
<p>注目するポイント:</p>
<ul>
<li><code>Seq Scan</code> → テーブル全体を走査（遅い）</li>
<li><code>Index Scan</code> → インデックスを使用（速い）</li>
<li><code>rows</code> の見積もりと実際の差が大きい → 統計情報が古い</li>
</ul>
<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>
<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>
<p>複合インデックスは左端のカラムから順に効きます。</p>
<pre><code class="language-sql">-- このインデックスは (user_id, status) の順
CREATE INDEX idx_orders_user_status ON orders(user_id, status);

-- 有効: user_id が先頭にある
SELECT * FROM orders WHERE user_id = 123 AND status = 'pending';
SELECT * FROM orders WHERE user_id = 123;

-- 無効: user_id を含まない
SELECT * FROM orders WHERE status = 'pending';
</code></pre>
<p>カーディナリティ（値の種類数）が高いカラムを先頭に置くのが基本です。</p>
<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>
<pre><code class="language-sql">-- 関数をかけるとインデックスが効かない
WHERE UPPER(email) = 'USER@EXAMPLE.COM'  -- NG
WHERE email = 'user@example.com'          -- OK

-- 型が違うと暗黙変換でインデックスが効かないことがある
WHERE user_id = '123'  -- user_id が INTEGER の場合は注意

-- LIKE の前方一致はOK、後方一致はNG
WHERE name LIKE 'John%'   -- OK
WHERE name LIKE '%John'   -- NG（インデックス非効率）
</code></pre>
<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>
<p>ループの中でクエリを発行するのは最も頻繁に見るパフォーマンス問題です。</p>
<pre><code class="language-sql">-- N+1: ユーザーごとに注文数を取得 (1 + N 回のクエリ)
SELECT * FROM users;
-- ↓ ループ内で N 回実行
SELECT COUNT(*) FROM orders WHERE user_id = ?;

-- 改善: JOIN で1回にまとめる
SELECT u.id, u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;
</code></pre>
<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>
<pre><code class="language-sql">-- SELECT * は避ける
SELECT * FROM products;  -- NG

-- 必要なカラムだけ取得
SELECT id, name, price FROM products;  -- OK

-- LIMIT を忘れない
SELECT id, name FROM products
ORDER BY created_at DESC
LIMIT 20;
</code></pre>
<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>
<p>相関サブクエリは行ごとに実行されるため遅くなりがちです。</p>
<pre><code class="language-sql">-- 遅い: 相関サブクエリ
SELECT name,
  (SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS cnt
FROM users u;

-- 速い: JOIN で書き換え
SELECT u.name, COUNT(o.id) AS cnt
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;
</code></pre>
<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>
<ol>
<li><code>EXPLAIN ANALYZE</code> で問題のあるクエリを特定する</li>
<li>適切なインデックスを貼る（複合インデックスのカラム順に注意）</li>
<li>N+1 は JOIN でまとめる</li>
<li><code>SELECT *</code> をやめ、必要なカラムだけ取得する</li>
</ol>
<p>まずスロークエリログを有効にして、実際に遅いクエリを特定するところから始めましょう。</p>
]]>
      </content:encoded>
      <pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>TypeScript 型安全テクニック</title>
      <link>https://mejiro877.github.io/lume-blog/posts/typescript-tips/</link>
      <guid isPermaLink="false">https://mejiro877.github.io/lume-blog/posts/typescript-tips/</guid>
      <content:encoded>
        <![CDATA[<p>TypeScript は「型を書けばいい」だけではなく、型システムをうまく使うことでバグを設計レベルで防げます。日常的に使えるテクニックをまとめます。</p>
<!-- more -->
<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>
<p><code>any</code> は型チェックを完全に無効にしますが、<code>unknown</code> は「何か分からない値」として扱い、使う前に型チェックを強制します。</p>
<pre><code class="language-typescript">// 危険: any はどこでも使えてしまう
function parseData(input: any) {
  return input.name.toUpperCase(); // 実行時エラーの可能性
}

// 安全: unknown は型ガードが必要
function parseData(input: unknown) {
  if (typeof input === &quot;object&quot; &amp;&amp; input !== null &amp;&amp; &quot;name&quot; in input) {
    return (input as { name: string }).name.toUpperCase();
  }
  throw new Error(&quot;Invalid input&quot;);
}
</code></pre>
<p>外部 API のレスポンスや JSON パースの結果には <code>unknown</code> を使いましょう。</p>
<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>
<p>フラグのような <code>boolean</code> の組み合わせより、Union 型で状態を明示するほうが安全です。</p>
<pre><code class="language-typescript">// 問題: isLoading と error が同時に true になれる
type State = {
  isLoading: boolean;
  data: User | null;
  error: string | null;
};

// 改善: 状態が排他的になる
type State =
  | { status: &quot;idle&quot; }
  | { status: &quot;loading&quot; }
  | { status: &quot;success&quot;; data: User }
  | { status: &quot;error&quot;; error: string };

function render(state: State) {
  switch (state.status) {
    case &quot;success&quot;:
      return state.data.name; // data が確実に存在する
    case &quot;error&quot;:
      return state.error;     // error が確実に存在する
  }
}
</code></pre>
<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>
<p>TypeScript 4.9 で追加された <code>satisfies</code> は、型の整合性を確認しつつ推論された型を保持します。</p>
<pre><code class="language-typescript">type Config = {
  port: number;
  host: string;
};

// as を使うと推論が失われる
const config = { port: 3000, host: &quot;localhost&quot; } as Config;
config.port; // number

// satisfies なら具体的な型が残る
const config = { port: 3000, host: &quot;localhost&quot; } satisfies Config;
config.port; // 3000 (リテラル型)
</code></pre>
<p>設定オブジェクトの定義によく使います。</p>
<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>
<p>同じ型チェックを複数箇所に書くなら、型ガード関数にまとめます。</p>
<pre><code class="language-typescript">type ApiResponse&lt;T&gt; =
  | { success: true; data: T }
  | { success: false; error: string };

function isSuccess&lt;T&gt;(res: ApiResponse&lt;T&gt;): res is { success: true; data: T } {
  return res.success === true;
}

const response = await fetchUser();
if (isSuccess(response)) {
  console.log(response.data); // T 型として使える
}
</code></pre>
<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>
<p>意図せず変更されては困るオブジェクトは読み取り専用にします。</p>
<pre><code class="language-typescript">// 定数オブジェクト
const ROLES = [&quot;admin&quot;, &quot;editor&quot;, &quot;viewer&quot;] as const;
type Role = typeof ROLES[number]; // &quot;admin&quot; | &quot;editor&quot; | &quot;viewer&quot;

// 関数の引数を変更不可にする
function process(config: Readonly&lt;Config&gt;) {
  // config.port = 8080; // エラー
}
</code></pre>
<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>
<ul>
<li><code>any</code> より <code>unknown</code> — 型安全を保ちつつ柔軟に</li>
<li>Discriminated Union — 状態管理を型で正確に表現</li>
<li><code>satisfies</code> — 型チェックと型推論を両立</li>
<li>型ガード関数 — ナローイングの再利用</li>
<li><code>as const</code> / <code>Readonly</code> — 不変性をコンパイル時に保証</li>
</ul>
<p>型を「注釈」ではなく「設計ツール」として使うと、TypeScript の本領が発揮されます。</p>
]]>
      </content:encoded>
      <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Git ブランチ戦略まとめ</title>
      <link>https://mejiro877.github.io/lume-blog/posts/git-workflow/</link>
      <guid isPermaLink="false">https://mejiro877.github.io/lume-blog/posts/git-workflow/</guid>
      <content:encoded>
        <![CDATA[<p>個人開発では <code>main</code> ブランチだけで事足りますが、チームで開発するとブランチ管理が重要になります。代表的な戦略と、それぞれの使いどころを整理します。</p>
<!-- more -->
<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>
<p>最もシンプルな戦略です。ブランチは <code>main</code> と <code>feature/*</code> の2種類だけ。</p>
<pre><code>main
  └── feature/add-login
  └── feature/fix-typo
</code></pre>
<p><strong>手順:</strong></p>
<ol>
<li><code>main</code> から <code>feature/xxx</code> を切る</li>
<li>実装して PR を出す</li>
<li>レビュー後に <code>main</code> へマージ</li>
<li>すぐデプロイ</li>
</ol>
<p><strong>向いている場面:</strong> 継続的デプロイができるプロダクト、スタートアップ、小〜中規模チーム。</p>
<pre><code class="language-bash">git switch main
git pull origin main
git switch -c feature/add-login

# 実装...

git push origin feature/add-login
# → GitHub で PR を作成
</code></pre>
<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>
<p>バージョン管理が必要なアプリ（モバイルアプリ、パッケージなど）向けです。</p>
<pre><code>main          ← 本番リリース済みコード
develop       ← 開発統合ブランチ
  └── feature/xxx
  └── release/1.2.0
  └── hotfix/crash-fix
</code></pre>
<p><strong>ブランチの役割:</strong></p>
<table>
<thead>
<tr>
<th>ブランチ</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>main</code></td>
<td>リリース済みコード</td>
</tr>
<tr>
<td><code>develop</code></td>
<td>次のリリースへの統合</td>
</tr>
<tr>
<td><code>feature/*</code></td>
<td>機能開発</td>
</tr>
<tr>
<td><code>release/*</code></td>
<td>リリース前の最終調整</td>
</tr>
<tr>
<td><code>hotfix/*</code></td>
<td>本番の緊急修正</td>
</tr>
</tbody>
</table>
<p>複雑な分、厳密なリリース管理が可能です。</p>
<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>
<p>どの戦略を採用しても、コミットメッセージのルールを決めておくと <code>git log</code> が読みやすくなります。</p>
<pre><code>feat: ログイン機能を追加
fix: パスワードリセット時のエラーを修正
docs: README にセットアップ手順を追記
refactor: 認証ロジックをサービス層へ移動
chore: 依存ライブラリを更新
</code></pre>
<p><a href="https://www.conventionalcommits.org/">Conventional Commits</a> に従うと、CHANGELOGの自動生成もできます。</p>
<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>
<p>ブランチ戦略と同じくらい重要なのが PR のサイズです。</p>
<ul>
<li>1 PR = 1 つの目的</li>
<li>変更行数は 400 行以内を目安に</li>
<li>レビュアーが 15 分で読み切れるくらいが理想</li>
</ul>
<p>大きな機能はフラグで隠しながら小さく分割してマージするのが現実的です。</p>
<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>
<ul>
<li>小〜中規模で CI/CD が整っているなら <strong>GitHub Flow</strong></li>
<li>バージョン管理が必要なら <strong>Git Flow</strong></li>
<li>いずれにせよコミットメッセージとPRサイズのルールは最初に決める</li>
</ul>
<p>ツールよりもチームの合意が大切です。</p>
]]>
      </content:encoded>
      <pubDate>Sun, 08 Mar 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Docker 入門 — イメージとコンテナの基礎</title>
      <link>https://mejiro877.github.io/lume-blog/posts/docker-basics/</link>
      <guid isPermaLink="false">https://mejiro877.github.io/lume-blog/posts/docker-basics/</guid>
      <content:encoded>
        <![CDATA[<p>Docker を使い始めたとき、「イメージ」と「コンテナ」の違いで混乱した経験はないでしょうか。この記事ではその概念を整理し、日常的な開発で使う基本コマンドをまとめます。</p>
<!-- more -->
<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>
<p><strong>イメージ</strong>はコンテナを起動するための設計図（テンプレート）です。読み取り専用で、<code>Dockerfile</code> から作られます。</p>
<p><strong>コンテナ</strong>はイメージを実際に起動したものです。書き込み可能な実行環境で、何度でも起動・停止できます。</p>
<pre><code>Dockerfile → (build) → イメージ → (run) → コンテナ
</code></pre>
<p>クラスとインスタンスの関係に近いイメージです。</p>
<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>
<pre><code class="language-bash"># イメージを取得
docker pull node:22

# コンテナを起動（起動後すぐ削除）
docker run --rm node:22 node --version

# バックグラウンドで起動
docker run -d -p 3000:3000 --name my-app my-image

# 起動中のコンテナ一覧
docker ps

# コンテナの中に入る
docker exec -it my-app bash

# コンテナを停止・削除
docker stop my-app &amp;&amp; docker rm my-app
</code></pre>
<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>
<pre><code class="language-dockerfile">FROM node:22-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000
CMD [&quot;node&quot;, &quot;server.js&quot;]
</code></pre>
<p><code>COPY</code> と <code>RUN</code> はキャッシュが効くので、変更頻度の低いものを上に書くのがポイントです。<code>package.json</code> を先にコピーして <code>npm ci</code> することで、ソースコードを変えても依存関係の再インストールが走りません。</p>
<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>
<p>複数コンテナを管理するときは <code>docker compose</code> が便利です。</p>
<pre><code class="language-yaml">services:
  app:
    build: .
    ports:
      - &quot;3000:3000&quot;
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: password
</code></pre>
<pre><code class="language-bash">docker compose up -d    # 起動
docker compose down     # 停止
docker compose logs -f  # ログ確認
</code></pre>
<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>
<p>Docker の基本は「イメージを作って、コンテナとして動かす」この一点です。最初は <code>docker run</code> と <code>docker compose up</code> だけ覚えておけば十分です。慣れてきたら <code>Dockerfile</code> の最適化（マルチステージビルドなど）に進みましょう。</p>
]]>
      </content:encoded>
      <pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>