メインコンテンツへスキップ
友田 陽大
型安全・バリデーション
TypeScript
アーキテクチャ設計
型安全性
B2B SaaS
フロントエンド

本番TypeScriptの型安全規律:any禁止・Zodで境界を守り・NeverErrorで網羅性を強制する

型安全を「方針」で終わらせない実務ガイド。strict系tsconfig、any/as/enum禁止、Zodで境界をparseするSSoT設計、NeverErrorによる網羅性の強制、satisfies、branded型、CIの型カバレッジまで本番の動くコードで解説します。

公開日
読了時間
20分
著者
友田 陽大
シェア
目次

「うちはTypeScriptだから型安全です」——この一文ほど、当てにならない言葉はありません。any が一箇所でも混ざれば、その先のコードは全部「願望ベースのコーディング」になります。as でエラーを黙らせれば、コンパイラはもう守ってくれません。TypeScriptを使うことと、型安全であることは、まったく別物です。

この記事は「型安全を、方針ではなく規律(discipline)として本番に根付かせる」ための実務ガイドです。私は複数名チームの サブスク学習プラットフォーム(Next.js 16 + Turborepo モノレポ)と、Supabase + Expo のモノレポの双方で、any/as/enum の原則禁止・NeverError による網羅検査・CIでの型カバレッジ計測という同じ規律を敷いています。その実装レベルの判断軸を、TypeScript 公式・Zod 公式に忠実に、しかし公式より判断軸を厚くして書きます。

本記事の基準:TypeScript 5.x 系satisfiesconst 型パラメータは 5.0、using/Disposable は 5.2 で導入)、Zod 4(stable)。API は typescriptlang.orgzod.dev で確認した現行仕様に基づきます。

なお、異なる言語・別リポジトリにまたがる「エンドツーエンド型安全」(OpenAPI 契約優先)は別記事 Next.js 16 × Go × OpenAPI に、決済ドメインに型安全を効かせた具体サブスク決済の冪等性と型安全 に書きました。本記事はそれらの土台となる「1リポジトリ内の TypeScript そのものの規律」に集中します。

0. 全体像:型安全を支える「6つの規律」

型安全は単一の機能ではなく、層をなす規律の積み重ねです。下の層が崩れると上は意味をなしません。

規律目的本記事の章
基盤厳格な tsconfigコンパイラを最大出力で働かせる§1
言語any/as/enum を断つ型の抜け穴を塞ぐ§2
境界Zod で parse する外部入力を信頼しない§3
モデル判別可能なユニオン不正な状態を表現不能にする§4・§6
網羅NeverError分岐漏れをコンパイルエラーにする§5
同一性branded 型IDや金額の取り違えを防ぐ§7

そしてこれらを CI で強制して初めて、規律はチーム規模でも生き残ります(§8)。順に見ていきます。


1. 基盤:tsconfig はコンパイラの「出力設定」である

まず最初にやるべきは、コンパイラをフルパワーで動かすことです。strict: true は出発点であって終点ではありません。tsconfig リファレンスが定義する追加フラグまで入れて初めて、現場で効く防御になります。

// tsconfig.json — 本番推奨の最小強化セット
{
  "compilerOptions": {
    "strict": true,                          // 下記strict系を一括有効化
    "noUncheckedIndexedAccess": true,        // 配列/インデックスアクセスに undefined を付与
    "exactOptionalPropertyTypes": true,      // `?:` と `| undefined` を区別する
    "noImplicitOverride": true,              // override キーワードを必須化
    "noImplicitReturns": true,               // 全パスで return を強制
    "noFallthroughCasesInSwitch": true,      // switch のフォールスルーを禁止
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "verbatimModuleSyntax": true,            // 実行時に成立しない import/export を禁止
    "isolatedModules": true                  // 1ファイル単位のトランスパイル安全性
  }
}

strict: true が有効化するのは noImplicitAnystrictNullChecksstrictFunctionTypesstrictPropertyInitializationuseUnknownInCatchVariables などです。useUnknownInCatchVariables が含まれているのが重要で、これにより catch (e)eany ではなく unknown になります(§3で活きます)。

1-1. なぜ noUncheckedIndexedAccess を入れるか

これは入れていないチームが圧倒的に多いが、最も事故を防ぐフラグです。

