Mengapa TypeScript Penting untuk Proyek Besar
Kalau kamu sudah lama berkecimpung di dunia pengembangan frontend, pasti pernah merasakan frustasi saat debugging kesalahan yang aneh-aneh atau saat memelihara kode yang sudah berbulan-bulan tidak disentuh. Nah, di sinilah TypeScript menjadi pahlawan yang bisa menolong kita para pengembang.
TypeScript itu pada dasarnya JavaScript yang diberi kekuatan super berupa static typing. Maksudnya apa? Sederhana saja, TypeScript memaksa kita untuk jelas tentang tipe data yang kita gunakan, sehingga banyak error yang biasanya muncul saat runtime bisa tertangkap dari awal waktu development.
Untuk proyek besar, ini benar-benar mengubah permainan. Bayangkan saja kamu bekerja bersama 10 developer lain dalam satu codebase yang memiliki ribuan baris kode. Tanpa TypeScript, setiap kali ada yang menambahkan fitur baru atau mengubah function yang sudah ada, risiko merusak hal lain itu sangat besar.
Di buildwithangga.com misalnya, mereka pasti memiliki kompleksitas yang tinggi dalam mengelola data pengguna, konten kursus, pelacakan kemajuan, dan sistem pembayaran. Dengan TypeScript, setiap developer bisa yakin kalau data yang mereka kirim dan terima itu sesuai dengan ekspektasi.
TypeScript juga membuat proses refactoring menjadi lebih aman. Kalau misalnya kita ingin mengubah struktur dari object pengguna, TypeScript akan langsung memberitahu di mana saja bagian kode yang perlu diperbarui. Tidak perlu khawatir ada yang tertinggalan atau kesalahan ketik yang bisa membuat aplikasi error.
Selain itu, TypeScript memiliki intellisense yang jauh lebih baik dibanding JavaScript biasa. IDE seperti VS Code akan memberikan suggestion yang lebih akurat, auto-completion yang lebih membantu, dan error detection yang real-time. Ini membuat proses coding menjadi lebih cepat dan mengurangi bug yang tidak perlu.
Yang paling penting, TypeScript itu self-documenting. Dengan type annotations yang jelas, developer baru yang bergabung dengan tim bisa lebih cepat memahami codebase tanpa perlu membaca dokumentasi yang mungkin sudah outdated. Type definitions itu pada dasarnya dokumentasi yang selalu up-to-date karena kalau tidak sesuai, kode akan error.
Instalasi dan Konfigurasi TypeScript
Oke, sekarang kita masuk ke bagian praktis. Sebelum bisa main-main dengan TypeScript, tentu aja kita harus menginstall dulu. Prosesnya sebenarnya gak ribet, tapi ada beberapa hal yang perlu diperhatikan supaya setup kita optimal untuk development.
Pertama-tama, pastikan kamu udah punya Node.js yang terinstall di komputer. Kalau belum, download dulu dari website resminya. Untuk mengecek apakah Node.js sudah terinstall, buka terminal atau command prompt dan ketik:
node --version
npm --version

Kalau kedua perintah itu menampilkan nomor versi, berarti kamu udah siap. Kalau masih error, berarti kamu perlu install Node.js dulu.
Setelah yakin Node.js udah terinstall, langkah pertama adalah membuat folder proyek baru untuk praktek kita:
mkdir bwa-ts
cd bwa-ts

npm init -y

Perintah ini akan membuat file package.json dengan konfigurasi default. File ini penting untuk mengelola dependencies proyek kita.
Sekarang kita langsung install TypeScript sebagai dependency lokal di proyek kita. Kenapa lokal? Karena dengan cara ini, setiap developer yang kerja di proyek yang sama akan menggunakan versi TypeScript yang sama. Jadi gak ada lagi cerita "eh kok di komputer gue jalan tapi di komputer lu error".
npm install --save-dev typescript @types/node ts-node

Kenapa begitu? Karena dengan cara ini, setiap developer yang kerja di proyek yang sama akan menggunakan versi TypeScript yang sama. Jadi gak ada lagi cerita "eh kok di komputer gue jalan tapi di komputer lu error".
Package @types/node itu memberikan type definitions untuk Node.js APIs, sementara ts-node memungkinkan kita menjalankan file TypeScript langsung tanpa perlu compile dulu ke JavaScript.
Setelah installasi selesai, langkah berikutnya adalah membuat struktur folder yang rapi. Bikin folder-folder berikut di dalam proyek kita:
mkdir src
mkdir src/components
mkdir src/utils
mkdir src/types
mkdir build
Struktur folder ini mirip dengan yang biasa dipake di proyek-proyek besar seperti buildwithangga. Folder src untuk kode TypeScript kita, components untuk komponen React (kalau pake React), utils untuk fungsi-fungsi helper, types untuk custom type definitions, dan build untuk hasil compile.
Langkah selanjutnya adalah membuat file konfigurasi TypeScript. File ini namanya tsconfig.json dan dia kayak panduan untuk TypeScript compiler tentang bagaimana cara mengcompile kode kita.
Untuk membuat file tsconfig.json, kita bisa jalankan perintah:
npx tsc --init

