Jika Anda seorang Laravel developer yang terbiasa dengan PHP yang strongly typed, beralih ke JavaScript murni seringkali terasa seperti mundur: tidak ada type hints, tidak ada IDE autocomplete yang reliable, dan bug yang seharusnya terdeteksi saat coding baru ketahuan saat runtime. TypeScript hadir untuk menyelesaikan frustrasi ini — membawa type system ke ekosistem JavaScript tanpa mengorbankan fleksibilitasnya.

Mengapa TypeScript Terasa Natural untuk PHP Developer

Jika Anda sudah nyaman dengan PHP 8.x, sebagian besar konsep TypeScript sudah familiar:

PHPTypeScript
string $namename: string
?string $namename: string | null
array $itemsitems: string[] atau Array<string>
interface Renderableinterface Renderable
readonly string $idreadonly id: string

Perbedaan utama: TypeScript memiliki tipe any sebagai escape hatch (hindari sebisa mungkin), dan type inference yang sangat canggih sehingga Anda tidak harus annotate semuanya secara manual.

Setup TypeScript di Project Laravel + Vite

npm install -D typescript @types/node

# Buat tsconfig.json
npx tsc --init
// tsconfig.json (konfigurasi untuk Laravel + Vite)
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "jsx": "preserve",
    "baseUrl": ".",
    "paths": {
      "@/*": ["resources/js/*"]
    },
    "types": ["vite/client", "node"]
  },
  "include": ["resources/js/**/*.ts", "resources/js/**/*.vue"]
}
// vite.config.js — rename ke vite.config.ts dan update
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({ input: ['resources/js/app.ts'], refresh: true }),
        vue(),
    ],
});

Dasar-dasar yang Perlu Dikuasai

Types dan Interfaces

// Type alias — cocok untuk union types dan simple types
type Status = 'draft' | 'published' | 'archived';
type ID = number | string;

// Interface — cocok untuk shape objek, bisa di-extend
interface Post {
    id: number;
    title: string;
    slug: string;
    status: Status;
    author: User;
    published_at: string | null;
    categories: Category[];
}

interface User {
    id: number;
    name: string;
    email: string;
    avatar_url: string | null;
}

// Extend interface
interface AdminPost extends Post {
    view_count: number;
    edit_url: string;
}

Generic Types

Generic memungkinkan komponen yang reusable dengan type safety:

// Respons API yang konsisten
interface ApiResponse<T> {
    data: T;
    message: string;
    success: boolean;
}

interface PaginatedResponse<T> {
    data: T[];
    current_page: number;
    last_page: number;
    total: number;
    per_page: number;
}

// Penggunaan
async function fetchPosts(): Promise<PaginatedResponse<Post>> {
    const res = await fetch('/api/posts');
    return res.json();
}

const response: ApiResponse<User> = await loginUser(credentials);

Utility Types yang Sering Dipakai

// Partial — semua field menjadi optional (cocok untuk update form)
type UpdatePostDto = Partial<Post>;

// Pick — ambil hanya field tertentu
type PostCard = Pick<Post, 'id' | 'title' | 'slug' | 'excerpt'>;

// Omit — hilangkan field tertentu
type CreatePostDto = Omit<Post, 'id' | 'author' | 'published_at'>;

// Required — semua field menjadi required
type PublishedPost = Required<Post>;

// Record — untuk objek dengan key dinamis
type PostsBySlug = Record<string, Post>;

TypeScript dengan Inertia.js (Laravel + Vue)

// resources/js/types/index.d.ts
export interface PageProps {
    auth: {
        user: User | null;
    };
    flash: {
        success?: string;
        error?: string;
    };
}

// Di komponen Vue
import { usePage } from '@inertiajs/vue3';
import type { PageProps } from '@/types';

const page = usePage<PageProps>();
const user = computed(() => page.props.auth.user);
// user sekarang fully typed!

Tips Migrasi Bertahap

Tidak perlu konversi semua file sekaligus. TypeScript bisa hidup berdampingan dengan JavaScript:

  1. Rename file satu per satu dari .js ke .ts
  2. Mulai dengan "strict": false, aktifkan secara bertahap
  3. Buat file types/index.d.ts untuk type definisi global
  4. Gunakan // @ts-ignore sementara untuk error yang belum sempat diperbaiki
  5. Perbaiki error satu per satu, jangan semua sekaligus

Investasi belajar TypeScript biasanya terbayar dalam 2-3 minggu: setelah itu, Anda akan jarang menghabiskan waktu debugging error yang seharusnya bisa terdeteksi IDE, dan autocomplete yang akurat membuat coding lebih cepat secara keseluruhan.