// noUncheckedIndexedAccess: false(既定)—— 嘘の型
const users: string[] = [];
const first = users[0];        // 型は string(だが実体は undefined!)
first.toUpperCase();           // 実行時に TypeError、コンパイルは通る

// noUncheckedIndexedAccess: true —— 正直な型
const first2 = users[0];       // 型は string | undefined
first2.toUpperCase();          // コンパイルエラー:Object is possibly 'undefined'
first2?.toUpperCase();         // ガードを強制される(正しい)

配列・Record・オブジェクトのインデックスアクセスは、実体としては常に undefined を返しうる。それを型に反映させるだけのフラグですが、process.env.FOO(実体は string | undefined)を string として扱う事故を構造的に防ぎます。

1-2. exactOptionalPropertyTypes が分ける2つの意味

「キーが無い」と「キーはあるが値が undefined」は、JavaScript では別物です。このフラグはそれを型でも区別します。

interface Settings {
  theme?: "dark" | "light"; // 「省略可能」であって「undefined を代入してよい」ではない
}

const s: Settings = {};
s.theme = undefined; // フラグ ON ならエラー:undefined は許可されていない
// 「キーを消す」意図なら delete s.theme; が正しい

{ theme: undefined } を JSON にシリアライズするとキーが消える、といった微妙な挙動差がバグの温床になります。意味を型で固定するのが正解です。

判断軸:どこまで厳しくするか。 私は新規リポジトリでは上記フルセットを最初から入れます。後から有効化するほど修正コストが指数的に増えるためです。既存の大規模コードに後付けするなら、noUncheckedIndexedAccess だけ別ブランチで段階導入し、ファイル単位で潰すのが現実的です。


2. 言語:型の3大抜け穴 any / as / enum を断つ

tsconfig で土台を固めたら、次はコードに開いた穴を塞ぐ番です。型安全を破壊する3つの抜け穴を、優先度順に断ちます。

2-1. any 禁止、unknown + ナローイングで受ける

any は「型チェックを無効化する」という意味です。一度通ると伝播し、触れたものすべての安全性を奪います。代わりに unknown(何でも入るが、絞らないと何もできない)を使います。

