Claude Code dan TypeScript: tips praktis agar cepat sekaligus aman
Gunakan strict, Zod, Union, generic, satisfies, dan type test untuk meningkatkan TypeScript dengan Claude Code.
Claude Code bisa mempercepat pengembangan TypeScript, terutama untuk form, helper API, dan test. Namun jika batas tipe tidak jelas, kode rapuh juga bisa muncul dengan cepat. Untuk pemula, kebiasaan paling aman adalah menetapkan aturan tipe sebelum meminta fitur dibuat.
Di artikel ini, strict berarti TypeScript tidak mudah meloloskan kode mencurigakan.
Tipe domain adalah aturan bisnis yang ditulis sebagai tipe.
Discriminated Union memisahkan bentuk data berdasarkan status, dan validasi runtime mengecek data saat program berjalan.
Beri Peta Tipe Terlebih Dahulu
Sebelum prompt panjang, siapkan peta kecil: aturan compiler, tipe domain, input eksternal, state, dan type test. Peta ini membuat diff dari Claude Code lebih mudah ditinjau.
flowchart TD
A["Kebutuhan"] --> B["tsconfig: aturan strict"]
B --> C["Tipe domain: Plan dan Account"]
C --> D["Data eksternal: unknown lalu validasi"]
D --> E["State: discriminated union"]
E --> F["Type test: expectTypeOf / tsd"]
F --> G["Implementasi dan review dengan Claude Code"]
Gunakan dokumentasi resmi sebagai dasar: strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, Narrowing, Generics, Utility Types, dan catatan satisfies.
Untuk validasi runtime, lihat juga dokumentasi Zod.
Bacaan terkait: TypeScript Utility Types, TypeScript Generics, dan Zod Validation.
Mulai dari tsconfig strict
Jangan hanya meminta “buat dengan TypeScript”. Beri Claude Code kontrak compiler terlebih dahulu.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"]
}
Masukkan juga batasan di prompt.
Repository ini memakai strict TypeScript.
Jangan menambahkan any. Input eksternal diterima sebagai unknown dan divalidasi dengan Zod.
Saat menangani Union di switch, tambahkan never exhaustiveness check.
Setelah implementasi, jalankan npx tsc --noEmit.
noUncheckedIndexedAccess membuat kemungkinan undefined tetap terlihat saat membaca array atau object.
Ini membantu menangkap field API yang hilang, list kosong, dan terjemahan CMS yang belum lengkap.
Use Case 1: Modelkan Plan SaaS sebagai Tipe Domain
Tipe domain adalah aturan bisnis dalam TypeScript. Plan, permission, billing state, dan publish state sebaiknya ada sebelum UI.
export type Plan = "free" | "pro" | "enterprise";
export type Account = {
id: string;
email: string;
plan: Plan;
seats: number;
trialEndsAt: string | null;
};
export type CreateAccountInput = {
email: string;
plan: Exclude<Plan, "enterprise">;
seats?: number;
};
export type UpdateAccountInput = Partial<
Pick<Account, "email" | "plan" | "seats" | "trialEndsAt">
>;
Exclude menghapus anggota dari Union.
Partial membuat property menjadi opsional. Cocok untuk update API, tetapi berbahaya untuk create input jika field wajib ikut menjadi opsional.
Use Case 2: Validasi Data API dari unknown
Tipe TypeScript hilang saat runtime.
API, form, cookie, localStorage, CSV, dan output AI bisa rusak.
Terima sebagai unknown, validasi, lalu gunakan nilai yang sudah bertipe.
npm install zod
import { z } from "zod";
const AccountSchema = z.object({
id: z.string().min(1),
email: z.string().email(),
plan: z.enum(["free", "pro", "enterprise"]),
seats: z.number().int().positive(),
trialEndsAt: z.string().datetime().nullable()
});
type Account = z.infer<typeof AccountSchema>;
export function parseAccountResponse(json: unknown): Account {
return AccountSchema.parse(json);
}
unknown berarti nilainya belum terbukti.
Berbeda dari any, ia memaksa validasi sebelum property dibaca.
Use Case 3: Tutup State Payment dengan Union
Payment, upload, submit form, dan background job adalah state machine.
status: string terlalu longgar.
type PaymentResult =
| { status: "pending"; invoiceId: string }
| { status: "paid"; invoiceId: string; paidAt: string }
| { status: "failed"; invoiceId: string; reason: string };
export function renderPaymentMessage(result: PaymentResult): string {
switch (result.status) {
case "pending":
return `Invoice ${result.invoiceId} is waiting for payment.`;
case "paid":
return `Invoice ${result.invoiceId} was paid at ${result.paidAt}.`;
case "failed":
return `Invoice ${result.invoiceId} failed: ${result.reason}.`;
default: {
const exhaustive: never = result;
return exhaustive;
}
}
}
Cabang never membuat TypeScript memeriksa semua kasus valid.
Jika status refunded ditambahkan nanti, compiler akan meminta cabang baru.
Use Case 4: Generic dan satisfies
Generic membuat helper bisa dipakai ulang tanpa kehilangan tipe spesifik di tempat pemanggilan.
export function groupBy<T, K extends PropertyKey>(
items: readonly T[],
getKey: (item: T) => K
): Partial<Record<K, T[]>> {
const grouped: Partial<Record<K, T[]>> = {};
for (const item of items) {
const key = getKey(item);
const bucket = grouped[key] ?? [];
bucket.push(item);
grouped[key] = bucket;
}
return grouped;
}
const accounts = [
{ id: "a1", plan: "free" },
{ id: "a2", plan: "pro" },
{ id: "a3", plan: "pro" }
] as const;
const byPlan = groupBy(accounts, (account) => account.plan);
const proAccounts = byPlan.pro ?? [];
console.log(proAccounts.map((account) => account.id));
Untuk object konfigurasi, satisfies biasanya lebih baik daripada assertion yang terlalu luas.
type ApiRoute = {
method: "GET" | "POST" | "PATCH" | "DELETE";
path: `/${string}`;
auth: boolean;
};
const routes = {
listAccounts: { method: "GET", path: "/accounts", auth: true },
createAccount: { method: "POST", path: "/accounts", auth: true },
healthCheck: { method: "GET", path: "/health", auth: false }
} as const satisfies Record<string, ApiRoute>;
type RouteName = keyof typeof routes;
export function getRoute(name: RouteName) {
return routes[name];
}
Tambahkan Type-Level Tests
Tipe publik yang penting perlu diuji.
npm install -D vitest tsd
import { expectTypeOf, test } from "vitest";
type CreateAccountInput = {
email: string;
plan: "free" | "pro";
seats?: number;
};
test("CreateAccountInput keeps the public API narrow", () => {
expectTypeOf<CreateAccountInput>().toMatchTypeOf<{
email: string;
plan: "free" | "pro";
seats?: number;
}>();
});
Use Case Nyata dan Pitfall Umum
| Use case | Yang dikunci type system | Yang bisa dibuat Claude Code |
|---|---|---|
| Billing SaaS | plan, invoice state, permission | cabang UI, form, pesan |
| Layar admin API | Zod schema, response type | fetch helper, tabel, loading state |
| CMS artikel | slug, locale, publish state, hero image | draft MDX, halaman list, validasi |
| Contact form | input schema, result Union | UI, submit handler, Vitest |
| Jebakan | Dampak | Solusi |
|---|---|---|
Response API sebagai any | JSON rusak tetap compile | unknown dan Zod |
status: string | state mustahil bisa masuk | discriminated Union |
Banyak as User | error disembunyikan | schema, guard, satisfies |
Partial<T> untuk create | field wajib menjadi opsional | pisahkan create dan update |
| Tidak ada type test | tipe publik melebar diam-diam | expectTypeOf atau tsd |
Di workflow artikel ClaudeCodeLab, Masa pernah melihat URL locale salah karena lang bertipe string.
Setelah locale dibuat sebagai Union tertutup, saran perubahan dari Claude Code menjadi jauh lebih stabil.
Aturan dan CTA
## TypeScript rules
- Use strict TypeScript.
- Do not introduce `any`. Use `unknown` at external boundaries.
- Prefer discriminated unions for states.
- Prefer `satisfies` over broad type assertions.
- Derive API types from Zod schemas when runtime data is involved.
- Add Vitest or tsd style type checks for exported helper types.
- Run `npx tsc --noEmit` before reporting completion.
Untuk proyek solo, katalog produk berisi template dan checklist workflow Claude Code.
Untuk tim, training dan konsultasi Claude Code dapat membantu migrasi strict, batas Zod, type tests, dan aturan CLAUDE.md di repository nyata.
Hasil Uji
Saya mencoba alur ini di proyek TypeScript kecil: response API diganti dari any menjadi unknown plus Zod, lalu Claude Code diminta menambahkan cabang Union dan test expectTypeOf.
Hasil yang berguna adalah state yang belum ditangani dan akses property yang tidak ada ditemukan sebelum review.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission safety ladder Claude Code: perluas akses tanpa kehilangan kontrol
Naik dari read-only ke edit terbatas, command bukti, dan cek deploy dengan kontrol yang jelas.
Claude Code Small PR Proof Pack: perubahan kecil yang mudah direview
Paket bukti untuk PR Claude Code: diff, check, URL publik, jalur CTA, dan rollback.
Review gate Claude Code sebelum commit: diff, test, URL publik, dan CTA
Cara memakai Claude Code sebelum commit: diff scope, build, URL publik, link Gumroad, CTA konsultasi, missing test, dan file tidak terkait.