Bahasa Indonesia

Kuasai panggilan API type-safe di TypeScript untuk aplikasi web yang andal, mudah dipelihara, dan bebas eror. Pelajari praktik terbaik dan teknik canggih.

Panggilan API Type-Safe dengan TypeScript: Panduan Komprehensif

Dalam pengembangan web modern, berinteraksi dengan API adalah tugas mendasar. TypeScript, dengan sistem tipenya yang kuat, menawarkan keuntungan signifikan dalam memastikan keandalan dan keterpeliharaan aplikasi Anda dengan mengaktifkan panggilan API yang type-safe. Panduan ini akan membahas cara memanfaatkan fitur-fitur TypeScript untuk membangun interaksi API yang kuat dan bebas eror, mencakup praktik terbaik, teknik canggih, dan contoh dunia nyata.

Mengapa Keamanan Tipe (Type Safety) Penting untuk Panggilan API

Saat bekerja dengan API, pada dasarnya Anda berurusan dengan data yang berasal dari sumber eksternal. Data ini mungkin tidak selalu dalam format yang Anda harapkan, yang dapat menyebabkan eror saat runtime dan perilaku yang tidak terduga. Keamanan tipe menyediakan lapisan perlindungan penting dengan memverifikasi bahwa data yang Anda terima sesuai dengan struktur yang telah ditentukan, sehingga dapat menangkap potensi masalah di awal proses pengembangan.

Menyiapkan Proyek TypeScript Anda

Sebelum mendalami panggilan API, pastikan Anda telah menyiapkan proyek TypeScript. Jika Anda memulai dari awal, Anda dapat menginisialisasi proyek baru menggunakan:

npm init -y
npm install typescript --save-dev
tsc --init

Ini akan membuat file `tsconfig.json` dengan opsi kompiler TypeScript default. Anda dapat menyesuaikan opsi ini agar sesuai dengan kebutuhan proyek Anda. Misalnya, Anda mungkin ingin mengaktifkan mode ketat (strict mode) untuk pemeriksaan tipe yang lebih ketat:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Mendefinisikan Tipe untuk Respons API

Langkah pertama dalam mencapai panggilan API yang type-safe adalah mendefinisikan tipe TypeScript yang mewakili struktur data yang Anda harapkan akan diterima dari API. Ini biasanya dilakukan menggunakan deklarasi `interface` atau `type`.

Menggunakan Interface

Interface adalah cara yang ampuh untuk mendefinisikan bentuk sebuah objek. Misalnya, jika Anda mengambil daftar pengguna dari API, Anda mungkin mendefinisikan interface seperti ini:

interface User {
  id: number;
  name: string;
  email: string;
  address?: string; // Properti opsional
  phone?: string; // Properti opsional
  website?: string; // Properti opsional
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
}

Tanda `?` setelah nama properti menunjukkan bahwa properti tersebut bersifat opsional. Ini berguna untuk menangani respons API di mana beberapa bidang mungkin tidak ada.

Menggunakan Type

Type mirip dengan interface tetapi menawarkan lebih banyak fleksibilitas, termasuk kemampuan untuk mendefinisikan union type dan intersection type. Anda dapat mencapai hasil yang sama seperti interface di atas menggunakan type:

type User = {
  id: number;
  name: string;
  email: string;
  address?: string; // Properti opsional
  phone?: string; // Properti opsional
  website?: string; // Properti opsional
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
};

Untuk struktur objek sederhana, interface dan type seringkali dapat digunakan secara bergantian. Namun, type menjadi lebih kuat ketika berhadapan dengan skenario yang lebih kompleks.

Melakukan Panggilan API dengan Axios

Axios adalah klien HTTP populer untuk membuat permintaan API di JavaScript dan TypeScript. Ia menyediakan API yang bersih dan intuitif, membuatnya mudah untuk menangani berbagai metode HTTP, header permintaan, dan data respons.