anyunknown具体型
代入を受ける✅ 何でも✅ 何でも❌ 型が合うものだけ
そのまま使う✅(チェックなし=危険❌ ナローイング必須✅ 安全
型エラーの伝播❌ 周囲に伝染する✅ 局所に閉じる✅ なし
使いどころ原則なし外部入力の受け口通常すべて
// ❌ any:その先は無検査。プロパティ名のタイポも素通り
function handle(payload: any) {
  return payload.usrId; // typo でも通る → 実行時に undefined
}

// ✅ unknown + ナローイング:使う前に必ず絞る
function handle(payload: unknown) {
  if (
    typeof payload === "object" &&
    payload !== null &&
    "userId" in payload &&
    typeof payload.userId === "string"
  ) {
    return payload.userId; // ここで初めて string として安全に使える
  }
  throw new Error("invalid payload");
}

とはいえ手書きのナローイングは冗長です。外部入力は Zod で一発で絞るのが実務解で、§3で扱います。

2-2. as キャストは「コンパイラへの嘘」

型アサーション as T は、実行時には何もしません。型チェックを黙らせるだけです。fetch の結果に as User を付けても、サーバーが違う形を返せば防げません。

// ❌ 最悪パターン:検証なしのキャスト。実体が違えば即クラッシュ
const res = await fetch("/api/user");
const user = (await res.json()) as User; // JSON.parse は any 相当。as で嘘の型を被せている

// ✅ parse する(§3)。実体と型が一致することを実行時に保証
const user = UserSchema.parse(await res.json());

as が許容できるのは、人間がコンパイラより多くを知っているごく狭い場合だけ(例:DOM の getElementById の戻りを特定要素型へ)です。それでも as const(リテラルを widening させない)とは役割が別物である点に注意してください。

// as const は「キャスト」ではなく「これ以上広げるな」の指示。これは推奨
const ROLES = ["admin", "member", "guest"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "member" | "guest"

2-3. enum を避け、as const ユニオン/オブジェクトを使う

TypeScript 公式の enum ハンドブック自身が、enum より**as const オブジェクト**を推奨しています。理由は明確です。

  • 数値 enum は値が不透明:ログに 2 とだけ出ても意味が読めない。
  • const enum はインライン展開される:依存のバージョン差で値がズレると、if の分岐を取り違えるバグになる。isolatedModules とも非互換。
  • JavaScript に存在しない構文enum は TS 独自で、生成される実行時オブジェクトも直感に反する。

公式が示す代替が、これです。

// ✅ as const オブジェクト + 派生ユニオン型(公式推奨)
const OrderStatus = {
  Pending: "pending",
  Paid: "paid",
  Shipped: "shipped",
  Cancelled: "cancelled",
} as const;

type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];
// "pending" | "paid" | "shipped" | "cancelled"

function advance(status: OrderStatus) { /* ... */ }
advance(OrderStatus.Paid); // 値で参照しても良い
advance("paid");           // リテラルでも通る(enum より柔軟)
enumas const ユニオン/オブジェクト
実行時の値不透明(数値)になりがちそのまま文字列で読める
JS との整合❌ TS独自構文✅ ただのオブジェクト
isolatedModules⚠️ const enum は非互換✅ 問題なし
Zod 連携噛み合わせにくいz.enum と一致(§3)
バンドル余分なヘルパ生成最小

結論:新規コードで enum を書く理由はほぼありません。


3. 境界:Parse, don't validate —— Zod を信頼境界に置く

ここが規律の心臓部です。原則は "Parse, don't validate"。「データが正しいか確認する(validate)」のではなく、「正しい型へ変換する(parse)」。parse を通った後の値は、型システムが保証する安全な領域に入ります。

外部からシステムに入るものはすべて信頼できない——API レスポンス、フォーム入力、環境変数、localStorageJSON.parse の戻り。これらの境界に Zod(現行は Zod 4 stable)を置きます。

3-1. スキーマが唯一の真実源(SSoT)—— z.infer で型を導出する

最大の利点は DRY:型を手書きせず、スキーマから z.infer で導出します。スキーマを変えれば型も自動で追従し、乖離が原理的に起きません

import { z } from "zod";

// スキーマ(実行時の検証ロジック)を一度だけ定義
const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  role: z.enum(["admin", "member", "guest"]), // §2-3 のユニオンと一致
  createdAt: z.coerce.date(),
});

// 型はスキーマから導出する(手書きしない=SSoT)
type User = z.infer<typeof UserSchema>;
// { id: string; email: string; role: "admin"|"member"|"guest"; createdAt: Date }

入力(変換前)と出力(変換後)で型が違う場合は z.input / z.output で取り分けられます(上の z.coerce.date() は入力 string、出力 Date)。

3-2. safeParse で失敗を「値」として扱う

parse は失敗時に例外を投げます。境界では失敗が想定内なので、例外ではなく結果オブジェクトで扱う safeParse を使い、型付きで分岐します。

// API ルートでのリクエスト検証(信頼境界)
export async function POST(req: Request) {
  const json: unknown = await req.json(); // JSON.parse 相当は unknown 扱いが正しい
  const result = UserSchema.safeParse(json);

  if (!result.success) {
    // result.error は ZodError。型付きで安全にハンドリングできる
    return Response.json({ errors: result.error.flatten() }, { status: 400 });
  }

  // result.data は User 型として保証されている。ここから先は安全地帯
  const user = result.data;
  return Response.json({ id: user.id });
}

セキュリティ的にも本質的です。OWASP の入力検証は「境界で全部弾く」が原則。Zod スキーマがそのまま入力検証の仕様書になります。

3-3. 環境変数も parse する

process.env.Xstring | undefined。直接使うと「本番で環境変数が一個抜けていて静かに壊れる」典型事故になります。起動時に一括 parseして落とします(fail fast)。

// env.ts —— アプリ起動時に一度だけ検証する
const EnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  NODE_ENV: z.enum(["development", "production", "test"]),
});

// 不正なら起動時に例外で即死させる(実行時の謎挙動より100倍デバッグしやすい)
export const env = EnvSchema.parse(process.env);
// 以降 env.DATABASE_URL は string として保証される

4. モデル:判別可能なユニオンで「不正な状態を表現不能」にする