Perintah ini akan membuat file tsconfig.json dengan konfigurasi default yang sangat panjang dan banyak komentar. Tapi konfigurasi default itu kadang terlalu verbose dan gak sesuai dengan kebutuhan proyek frontend modern. Makanya, kita perlu customize sedikit.
Hapus semua isi file tsconfig.json dan ganti dengan konfigurasi yang lebih praktis ini:
Ini contoh konfigurasi tsconfig.json yang cocok untuk proyek frontend seperti yang mungkin digunakan di buildwithangga:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@utils/*": ["utils/*"],
"@types/*": ["types/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build", "dist"]
}
Konfigurasi di atas lumayan komplit untuk kebanyakan proyek frontend. Mari kita bahas beberapa bagian penting:
Setting "target" ke "ES2020" artinya kita ngasih tau TypeScript untuk mengcompile kode kita ke JavaScript versi ES2020. Ini cukup modern dan didukung sama sebagian besar browser yang masih aktif digunakan.
Setting "jsx" ke "react-jsx" itu penting kalau kamu menggunakan React di proyek kamu. Ini mengaktifkan dukungan untuk JSX syntax yang biasanya dipake di React components.
Bagian "paths" itu sangat berguna untuk membuat import statements yang lebih bersih. Daripada nulis import Button from '../../../components/Button', kita bisa nulis import Button from '@components/Button'.
Setting "strict" ke true itu penting banget. Ini mengaktifkan semua strict checking yang ada di TypeScript. Memang awalnya bisa bikin banyak error, tapi ini akan membantu kita menulis kode yang lebih robust.
Yang terakhir, jangan lupa untuk menambahkan script di package.json supaya kita bisa menjalankan TypeScript compiler dengan mudah:
{
"scripts": {
"type-check": "tsc --noEmit",
"type-check:watch": "tsc --noEmit --watch"
}
}
Dengan script ini, kita bisa jalankan npm run type-check untuk mengecek apakah ada type errors di kode kita tanpa menghasilkan file JavaScript. Ini berguna banget untuk CI/CD pipeline atau sebagai bagian dari development workflow kita.
Sekarang kita test apakah setup kita udah bener. Bikin file baru di folder src dengan nama index.ts:
// src/index.ts
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
const buildWithAnggaUser: User = {
id: 1,
name: "Angga Risky",
email: "[email protected]",
isActive: true
};
function getUserInfo(user: User): string {
return `${user.name} (${user.email}) - Status: ${user.isActive ? 'Aktif' : 'Tidak Aktif'}`;
}
console.log(getUserInfo(buildWithAnggaUser));
Untuk menjalankan file ini, kita bisa pake ts-node yang udah kita install tadi:
npx ts-node src/index.ts
Kalau berhasil, kamu akan melihat output: "Angga Risky ([email protected]) - Status: Aktif".

Kita juga bisa test TypeScript compiler dengan menjalankan:
npx tsc
Perintah ini akan mengcompile semua file TypeScript di folder src dan menghasilkan file JavaScript di folder yang ditentukan di tsconfig.json (dalam hal ini folder build).
Kalau mau lebih praktis lagi, kamu bisa menambahkan beberapa script lain di package.json:
{
"scripts": {
"build": "tsc",
"start": "node build/index.js",
"dev": "ts-node src/index.ts",
"type-check": "tsc --noEmit",
"type-check:watch": "tsc --noEmit --watch"
}
}
Dengan script-script ini, workflow development kita jadi lebih smooth:
npm run devuntuk menjalankan kode TypeScript langsungnpm run builduntuk compile ke JavaScriptnpm startuntuk menjalankan hasil compilenpm run type-checkuntuk mengecek types tanpa compilenpm run type-check:watchuntuk mengecek types secara real-time
Terakhir, jangan lupa bikin file .gitignore supaya folder node_modules dan build gak ke-commit ke repository:
node_modules/
build/
dist/
*.log
.env
.DS_Store
Sampai di sini, setup TypeScript kita udah komplet dan siap dipake untuk development serius!
Basic Types dan Type Annotations

Nah, sekarang kita masuk ke inti dari TypeScript: types! Kalau kamu udah terbiasa dengan JavaScript, konsep types mungkin masih asing. Tapi tenang aja, sebenernya types itu cuma cara kita kasih tau TypeScript tentang jenis data apa yang kita harapkan.
Di JavaScript biasa, kita bisa nulis variabel kayak gini:
let userName = "Angga";
let userAge = 25;
let isActive = true;
Nah, di TypeScript, kita bisa tambahin type annotations untuk memastikan variabel-variabel ini gak berubah tipe datanya secara gak sengaja:
let userName: string = "Angga";
let userAge: number = 25;
let isActive: boolean = true;
Sebenernya, untuk kasus kayak gini, TypeScript udah bisa menebak tipe datanya sendiri (ini disebut type inference). Jadi kita gak perlu nulis type annotations-nya. Tapi kadang ada kasus di mana kita perlu eksplisit tentang tipe data yang kita mau.
Tipe Data Primitif
TypeScript punya beberapa tipe data primitif yang wajib kamu tau:
String untuk teks:
let courseName: string = "Mastering React Native";
let instructorName: string = "BuildWithAngga Team";
Number untuk angka (integer atau float):
let coursePrice: number = 299000;
let rating: number = 4.8;
let studentCount: number = 1250;
Boolean untuk nilai benar/salah:
let isPublished: boolean = true;
let isFree: boolean = false;
Array untuk kumpulan data:
let categories: string[] = ["Frontend", "Mobile", "Backend"];
let prices: number[] = [199000, 299000, 399000];
// Atau bisa juga pake sintaks generic
let tags: Array<string> = ["React", "TypeScript", "JavaScript"];
Type Annotations untuk Objek
Untuk objek, kita bisa definisikan struktur properties-nya:
let course: {
id: number;
title: string;
instructor: string;
price: number;
isPublished: boolean;
} = {
id: 1,
title: "Complete TypeScript Course",
instructor: "Angga Risky",
price: 349000,
isPublished: true
};
Tapi cara di atas agak verbose. Makanya TypeScript punya konsep yang namanya interface (kita bahas nanti).
Union Types
Kadang kita butuh variabel yang bisa menerima lebih dari satu tipe data. Di sinilah union types berguna:
let courseId: string | number = "TS001";
courseId = 12345; // ini juga valid
let status: "draft" | "published" | "archived" = "draft";
Union types berguna banget untuk kasus kayak response API yang bisa sukses atau error:
let apiResponse: string | null = null;
apiResponse = "Data berhasil dimuat";
Any dan Unknown
TypeScript juga punya tipe any yang artinya "terima apa aja". Tapi hati-hati, pake any terlalu banyak bisa bikin kita kehilangan manfaat type checking:
let dynamicContent: any = "Ini string";
dynamicContent = 42; // boleh
dynamicContent = true; // juga boleh
Alternatif yang lebih aman adalah unknown:
let userInput: unknown = getUserInput();
// Harus type checking dulu sebelum dipake
if (typeof userInput === "string") {
console.log(userInput.toUpperCase());
}
Void dan Never
Untuk function yang gak return apa-apa, kita pake void:
function logCourseActivity(message: string): void {
console.log(`BuildWithAngga Activity: ${message}`);
}
Sedangkan never untuk function yang gak pernah return (misalnya yang selalu throw error):
function throwError(message: string): never {
throw new Error(message);
}
Optional Properties
Kadang kita punya property yang opsional (boleh ada boleh gak). Tinggal tambahin tanda tanya:
let studentProfile: {
name: string;
email: string;
avatar?: string; // opsional
phone?: string; // opsional
} = {
name: "John Doe",
email: "[email protected]"
// avatar dan phone boleh gak diisi
};
Type Assertions
Kadang kita tau lebih banyak tentang tipe data dibanding TypeScript. Di kasus ini, kita bisa pake type assertions:
let courseElement = document.getElementById("course-card") as HTMLDivElement;
// Atau pake sintaks angle bracket (tapi gak direkomendasikan di JSX)
let anotherElement = <HTMLDivElement>document.getElementById("another-card");
Literal Types
Kita juga bisa bikin tipe yang cuma menerima nilai spesifik:
let difficulty: "beginner" | "intermediate" | "advanced" = "beginner";
let platform: "web" | "mobile" | "desktop" = "web";
Literal types berguna banget untuk konstanta atau konfigurasi:
const buildWithAnggaConfig = {
apiUrl: "<https://api.buildwithangga.com>" as const,
version: "v2" as const,
environment: "production" as const
};
Dengan type annotations yang proper, kode kita jadi lebih dapat diprediksi dan tidak mudah error. IDE juga bakal kasih autocompletion yang lebih akurat, dan kita bisa menangkap error sebelum kode di-deploy ke production!
Interfaces dan Type Aliases

Setelah kita paham basic types, sekarang saatnya belajar tentang cara membuat tipe data custom yang lebih kompleks. Di sinilah interfaces dan type aliases berperan penting. Kedua fitur ini membantu kita mendefinisikan struktur data yang lebih rumit dan bisa digunakan berulang-ulang.
Bayangin kamu lagi bikin aplikasi pembelajaran online seperti buildwithangga. Pasti ada banyak objek dengan struktur yang mirip-mirip, kayak data kursus, data siswa, data instruktur, dan sebagainya. Daripada nulis tipe objek berulang-ulang, lebih baik kita bikin template yang bisa dipake berkali-kali.
Interface: Blueprint untuk Objek
Interface itu kayak blueprint atau cetak biru untuk objek. Dia mendefinisikan properti apa aja yang harus ada dan tipe datanya gimana. Ini contoh interface untuk data kursus di buildwithangga:
interface Course {
id: number;
title: string;
description: string;
instructor: string;
price: number;
duration: number; // dalam menit
level: "beginner" | "intermediate" | "advanced";
isPublished: boolean;
thumbnailUrl: string;
tags: string[];
}
Dengan interface di atas, kita bisa bikin objek kursus yang konsisten:
const reactCourse: Course = {
id: 1,
title: "Mastering React untuk Pemula",
description: "Belajar React dari nol sampai mahir",
instructor: "Angga Risky",
price: 299000,
duration: 1200,
level: "beginner",
isPublished: true,
thumbnailUrl: "<https://buildwithangga.com/thumbnails/react-course.jpg>",
tags: ["React", "JavaScript", "Frontend"]
};
Kalau kita lupa tambahin property atau salah tipe data, TypeScript langsung akan ngasih error. Ini membantu banget untuk mencegah bug yang gak ketahuan.
Optional Properties di Interface
Kadang ada property yang gak selalu perlu diisi. Kita bisa bikin property jadi optional dengan menambahkan tanda tanya:
interface Student {
id: number;
name: string;
email: string;
profilePicture?: string; // optional
bio?: string; // optional
dateOfBirth?: Date; // optional
enrolledCourses: number[]; // array course IDs
}
// Valid tanpa property optional
const newStudent: Student = {
id: 1,
name: "John Doe",
email: "[email protected]",
enrolledCourses: [1, 2, 3]
};
Readonly Properties
Kadang kita mau bikin property yang gak bisa diubah setelah objek dibuat. Gunakan keyword readonly:
interface ApiConfig {
readonly baseUrl: string;
readonly apiKey: string;
timeout: number; // bisa diubah
}
const buildWithAnggaConfig: ApiConfig = {
baseUrl: "<https://api.buildwithangga.com>",
apiKey: "secret-key-123",
timeout: 5000
};
// Error! Cannot assign to 'baseUrl' because it is readonly
// buildWithAnggaConfig.baseUrl = "<https://new-api.com>";
// Ini boleh karena timeout bukan readonly
buildWithAnggaConfig.timeout = 10000;
Extending Interfaces
Interface bisa "mewarisi" dari interface lain. Ini berguna banget untuk menghindari duplikasi kode:
interface BaseUser {
id: number;
name: string;
email: string;
createdAt: Date;
}
interface Student extends BaseUser {
enrolledCourses: number[];
totalWatchTime: number;
}
interface Instructor extends BaseUser {
bio: string;
expertise: string[];
coursesCreated: number[];
rating: number;
}
const instructor: Instructor = {
// Properties dari BaseUser
id: 1,
name: "Angga Risky",
email: "[email protected]",
createdAt: new Date(),
// Properties khusus Instructor
bio: "Frontend developer dengan 5+ tahun pengalaman",
expertise: ["React", "Vue", "TypeScript"],
coursesCreated: [1, 2, 3],
rating: 4.8
};
Type Aliases: Alternatif untuk Interface
Type aliases mirip dengan interface, tapi lebih fleksibel. Kita bisa pake type aliases untuk union types, primitive types, dan bahkan function types:
// Type alias untuk union type
type CourseStatus = "draft" | "review" | "published" | "archived";
// Type alias untuk objek (mirip interface)
type PaymentMethod = {
id: string;
name: string;
isActive: boolean;
};
// Type alias untuk function
type CourseValidator = (course: Course) => boolean;
// Type alias untuk array
type CourseList = Course[];
Kapan Pakai Interface vs Type Aliases?
Aturan umumnya:
- Pakai interface untuk mendefinisikan struktur objek yang mungkin akan di-extend atau di-implement
- Pakai type aliases untuk union types, computed types, atau tipe yang lebih kompleks
// Interface untuk objek yang bisa di-extend
interface User {
id: number;
name: string;
}
// Type alias untuk union types
type Theme = "light" | "dark" | "auto";
type AlertType = "success" | "error" | "warning" | "info";
Index Signatures
Kadang kita gak tau pasti property apa aja yang akan ada di objek, tapi kita tau pola tipe datanya. Index signatures bisa membantu:
interface CourseRatings {
[courseId: string]: number; // key berupa string, value berupa number
}
const ratings: CourseRatings = {
"react-fundamentals": 4.5,
"vue-masterclass": 4.8,
"typescript-basics": 4.2
};
// Bisa tambahin property baru kapan aja
ratings["nextjs-course"] = 4.9;
Function Types di Interface

Interface juga bisa mendefinisikan bentuk function:
interface CourseService {
getCourse(id: number): Promise<Course>;
createCourse(course: Omit<Course, 'id'>): Promise<Course>;
updateCourse(id: number, updates: Partial<Course>): Promise<Course>;
deleteCourse(id: number): Promise<boolean>;
}
class BuildWithAnggaCourseService implements CourseService {
async getCourse(id: number): Promise<Course> {
// implementasi get course
const response = await fetch(`/api/courses/${id}`);
return response.json();
}
async createCourse(course: Omit<Course, 'id'>): Promise<Course> {
// implementasi create course
const response = await fetch('/api/courses', {
method: 'POST',
body: JSON.stringify(course)
});
return response.json();
}
// implementasi method lainnya...
}
Dengan interfaces dan type aliases, kode TypeScript kita jadi lebih terstruktur, mudah dibaca, dan gampang di-maintain. Plus, kita dapet manfaat autocompletion dan type checking yang maksimal dari IDE!
Generic Types dan Utility Types
Nah, sekarang kita masuk ke materi yang agak lebih advanced tapi super powerful: generic types dan utility types. Kalau sebelumnya kita belajar bikin tipe data yang spesifik, sekarang kita akan belajar bikin tipe data yang fleksibel dan bisa digunakan untuk berbagai macam situasi.
Generic types itu kayak template yang bisa diisi dengan tipe data apa aja. Bayangin kamu punya sebuah kotak yang bisa diisi apapun - bisa kursus, bisa siswa, bisa instruktur. Itu konsep dasar dari generics. Sedangkan utility types itu helper types yang udah disediakan TypeScript untuk mempermudah transformasi tipe data.
Pengenalan Generic Types
Generic types ditulis dengan menggunakan angle brackets <T>. Huruf T itu cuma konvensi, kamu bisa pake huruf apa aja. Mari kita lihat contoh sederhana:
// Generic function untuk buildwithangga API response
function createApiResponse<T>(data: T, success: boolean, message: string) {
return {
data,
success,
message,
timestamp: new Date()
};
}
// Bisa dipake untuk berbagai tipe data
const courseResponse = createApiResponse<Course>({
id: 1,
title: "React Fundamentals",
instructor: "Angga Risky",
price: 299000
}, true, "Course berhasil dimuat");
const studentResponse = createApiResponse<Student>({
id: 1,
name: "John Doe",
email: "[email protected]"
}, true, "Data student berhasil dimuat");
Generic Interface
Interface juga bisa menggunakan generics. Ini sangat berguna untuk membuat struktur data yang reusable:
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
timestamp: Date;
meta?: {
total?: number;
page?: number;
limit?: number;
};
}
interface PaginatedList<T> {
items: T[];
pagination: {
currentPage: number;
totalPages: number;
totalItems: number;
itemsPerPage: number;
};
}
// Penggunaan untuk data kursus buildwithangga
type CourseListResponse = ApiResponse<PaginatedList<Course>>;
type StudentListResponse = ApiResponse<PaginatedList<Student>>;
const courseList: CourseListResponse = {
data: {
items: [
{
id: 1,
title: "TypeScript untuk Frontend",
instructor: "Angga Risky",
price: 349000,
duration: 1800,
level: "intermediate",
isPublished: true,
thumbnailUrl: "<https://buildwithangga.com/thumb/ts-course.jpg>",
tags: ["TypeScript", "Frontend"]
}
],
pagination: {
currentPage: 1,
totalPages: 5,
totalItems: 50,
itemsPerPage: 10
}
},
success: true,
message: "Daftar kursus berhasil dimuat",
timestamp: new Date()
};
Constraints pada Generics
Kadang kita mau membatasi tipe apa aja yang bisa dipake di generic. Gunakan keyword extends:
// Hanya menerima objek yang punya property id
interface HasId {
id: number;
}
function updateItem<T extends HasId>(item: T, updates: Partial<T>): T {
return { ...item, ...updates };
}
// Bisa dipake untuk Course karena punya property id
const updatedCourse = updateItem(reactCourse, {
title: "React Advanced Concepts",
price: 399000
});
// Error kalau objek gak punya property id
// const invalidUpdate = updateItem({ name: "Test" }, { name: "Updated" });
Multiple Type Parameters
Generic bisa punya lebih dari satu parameter:
interface KeyValuePair<K, V> {
key: K;
value: V;
}
interface CourseProgress<T extends Course, U extends Student> {
course: T;
student: U;
completedLessons: number;
totalLessons: number;
lastAccessedAt: Date;
certificateEarned: boolean;
}
function createProgress<T extends Course, U extends Student>(
course: T,
student: U
): CourseProgress<T, U> {
return {
course,
student,
completedLessons: 0,
totalLessons: course.duration / 30, // asumsi tiap lesson 30 menit
lastAccessedAt: new Date(),
certificateEarned: false
};
}
Utility Types: Partial
Partial<T> membuat semua property dari tipe T menjadi optional. Berguna banget untuk update operations:
// Fungsi untuk update course
function updateCourse(id: number, updates: Partial<Course>): Promise<Course> {
// updates bisa berisi sebagian atau semua property Course
return fetch(`/api/courses/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
}).then(res => res.json());
}
// Bisa update cuma sebagian property
updateCourse(1, {
price: 199000,
isPublished: true
});
// Atau cuma satu property
updateCourse(2, {
title: "Updated Course Title"
});
Utility Types: Pick dan Omit
Pick<T, K> mengambil property tertentu dari tipe T, sedangkan Omit<T, K> menghilangkan property tertentu:
// Pick: ambil cuma property tertentu
type CoursePreview = Pick<Course, 'id' | 'title' | 'instructor' | 'thumbnailUrl'>;
const courseCard: CoursePreview = {
id: 1,
title: "Vue.js Masterclass",
instructor: "BuildWithAngga Team",
thumbnailUrl: "<https://buildwithangga.com/thumb/vue-course.jpg>"
};
// Omit: hilangkan property tertentu
type CreateCourseRequest = Omit<Course, 'id' | 'createdAt' | 'updatedAt'>;
async function createNewCourse(courseData: CreateCourseRequest): Promise<Course> {
const response = await fetch('/api/courses', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(courseData)
});
return response.json();
}
Utility Types: Required dan Readonly
Required<T> membuat semua property jadi wajib, sedangkan Readonly<T> membuat semua property jadi readonly:
interface CourseSettings {
theme?: 'light' | 'dark';
autoplay?: boolean;
quality?: 'low' | 'medium' | 'high';
subtitles?: boolean;
}
// Semua property jadi required
type RequiredSettings = Required<CourseSettings>;
const defaultSettings: RequiredSettings = {
theme: 'light',
autoplay: false,
quality: 'medium',
subtitles: true
};
// Semua property jadi readonly
type ImmutableCourse = Readonly<Course>;
const immutableCourse: ImmutableCourse = {
id: 1,
title: "Immutable Course",
instructor: "Test Instructor",
price: 299000,
duration: 1200,
level: "beginner",
isPublished: true,
thumbnailUrl: "test.jpg",
tags: ["test"]
};
// Error! Cannot assign to readonly property
// immutableCourse.title = "New Title";
Utility Types: Record
Record<K, T> membuat objek dengan key bertipe K dan value bertipe T:
// Record untuk menyimpan rating per kategori
type CategoryRatings = Record<string, number>;
const buildWithAnggaRatings: CategoryRatings = {
"frontend": 4.8,
"backend": 4.6,
"mobile": 4.7,
"ui-ux": 4.9
};
// Record untuk course status tracking
type CourseStatusMap = Record<number, 'not-started' | 'in-progress' | 'completed'>;
const studentProgress: CourseStatusMap = {
1: 'completed',
2: 'in-progress',
3: 'not-started'
};
Conditional Types
Ini fitur yang lebih advanced, tapi sangat powerful untuk membuat tipe yang adaptif:
type ApiResponseType<T> = T extends string
? { message: T }
: T extends number
? { code: T }
: { data: T };
// Hasilnya akan berbeda tergantung tipe inputnya
type StringResponse = ApiResponseType<string>; // { message: string }
type NumberResponse = ApiResponseType<number>; // { code: number }
type ObjectResponse = ApiResponseType<Course>; // { data: Course }
Generic types dan utility types ini bakal bikin kode TypeScript kamu jauh lebih fleksibel dan maintainable. Awalnya mungkin terlihat rumit, tapi setelah terbiasa, fitur-fitur ini bakal jadi senjata ampuh dalam development sehari-hari!
TypeScript dengan React: Props Typing

Nah, sekarang kita masuk ke bagian yang paling sering dipake di dunia frontend development: TypeScript dengan React. Kalau kamu udah familiar dengan React, pasti tau betapa pentingnya props untuk komunikasi antar komponen. Nah, dengan TypeScript, kita bisa bikin props typing yang lebih aman dan mudah di-maintain.
Props typing itu pada dasarnya cara kita kasih tau TypeScript tentang struktur data apa yang diharapkan sebuah komponen React. Ini kayak kontrak antara parent component dengan child component. Dengan props typing yang baik, kita bisa hindari error kayak "Cannot read property of undefined" yang sering bikin sakit kepala.
Basic Props Typing
Mari kita mulai dengan contoh sederhana. Misalnya kita mau bikin komponen course card untuk platform buildwithangga:
import React from 'react';
interface CourseCardProps {
id: number;
title: string;
instructor: string;
price: number;
thumbnailUrl: string;
level: 'beginner' | 'intermediate' | 'advanced';
isPublished: boolean;
}
const CourseCard: React.FC<CourseCardProps> = ({
id,
title,
instructor,
price,
thumbnailUrl,
level,
isPublished
}) => {
const formatPrice = (price: number): string => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR'
}).format(price);
};
return (
<div className="course-card">
<img src={thumbnailUrl} alt={title} />
<div className="course-info">
<h3>{title}</h3>
<p>Oleh: {instructor}</p>
<span className={`level level-${level}`}>{level}</span>
<div className="price">{formatPrice(price)}</div>
{!isPublished && <span className="draft-badge">Draft</span>}
</div>
</div>
);
};
export default CourseCard;
Dengan interface CourseCardProps, TypeScript akan memastikan bahwa setiap kali kita pake komponen ini, kita harus ngirim props dengan tipe data yang benar.
Optional Props
Kadang ada props yang gak selalu diperlukan. Kita bisa bikin props jadi opsional dengan tanda tanya:
interface StudentProfileProps {
id: number;
name: string;
email: string;
profilePicture?: string; // opsional
bio?: string; // opsional
enrolledCourses: Course[];
onEditProfile?: () => void; // callback function opsional
}
const StudentProfile: React.FC<StudentProfileProps> = ({
id,
name,
email,
profilePicture,
bio,
enrolledCourses,
onEditProfile
}) => {
return (
<div className="student-profile">
<div className="profile-header">
{profilePicture ? (
<img src={profilePicture} alt={name} />
) : (
<div className="default-avatar">{name.charAt(0)}</div>
)}
<h2>{name}</h2>
<p>{email}</p>
{onEditProfile && (
<button onClick={onEditProfile}>Edit Profile</button>
)}
</div>
{bio && (
<div className="bio">
<h3>Bio</h3>
<p>{bio}</p>
</div>
)}
<div className="enrolled-courses">
<h3>Kursus yang Diikuti ({enrolledCourses.length})</h3>
{enrolledCourses.map(course => (
<CourseCard key={course.id} {...course} />
))}
</div>
</div>
);
};
Children Props
React punya props khusus namanya children yang berisi elemen React apapun yang ada di antara opening dan closing tag komponen. TypeScript punya tipe khusus untuk ini:
interface ModalProps {
isOpen: boolean;
title: string;
onClose: () => void;
children: React.ReactNode; // bisa berisi apapun
}
const Modal: React.FC<ModalProps> = ({ isOpen, title, onClose, children }) => {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2>{title}</h2>
<button onClick={onClose} className="close-btn">×</button>
</div>
<div className="modal-body">
{children}
</div>
</div>
</div>
);
};
// Penggunaan:
// <Modal isOpen={true} title="Konfirmasi" onClose={() => setIsOpen(false)}>
// <p>Apakah Anda yakin ingin menghapus kursus ini?</p>
// <button>Ya, Hapus</button>
// <button>Batal</button>
// </Modal>
Event Handler Props
Untuk event handlers, TypeScript punya tipe-tipe khusus yang lebih spesifik:
interface SearchBarProps {
placeholder?: string;
value: string;
onChange: (value: string) => void;
onSubmit: (query: string) => void;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
}
const SearchBar: React.FC<SearchBarProps> = ({
placeholder = "Cari kursus...",
value,
onChange,
onSubmit,
onFocus,
onBlur
}) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(value);
};
return (
<form onSubmit={handleSubmit} className="search-bar">
<input
type="text"
placeholder={placeholder}
value={value}
onChange={handleInputChange}
onFocus={onFocus}
onBlur={onBlur}
className="search-input"
/>
<button type="submit" className="search-btn">
Cari
</button>
</form>
);
};
Generic Props
Kadang kita mau bikin komponen yang bisa handle berbagai tipe data. Di sinilah generic props berguna:
interface TableProps<T> {
data: T[];
columns: {
key: keyof T;
title: string;
render?: (value: T[keyof T], item: T) => React.ReactNode;
}[];
onRowClick?: (item: T) => void;
}
function Table<T>({ data, columns, onRowClick }: TableProps<T>) {
return (
<table className="data-table">
<thead>
<tr>
{columns.map(column => (
<th key={String(column.key)}>{column.title}</th>
))}
</tr>
</thead>
<tbody>
{data.map((item, index) => (
<tr
key={index}
onClick={() => onRowClick?.(item)}
className={onRowClick ? 'clickable' : ''}
>
{columns.map(column => (
<td key={String(column.key)}>
{column.render
? column.render(item[column.key], item)
: String(item[column.key])
}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
// Penggunaan untuk data kursus buildwithangga
const courseColumns = [
{ key: 'title' as keyof Course, title: 'Judul Kursus' },
{ key: 'instructor' as keyof Course, title: 'Instruktur' },
{
key: 'price' as keyof Course,
title: 'Harga',
render: (value: Course['price']) => formatPrice(value)
},
{
key: 'level' as keyof Course,
title: 'Level',
render: (value: Course['level']) => (
<span className={`badge badge-${value}`}>{value}</span>
)
}
];
<Table
data={courses}
columns={courseColumns}
onRowClick={(course) => navigate(`/courses/${course.id}`)}
/>
Props dengan Conditional Types
Untuk kasus yang lebih advanced, kita bisa pake conditional types dalam props:
interface BaseButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
}
interface ButtonWithHref extends BaseButtonProps {
href: string;
onClick?: never; // gak boleh ada onClick kalau ada href
}
interface ButtonWithOnClick extends BaseButtonProps {
onClick: () => void;
href?: never; // gak boleh ada href kalau ada onClick
}
type ButtonProps = ButtonWithHref | ButtonWithOnClick;
const Button: React.FC<ButtonProps> = ({ children, variant = 'primary', size = 'medium', disabled, ...props }) => {
const buttonClass = `btn btn-${variant} btn-${size} ${disabled ? 'disabled' : ''}`;
if ('href' in props) {
return (
<a
href={props.href}
className={buttonClass}
aria-disabled={disabled}
>
{children}
</a>
);
}
return (
<button
onClick={props.onClick}
disabled={disabled}
className={buttonClass}
>
{children}
</button>
);
};
Props Forwarding dengan forwardRef
Kalau kita perlu forward ref ke child component, TypeScript juga bisa handle ini dengan baik:
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: string;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ label, error, helperText, className, ...props }, ref) => {
const inputClass = `input ${error ? 'input-error' : ''} ${className || ''}`;
return (
<div className="input-wrapper">
{label && <label className="input-label">{label}</label>}
<input
ref={ref}
className={inputClass}
{...props}
/>
{error && <span className="input-error-message">{error}</span>}
{helperText && !error && <span className="input-helper">{helperText}</span>}
</div>
);
}
);
Input.displayName = 'Input';
Dengan props typing yang baik, komponen React kita jadi lebih robust, mudah dipahami, dan gampang di-maintain. Plus, developer experience jadi jauh lebih baik karena IDE bisa kasih autocompletion dan error detection yang akurat!
Error Handling dan Debugging

Nah, sekarang kita masuk ke bagian yang paling "nyata" dalam development sehari-hari: error handling dan debugging. Kalau kamu pikir dengan TypeScript semua error bakal hilang, sayangnya itu gak sepenuhnya benar. TypeScript memang membantu mencegah banyak error di compile time, tapi tetap aja ada error yang bisa muncul saat runtime.
Yang bikin TypeScript istimewa adalah dia kasih kita tools yang lebih baik untuk handle error dan debug kode kita. Plus, dengan type safety yang ketat, kita bisa lebih percaya diri kalau kode yang kita tulis itu robust dan gak gampang break di production.
Memahami TypeScript Errors
Sebelum kita belajar handle error, kita harus paham dulu jenis-jenis error yang mungkin muncul. Ada dua kategori utama: compile-time errors dan runtime errors.
Compile-time errors itu error yang tertangkap sama TypeScript compiler sebelum kode dijalankan:
interface Course {
id: number;
title: string;
price: number;
instructor: string;
}
const buildWithAnggaCourse: Course = {
id: 1,
title: "Complete TypeScript Course",
// Error: Property 'price' is missing
instructor: "Angga Risky"
};
// Error: Property 'description' does not exist on type 'Course'
console.log(buildWithAnggaCourse.description);
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
function getCourseById(id: number): Course | null {
// implementasi...
return null;
}
getCourseById("invalid-id"); // TypeScript error
Sedangkan runtime errors itu error yang terjadi saat aplikasi berjalan, meskipun TypeScript udah cek semua type-nya:
// Ini lolos type checking, tapi bisa error di runtime
async function fetchCourse(id: number): Promise<Course> {
const response = await fetch(`https://api.buildwithangga.com/courses/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // Bisa aja API return data yang gak sesuai interface
}
Membuat Custom Error Types
Untuk error handling yang lebih terstruktur, kita bisa bikin custom error types:
class CourseNotFoundError extends Error {
constructor(courseId: number) {
super(`Course dengan ID ${courseId} tidak ditemukan`);
this.name = 'CourseNotFoundError';
}
}
class InvalidCourseDataError extends Error {
constructor(message: string) {
super(`Data kursus tidak valid: ${message}`);
this.name = 'InvalidCourseDataError';
}
}
class NetworkError extends Error {
constructor(public status: number, message: string) {
super(`Network error ${status}: ${message}`);
this.name = 'NetworkError';
}
}
// Union type untuk semua kemungkinan errors
type CourseServiceError = CourseNotFoundError | InvalidCourseDataError | NetworkError;
Result Pattern untuk Error Handling
Salah satu pattern yang bagus untuk error handling di TypeScript adalah Result pattern. Ini memaksa kita untuk secara eksplisit handle error:
type Result<T, E = Error> = {
success: true;
data: T;
} | {
success: false;
error: E;
};
class CourseService {
async getCourse(id: number): Promise<Result<Course, CourseServiceError>> {
try {
const response = await fetch(`https://api.buildwithangga.com/courses/${id}`);
if (response.status === 404) {
return {
success: false,
error: new CourseNotFoundError(id)
};
}
if (!response.ok) {
return {
success: false,
error: new NetworkError(response.status, response.statusText)
};
}
const courseData = await response.json();
// Validasi struktur data
if (!this.isValidCourse(courseData)) {
return {
success: false,
error: new InvalidCourseDataError("Data tidak sesuai struktur Course")
};
}
return {
success: true,
data: courseData
};
} catch (error) {
return {
success: false,
error: new NetworkError(0, error instanceof Error ? error.message : "Unknown error")
};
}
}
private isValidCourse(data: any): data is Course {
return (
typeof data === 'object' &&
typeof data.id === 'number' &&
typeof data.title === 'string' &&
typeof data.price === 'number' &&
typeof data.instructor === 'string'
);
}
}
// Penggunaan dengan explicit error handling
async function loadCourse(id: number) {
const courseService = new CourseService();
const result = await courseService.getCourse(id);
if (!result.success) {
switch (result.error.name) {
case 'CourseNotFoundError':
console.error('Kursus tidak ditemukan:', result.error.message);
break;
case 'InvalidCourseDataError':
console.error('Data kursus tidak valid:', result.error.message);
break;
case 'NetworkError':
console.error('Error jaringan:', result.error.message);
break;
default:
console.error('Error tidak diketahui:', result.error.message);
}
return null;
}
// Di sini kita yakin result.data itu valid Course
console.log('Kursus berhasil dimuat:', result.data.title);
return result.data;
}
Type Guards untuk Runtime Validation
Type guards membantu kita validasi data di runtime sambil tetap maintain type safety:
// Type guard function
function isCourse(obj: any): obj is Course {
return (
obj &&
typeof obj === 'object' &&
typeof obj.id === 'number' &&
typeof obj.title === 'string' &&
typeof obj.price === 'number' &&
typeof obj.instructor === 'string' &&
typeof obj.isPublished === 'boolean'
);
}
function isStudent(obj: any): obj is Student {
return (
obj &&
typeof obj === 'object' &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string' &&
Array.isArray(obj.enrolledCourses)
);
}
// Penggunaan dalam API response handling
async function handleApiResponse<T>(
response: Response,
validator: (obj: any) => obj is T
): Promise<T> {
if (!response.ok) {
throw new NetworkError(response.status, `API error: ${response.statusText}`);
}
const data = await response.json();
if (!validator(data)) {
throw new InvalidCourseDataError("Response data tidak sesuai dengan expected type");
}
return data; // TypeScript tau ini udah valid T
}
// Contoh penggunaan
async function fetchBuildWithAnggaCourse(id: number): Promise<Course> {
const response = await fetch(`https://api.buildwithangga.com/courses/${id}`);
return handleApiResponse(response, isCourse);
}
Debugging dengan Source Maps
Untuk debugging yang efektif, pastikan tsconfig.json kamu punya pengaturan ini:
{
"compilerOptions": {
"sourceMap": true,
"declarationMap": true,
"inlineSourceMap": false,
"inlineSources": false
}
}
Pengaturan ini akan generate source maps yang memungkinkan kamu debug TypeScript code langsung di browser, bukan JavaScript hasil compile.
Assertion Functions untuk Debugging
Assertion functions berguna untuk debugging dan memastikan kondisi tertentu:
function assertIsCourse(obj: any, context: string = ''): asserts obj is Course {
if (!isCourse(obj)) {
throw new Error(`Assertion failed: object is not a Course. Context: ${context}`);
}
}
function assertIsNotNull<T>(value: T | null | undefined, message: string = ''): asserts value is T {
if (value == null) {
throw new Error(`Assertion failed: value is null or undefined. ${message}`);
}
}
// Penggunaan dalam development/debugging
function processCourseData(courseData: unknown) {
assertIsCourse(courseData, 'processCourseData input');
// TypeScript sekarang tau courseData itu Course
console.log(`Processing course: ${courseData.title}`);
assertIsNotNull(courseData.instructor, 'Course must have instructor');
// Lanjut proses...
}
Error Boundary untuk React Components
Kalau kamu pakai React, bisa bikin Error Boundary dengan TypeScript:
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}
class BuildWithAnggaErrorBoundary extends React.Component<
React.PropsWithChildren<{}>,
ErrorBoundaryState
> {
constructor(props: React.PropsWithChildren<{}>) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return {
hasError: true,
error
};
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('BuildWithAngga Error Boundary caught an error:', error, errorInfo);
// Bisa kirim error ke monitoring service
// logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Oops! Terjadi kesalahan</h2>
<p>Mohon refresh halaman atau coba lagi nanti.</p>
<details>
<summary>Detail error (untuk debugging)</summary>
<pre>{this.state.error?.stack}</pre>
</details>
</div>
);
}
return this.props.children;
}
}
Monitoring dan Logging
Untuk production, kita perlu sistem monitoring yang baik:
interface LogEntry {
level: 'info' | 'warn' | 'error';
message: string;
timestamp: Date;
userId?: number;
courseId?: number;
metadata?: Record<string, any>;
}
class Logger {
private logs: LogEntry[] = [];
private log(level: LogEntry['level'], message: string, metadata?: Record<string, any>) {
const logEntry: LogEntry = {
level,
message,
timestamp: new Date(),
metadata
};
this.logs.push(logEntry);
// Kirim ke monitoring service
this.sendToMonitoring(logEntry);
}
info(message: string, metadata?: Record<string, any>) {
this.log('info', message, metadata);
}
warn(message: string, metadata?: Record<string, any>) {
this.log('warn', message, metadata);
}
error(message: string, error?: Error, metadata?: Record<string, any>) {
this.log('error', message, {
...metadata,
error: error?.message,
stack: error?.stack
});
}
private async sendToMonitoring(logEntry: LogEntry) {
try {
await fetch('<https://api.buildwithangga.com/logging>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
});
} catch (error) {
// Silent fail untuk logging
console.error('Failed to send log to monitoring:', error);
}
}
}
// Global logger instance
export const logger = new Logger();
// Penggunaan
logger.info('User enrolled in course', { userId: 123, courseId: 456 });
logger.error('Failed to load course', error, { courseId: 789 });
Dengan error handling dan debugging yang proper, aplikasi buildwithangga kita bakal jauh lebih stabil dan mudah di-maintain. Ingat, error itu normal dalam development, yang penting adalah kita handle dengan baik!
Penutup
Sampai di sini kita udah membahas hampir semua aspek penting tentang TypeScript untuk frontend development. Dari basic types sampai error handling, semua konsep ini bakal bikin kamu jadi developer yang lebih produktif dan percaya diri dalam menulis kode.
TypeScript memang terlihat menakutkan di awal, tapi percaya deh, setelah kamu terbiasa, kamu gak akan mau balik lagi ke JavaScript biasa. Type safety yang diberikan TypeScript itu kayak jaring pengaman yang melindungi kamu dari berbagai macam bug yang bisa muncul di production.
Mengapa BuildWithAngga untuk Lanjut Belajar
Setelah kamu paham fundamental TypeScript, langkah selanjutnya adalah praktik melalui proyek nyata. BuildWithAngga punya pendekatan yang praktis dan berbasis proyek, jadi kamu akan langsung praktik dengan membangun aplikasi nyata. Ini sangat penting untuk TypeScript karena konsep type system itu lebih mudah dipahami melalui praktik.
Mentor di BuildWithAngga juga professionals berpengalaman yang aktif bekerja di industri. Mereka bisa berbagi best practices dan wawasan dunia nyata yang gak bisa kamu dapet dari dokuumentasi atau tutorial biasa.
Rekomendasi Jalur Pembelajaran
Kalau kamu mau melanjutkan perjalanan belajar dengan TypeScript, ini jalur kursus yang bisa kamu pertimbangkan:
Mulai dengan jalur "Frontend Web Developer" untuk fondasi JavaScript yang kuat. Lanjut ke kursus "React Developer" untuk pengalaman praktis menggabungkan React dengan TypeScript. Untuk full-stack development, kursus "Full-Stack JavaScript" menunjukkan bagaimana TypeScript dapat digunakan di frontend dan backend.
Tips Sukses Belajar TypeScript
Praktik secara konsisten adalah kunci utama. Sisihkan waktu setiap hari untuk coding, meskipun cuma 30 menit. Jangan terburu-buru ke konsep yang rumit - kuasai dasar-dasarnya dulu. Bangun proyek nyata yang kamu sukai dan implementasikan menggunakan TypeScript.
Bergabunglah dengan community BuildWithAngga dan berpartisipasi aktif dalam diskusi. Jangan ragu untuk bertanya atau berbagi kemajuan kamu.
Kesimpulan
Perjalanan TypeScript membutuhkan dedikasi tapi hasil yang kamu dapat akan sangat berharga. BuildWithAngga menyediakan jalur pembelajaran terstruktur yang akan membimbing kamu dari pemula sampai tingkat lanjut.
Jangan menunggu untuk mulai belajar. Kunjungi buildwithangga.com dan jelajahi opsi kursus yang tersedia. Investasi dalam pengembangan kemampuan adalah investasi terbaik yang bisa kamu buat untuk karier masa depan kamu.
Selamat belajar, dan semoga berhasil untuk perjalanan TypeScript kamu!