Menginstal Axios

npm install axios

Melakukan Panggilan API dengan Tipe

Untuk membuat panggilan API yang type-safe dengan Axios, Anda dapat menggunakan metode `axios.get` dan menentukan tipe respons yang diharapkan menggunakan generic:

import axios from 'axios';

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Eror saat mengambil data pengguna:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

Dalam contoh ini, `axios.get('...')` memberitahu TypeScript bahwa data respons diharapkan berupa sebuah array (larik) dari objek `User`. Ini memungkinkan TypeScript untuk menyediakan pemeriksaan tipe dan pelengkapan otomatis (autocompletion) saat bekerja dengan data respons.

Menangani Metode HTTP yang Berbeda

Axios mendukung berbagai metode HTTP, termasuk `GET`, `POST`, `PUT`, `DELETE`, dan `PATCH`. Anda dapat menggunakan metode yang sesuai untuk membuat berbagai jenis permintaan API. Misalnya, untuk membuat pengguna baru, Anda bisa menggunakan metode `axios.post`:

async function createUser(user: Omit): Promise {
  try {
    const response = await axios.post('https://jsonplaceholder.typicode.com/users', user);
    return response.data;
  } catch (error) {
    console.error('Eror saat membuat pengguna:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Leading the way',
    bs: 'Innovative solutions'
  }
};

createUser(newUser).then(user => {
  console.log('Pengguna yang dibuat:', user);
});

Dalam contoh ini, `Omit` membuat sebuah tipe yang sama dengan `User` tetapi tanpa properti `id`. Ini berguna karena `id` biasanya dibuat oleh server saat membuat pengguna baru.

Menggunakan Fetch API

Fetch API adalah API bawaan JavaScript untuk membuat permintaan HTTP. Meskipun lebih dasar daripada Axios, ia juga dapat digunakan dengan TypeScript untuk mencapai panggilan API yang type-safe. Anda mungkin lebih memilihnya untuk menghindari penambahan dependensi jika sesuai dengan kebutuhan Anda.

Melakukan Panggilan API dengan Tipe menggunakan Fetch

Untuk membuat panggilan API yang type-safe dengan Fetch, Anda dapat menggunakan fungsi `fetch` dan kemudian mem-parsing respons sebagai JSON, dengan menentukan tipe respons yang diharapkan:

async function fetchUsers(): Promise {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    if (!response.ok) {
      throw new Error(`Eror HTTP! status: ${response.status}`);
    }
    const data: User[] = await response.json();
    return data;
  } catch (error) {
    console.error('Eror saat mengambil data pengguna:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

Dalam contoh ini, `const data: User[] = await response.json();` memberitahu TypeScript bahwa data respons harus diperlakukan sebagai sebuah array (larik) dari objek `User`. Ini memungkinkan TypeScript untuk melakukan pemeriksaan tipe dan pelengkapan otomatis.

Menangani Metode HTTP yang Berbeda dengan Fetch

Untuk membuat berbagai jenis permintaan API dengan Fetch, Anda dapat menggunakan fungsi `fetch` dengan opsi yang berbeda, seperti opsi `method` dan `body`. Misalnya, untuk membuat pengguna baru, Anda dapat menggunakan kode berikut:

async function createUser(user: Omit): Promise {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(user)
    });
    if (!response.ok) {
      throw new Error(`Eror HTTP! status: ${response.status}`);
    }
    const data: User = await response.json();
    return data;
  } catch (error) {
    console.error('Eror saat membuat pengguna:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Leading the way',
    bs: 'Innovative solutions'
  }
};

createUser(newUser).then(user => {
  console.log('Pengguna yang dibuat:', user);
});

Menangani Eror API

Penanganan eror adalah aspek penting dari panggilan API. API dapat gagal karena berbagai alasan, termasuk masalah konektivitas jaringan, eror server, dan permintaan yang tidak valid. Penting untuk menangani eror ini dengan baik untuk mencegah aplikasi Anda mogok atau menampilkan perilaku yang tidak terduga.

Menggunakan Blok Try-Catch

Cara paling umum untuk menangani eror dalam kode asinkron adalah dengan menggunakan blok try-catch. Ini memungkinkan Anda untuk menangkap setiap pengecualian (exception) yang dilemparkan selama panggilan API dan menanganinya dengan tepat.

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Eror saat mengambil data pengguna:', error);
    // Tangani eror, misalnya, tampilkan pesan eror kepada pengguna
    throw error; // Lempar kembali eror agar kode pemanggil juga dapat menanganinya
  }
}