型安全の真価は、検証以上にモデリングにあります。標語は "Make illegal states unrepresentable"——不正な組み合わせを、そもそも型として作れなくする。

ありがちなのは「全部 optional の神オブジェクト」です。

// ❌ 不正な状態が表現できてしまう
interface Fetch {
  loading: boolean;
  data?: User;
  error?: string;
}
// loading:true なのに data がある、error と data が同時にある…全部作れてしまう

判別可能なユニオン(discriminated union)で、ありえる状態だけを列挙します。

// ✅ 取りうる状態だけが型として存在する
type FetchState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: User }   // success の時だけ data がある
  | { status: "error"; message: string }; // error の時だけ message がある

function render(state: FetchState) {
  switch (state.status) {
    case "success":
      return state.data.email;   // data は success 分岐でのみアクセス可
    case "error":
      return state.message;      // message も同様
    // idle / loading は data を持たない → 触ろうとすると型エラー
  }
}

status という共通リテラル(判別子)で、TypeScript が各分岐の型を自動で絞り込みます。「data があるのに loading」のような矛盾状態は、書こうとしても型が許しません。 これが ETC(Easy To Change)にも効きます——状態が増えても、影響箇所はコンパイラが教えてくれる(次章)。


5. 網羅:NeverError で「分岐漏れ」をコンパイルエラーにする

判別可能なユニオンの真価は、網羅性チェックと組み合わせた時に出ます。never 型は「ありえない値」を表し、全ケースを処理し切ると残りが never になるという性質を利用します(公式の exhaustiveness パターン)。

これを再利用可能なカスタムエラーにしたのが NeverError です。

// never-error.ts —— 「ここには到達しないはず」を型と実行時の両方で守る
export class NeverError extends Error {
  constructor(value: never) {
    super(`Unreachable: unexpected value ${JSON.stringify(value)}`);
    this.name = "NeverError";
  }
}

使い方は、switchdefault で受けるだけです。

function label(state: FetchState): string {
  switch (state.status) {
    case "idle":    return "待機中";
    case "loading": return "読み込み中";
    case "success": return "完了";
    case "error":   return "エラー";
    default:
      // 全ケースを処理していれば state は never 型 → コンパイル通過
      throw new NeverError(state);
  }
}

ここに { status: "cancelled" } をユニオンへ追加すると、defaultstatenever に収束しなくなり、NeverError(state) がコンパイルエラーになります。

Argument of type '{ status: "cancelled" }' is not assignable to parameter of type 'never'.

これが効く理由は ETC(変更容易性)と信頼性の両立です。状態を1つ足すと、その状態を扱い忘れている全箇所がビルドで赤くなる。テストを書く前に、コンパイラが「ここも直せ」と全部指してくれる。enum + 文字列比較ではこの保証は得られません(§2-3で enum を避ける実利のひとつ)。私のサブスク基盤では、課金状態・コミッション台帳・認証状態など、ドメインの状態機械すべてにこの NeverError パターンを適用しています。

default: return assertNever(x) 関数版との違い:例外クラスにしておくと、スタックトレースに「どの不正値で落ちたか」が残り、本番での原因切り分けが速い。関数版でも網羅性は守れますが、私は観測性のため例外クラスを採ります。


6. satisfies:型チェックしつつ「絞り込みを失わない」

§2 で「as は嘘」と書きました。では「この値が型 T を満たすことは確認したいが、リテラルの精度は落としたくない」時はどうするか。TypeScript 5.0 の satisfies 演算子がその答えです。

type RouteConfig = Record<string, { path: string; auth: boolean }>;

// ❌ 注釈(: RouteConfig)—— 型は RouteConfig に「広がる」
const routesA: RouteConfig = {
  home: { path: "/", auth: false },
  dashboard: { path: "/app", auth: true },
};
routesA.home;     // OK だが…
routesA.unknown;  // ⚠️ Record なのでタイポも string キーとして通ってしまう

// ✅ satisfies —— RouteConfig に適合することを検証しつつ、具体的なキーは保持
const routesB = {
  home: { path: "/", auth: false },
  dashboard: { path: "/app", auth: true },
} satisfies RouteConfig;
routesB.home;     // OK
routesB.unknown;  // ✅ コンパイルエラー:存在しないキー

satisfies は「制約は課すが、推論された具体型は捨てない」。as(実行時に無力な嘘)とも、型注釈(widening する)とも違う、第三の道です。設定オブジェクト・マッピング・定数テーブルで多用します。関連して 5.0 の const 型パラメータ(<const T>as const 相当の推論を関数引数に効かせる)も、同じ「精度を落とさない」系の道具です。


7. branded 型:IDと金額の「取り違え」を型で止める

stringuserIdstringorderId は、TypeScript の構造的型付けでは区別されません。引数の順序を間違えても通ってしまう。branded(nominal)型で、構造が同じでも別物として扱わせます。

// 公称型を作る(実行時コストゼロ。型レベルのタグだけ)
type Brand<T, B extends string> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

declare function cancelOrder(user: UserId, order: OrderId): void;

const u = "u_123" as UserId;
const o = "o_456" as OrderId;

cancelOrder(u, o); // ✅
cancelOrder(o, u); // ❌ コンパイルエラー:引数の取り違えを型が検出

Zod なら境界の parse と同時に brand を付けられます(.brand())。「検証済みの値」であること自体を型に焼き込めるのが強力です。

const UserId = z.string().uuid().brand<"UserId">();
type UserId = z.infer<typeof UserId>; // string & z.$brand<"UserId">

// parse を通った値だけが UserId 型を名乗れる=「未検証の文字列」と区別される
const id = UserId.parse("550e8400-e29b-41d4-a716-446655440000");

金額も同じ発想で守ります。cents(整数・最小単位)と yen(表示)を別の brand にすれば、100倍ズレた金額をうっかり渡す事故が型で止まります。決済ドメインでこれをどう徹底したかは サブスク決済の冪等性と型安全 に詳述しました。


8. CI:規律を「人間の善意」に頼らない

ここまでの規律は、強制しなければ必ず腐ります。レビューの見落とし一回で any が入り、次第に伝播する。チーム規模で生き残らせる唯一の方法は、機械に守らせることです。

8-1. ESLint で抜け穴を構文レベルで禁止

typescript-eslint の型情報つきルールを error に設定します。

// eslint 設定(抜粋)
{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",        // any を書けなくする
    "@typescript-eslint/no-unsafe-assignment": "error",   // any 由来の代入を禁止
    "@typescript-eslint/no-unsafe-member-access": "error", // any のプロパティ参照を禁止
    "@typescript-eslint/no-unsafe-call": "error",
    "@typescript-eslint/no-unsafe-return": "error",
    "@typescript-eslint/consistent-type-assertions": [     // as キャストを抑制
      "error", { "assertionStyle": "never" }
    ]
  }
}

no-unsafe-* 系が重要で、any を直接書かなくても、JSON.parse や型なしライブラリ経由で any が紛れ込んだ瞬間に検出します。これが「any 禁止」を実効化します。

8-2. tsc と型カバレッジをゲートにする

CI のジョブとして、型チェックと型カバレッジ計測をマージの必須条件にします。

# 1. 型エラーゼロを保証(emit せず型検査だけ)
tsc --noEmit

# 2. lint(上の no-explicit-any 等)
eslint . --max-warnings 0

# 3. 型カバレッジ:明示的な型がついている割合を計測し、しきい値で落とす
type-coverage --strict --at-least 99 --ignore-files "**/*.test.ts"

type-coverage --strict は、コード中で any(に潰れている箇所)の割合を可視化します。しきい値(例:99%)を下回ったら CI が落ちる。「いつの間にか型が緩んでいた」を数値で止める仕組みです。私のモノレポでは、これらを Turborepo のタスクとして全パッケージ横断で回し、PRごとに強制しています。


9. よくある落とし穴

現場で繰り返し見る、型安全を自分で無効化してしまうパターンです。

9-1. エラーを黙らせるための as

// ❌ 「とりあえず通す」ための as。バグを未来に先送りしているだけ
const config = loadConfig() as AppConfig;

as でエラーが消えても、実体は変わっていません。コンパイラの警告は「実装が間違っている」サインです。消すべきは警告ではなく原因。境界なら §3 の parse に置き換えます。

9-2. 手書きの型ガードが型と乖離する