Menangani Kode Eror Spesifik

API sering kali mengembalikan kode eror spesifik untuk menunjukkan jenis eror yang terjadi. Anda dapat menggunakan kode eror ini untuk menyediakan penanganan eror yang lebih spesifik. Misalnya, Anda mungkin ingin menampilkan pesan eror yang berbeda untuk eror 404 Not Found daripada untuk eror 500 Internal Server Error.

async function fetchUser(id: number): Promise {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      console.log(`Pengguna dengan ID ${id} tidak ditemukan.`);
      return null; // Atau lemparkan eror kustom
    } else {
      console.error('Eror saat mengambil data pengguna:', error);
      throw error;
    }
  }
}

fetchUser(123).then(user => {
  if (user) {
    console.log('Pengguna:', user);
  } else {
    console.log('Pengguna tidak ditemukan.');
  }
});

Membuat Tipe Eror Kustom

Untuk skenario penanganan eror yang lebih kompleks, Anda dapat membuat tipe eror kustom untuk mewakili berbagai jenis eror API. Ini memungkinkan Anda memberikan informasi eror yang lebih terstruktur dan menangani eror dengan lebih efektif.

class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

async function fetchUser(id: number): Promise {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      throw new ApiError(404, `Pengguna dengan ID ${id} tidak ditemukan.`);
    } else {
      console.error('Eror saat mengambil data pengguna:', error);
      throw new ApiError(500, 'Internal Server Error'); //Atau kode status lain yang sesuai
    }
  }
}

fetchUser(123).catch(error => {
  if (error instanceof ApiError) {
    console.error(`Eror API: ${error.statusCode} - ${error.message}`);
  } else {
    console.error('Terjadi eror yang tidak terduga:', error);
  }
});

Validasi Data

Bahkan dengan sistem tipe TypeScript, sangat penting untuk memvalidasi data yang Anda terima dari API saat runtime. API dapat mengubah struktur respons mereka tanpa pemberitahuan, dan tipe TypeScript Anda mungkin tidak selalu sinkron sempurna dengan respons aktual API.

Menggunakan Zod untuk Validasi Runtime

Zod adalah pustaka TypeScript yang populer untuk validasi data saat runtime. Ini memungkinkan Anda untuk mendefinisikan skema yang menggambarkan struktur data yang diharapkan dan kemudian memvalidasi data terhadap skema tersebut saat runtime.

Menginstal Zod

npm install zod

Memvalidasi Respons API dengan Zod

Untuk memvalidasi respons API dengan Zod, Anda dapat mendefinisikan skema Zod yang sesuai dengan tipe TypeScript Anda dan kemudian menggunakan metode `parse` untuk memvalidasi data.

import { z } from 'zod';

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  address: z.string().optional(),
  phone: z.string().optional(),
  website: z.string().optional(),
  company: z.object({
    name: z.string(),
    catchPhrase: z.string(),
    bs: z.string(),
  }).optional(),
});

type User = z.infer;

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    const data = z.array(userSchema).parse(response.data);
    return data;
  } catch (error) {
    console.error('Eror saat mengambil data pengguna:', error);
    throw error;
  }
}