// ❌ 型と独立した手書きガード。User にフィールドを足してもガードは更新されない
function isUser(x: unknown): x is User {
  return typeof x === "object" && x !== null && "id" in x; // email を見ていない!
}

is User という述語は人間の主張であって、コンパイラは中身が正しいか検証しません。Useremail を追加してもこのガードは黙って通り、型と実体が静かに乖離します。Zod スキーマから z.infer すれば、スキーマ=ガード=型が常に一致します(§3)。これが「手書きガードを書かない」理由です。

9-3. JSON.parse の戻りを型として信じる

// ❌ JSON.parse は any 相当。as で被せた型は何の保証もない
const data = JSON.parse(raw) as ApiResponse;

// ✅ unknown で受けて parse する
const data = ApiResponseSchema.parse(JSON.parse(raw));

JSON.parse の戻りは実質 any。外から来た文字列の中身を、コンパイラは知りようがありません。必ず parse を挟む

9-4. fetch().json() を型付きと思い込む

§2-2 で触れた通り、res.json() の戻りは Promise<any>as User は嘘。ネットワーク越しの値こそ最も信頼できない入力です。Zod の safeParse で受けるのが唯一の正解です。

9-5. enum 比較で網羅性を取りこぼす

enum + if/else の連鎖は、ケースを足してもコンパイラが漏れを指摘しません。§4 の判別可能ユニオン + §5 の NeverError に置き換えれば、漏れがビルドで落ちます。


10. 横断的に効く理由(なぜこの規律が「価値」になるのか)

最後に、この規律が単なる潔癖ではなく事業価値である理由を、設計原則で整理します。

  • セキュリティ:外部入力を境界で全部 parse する(§3)= OWASP 入力検証の実装。型が仕様書を兼ねる。
  • 保守性 / ETC:型が変更の影響範囲を局所化する。状態を足すと NeverError が全漏れ箇所を指す(§4・§5)。リファクタが「祈り」でなく「機械的作業」になる。
  • 信頼性:不正な状態を表現不能にする(§4)= そもそもバグの種が型として存在できない。
  • DRY:スキーマが型・検証・ドキュメントの唯一の真実源(§3)。乖離が起きない。
  • テスト容易性:parse 関数も純粋関数も入出力が型で固定され、テストが書きやすい。型で潰せる領域はテストを書かなくて済む(テストすべきはロジックの分岐だけ)。

型安全は「バグを減らす」だけの話ではありません。「この設計なら任せられる」とエンタープライズが判断する根拠そのものです。レビューで毎回「ここ any ですよ」と指摘し合う消耗から解放され、人間はドメインの本質的な議論に時間を使えるようになります。


まとめ:型安全は「規律」であって「機能」ではない

要点を最後に6行で。

  1. tsconfig を最大出力にstrict に加え noUncheckedIndexedAccessexactOptionalPropertyTypes まで(§1)。
  2. 3大抜け穴を断つanyunknownas → parse、enumas const ユニオン(§2)。
  3. 境界で parse する:Zod を信頼境界に置き、z.infer で型を導出(SSoT)、safeParse で失敗を値として扱う(§3)。
  4. 不正な状態を表現不能に:判別可能ユニオン + NeverError で網羅性をコンパイルエラー化(§4・§5)。
  5. 精度と公称性satisfies で widening を防ぎ、branded 型で ID・金額の取り違えを止める(§6・§7)。
  6. CI で強制no-explicit-anyno-unsafe-*tsc --noEmit・型カバレッジをマージ条件に(§8)。

「一人 × 生成AI(Claude Code)で、速く・安く・安全に」作る——その「安全」を担保する背骨が、この型安全規律です。本記事のコードと判断軸は、複数名チームで運用する サブスク学習プラットフォーム で実際に敷いているものです。言語を跨ぐ型安全は Next.js × Go × OpenAPI を、決済ドメインへの適用は サブスク決済の冪等性と型安全 を併せてどうぞ。

型安全な設計・既存コードベースの型負債の解消・CIでの品質ゲート構築のご相談は、お問い合わせからお気軽にどうぞ。

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

金融リテラシー教育のサブスク学習プラットフォーム(マルチチャネル課金・冪等な決済・代理店コミッションをNext.js 16モノレポで構築)

ケーススタディを見る