Dalam contoh ini, `z.array(userSchema).parse(response.data)` memvalidasi bahwa data respons adalah sebuah array (larik) dari objek yang sesuai dengan `userSchema`. Jika data tidak sesuai dengan skema, Zod akan melemparkan eror, yang kemudian dapat Anda tangani dengan tepat.

Teknik Lanjutan

Menggunakan Generic untuk Fungsi API yang Dapat Digunakan Kembali

Generic memungkinkan Anda menulis fungsi API yang dapat digunakan kembali yang dapat menangani berbagai jenis data. Misalnya, Anda dapat membuat fungsi `fetchData` generik yang dapat mengambil data dari endpoint API mana pun dan mengembalikannya dengan tipe yang benar.

async function fetchData(url: string): Promise {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (error) {
    console.error(`Eror saat mengambil data dari ${url}:`, error);
    throw error;
  }
}

// Penggunaan
fetchData('https://jsonplaceholder.typicode.com/users').then(users => {
  console.log('Pengguna:', users);
});

fetchData<{ title: string; body: string }>('https://jsonplaceholder.typicode.com/todos/1').then(todo => {
    console.log('Todo', todo)
});

Menggunakan Interceptor untuk Penanganan Eror Global

Axios menyediakan interceptor yang memungkinkan Anda untuk mencegat permintaan dan respons sebelum ditangani oleh kode Anda. Anda dapat menggunakan interceptor untuk mengimplementasikan penanganan eror global, seperti mencatat eror atau menampilkan pesan eror kepada pengguna.

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error('Penangan eror global:', error);
    // Tampilkan pesan eror kepada pengguna
    return Promise.reject(error);
  }
);

Menggunakan Variabel Lingkungan untuk URL API

Untuk menghindari penulisan URL API secara langsung (hardcoding) di kode Anda, Anda dapat menggunakan variabel lingkungan untuk menyimpan URL tersebut. Ini memudahkan konfigurasi aplikasi Anda untuk lingkungan yang berbeda, seperti pengembangan, staging, dan produksi.

Contoh menggunakan file `.env` dan paket `dotenv`.

// .env
API_URL=https://api.example.com
// Instal dotenv
npm install dotenv
// Impor dan konfigurasikan dotenv
import * as dotenv from 'dotenv'
dotenv.config()

const apiUrl = process.env.API_URL || 'http://localhost:3000'; // berikan nilai default

async function fetchData(endpoint: string): Promise {
  try {
    const response = await axios.get(`${apiUrl}/${endpoint}`);
    return response.data;
  } catch (error) {
    console.error(`Eror saat mengambil data dari ${apiUrl}/${endpoint}:`, error);
    throw error;
  }
}

Kesimpulan

Panggilan API yang type-safe sangat penting untuk membangun aplikasi web yang kuat, mudah dipelihara, dan bebas eror. TypeScript menyediakan fitur-fitur canggih yang memungkinkan Anda untuk mendefinisikan tipe untuk respons API, memvalidasi data saat runtime, dan menangani eror dengan baik. Dengan mengikuti praktik terbaik dan teknik yang diuraikan dalam panduan ini, Anda dapat secara signifikan meningkatkan kualitas dan keandalan interaksi API Anda.

Dengan menggunakan TypeScript dan pustaka seperti Axios dan Zod, Anda dapat memastikan bahwa panggilan API Anda aman secara tipe, data Anda divalidasi, dan eror Anda ditangani dengan baik. Ini akan menghasilkan aplikasi yang lebih kuat dan mudah dipelihara.

Ingatlah untuk selalu memvalidasi data Anda saat runtime, bahkan dengan sistem tipe TypeScript. API dapat berubah, dan tipe Anda mungkin tidak selalu sinkron sempurna dengan respons aktual API. Dengan memvalidasi data Anda saat runtime, Anda dapat menangkap potensi masalah sebelum menyebabkan masalah di aplikasi Anda.

Selamat membuat kode!