← Back to blog

Membuat TypeScript SDK yang Nyaman Digunakan

TypeScriptSDKAPI DesignDeveloper Experience

Membungkus REST API dengan beberapa fungsi fetch memang mudah. Membuat SDK yang tetap nyaman setelah endpoint bertambah, format error berubah, dan dipakai oleh project lain ternyata membutuhkan lebih banyak perhatian.

Menurut saya, SDK yang baik tidak perlu terasa pintar. Ia cukup membuat alur umum menjadi sederhana, memberikan type yang membantu, dan tidak menyembunyikan terlalu banyak perilaku penting.

Mulai dari cara pengguna memakainya

Sebelum menyusun struktur folder, tulis contoh penggunaan yang ingin dicapai:

import { AgentClient } from '@example/agent-client';

const client = new AgentClient({
  apiKey: process.env.AGENT_API_KEY
});

const response = await client.chat.send({
  agentId: 'learning-assistant',
  message: 'Jelaskan materi ini dengan contoh sederhana.'
});

Contoh kecil seperti ini membantu menjawab beberapa pertanyaan lebih awal:

  • Apa yang wajib dikonfigurasi?
  • Resource apa yang paling sering digunakan?
  • Nama method mana yang mudah ditebak?
  • Bagian mana yang perlu asynchronous?
  • Hasil seperti apa yang diharapkan pengguna?

API publik SDK sebaiknya dibentuk dari kebutuhan pemakai, bukan dari struktur internal server.

Pisahkan transport dan resource

Saya lebih suka memisahkan kode HTTP dari domain yang menggunakannya. Transport menangani base URL, header, serialisasi, timeout, dan error. Resource fokus pada endpoint dan type.

class HttpClient {
  constructor(
    private baseUrl: string,
    private apiKey: string
  ) {}

  async request<T>(path: string, init?: RequestInit): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      ...init,
      headers: {
        'content-type': 'application/json',
        authorization: `Bearer ${this.apiKey}`,
        ...init?.headers
      }
    });

    if (!response.ok) {
      throw await ApiError.fromResponse(response);
    }

    return response.json() as Promise<T>;
  }
}

Dengan pemisahan ini, autentikasi atau strategi retry dapat diperbaiki di satu tempat tanpa mengubah seluruh resource.

Jangan bocorkan response mentah ke mana-mana

Response server sering mengikuti kebutuhan backend, sedangkan pengguna SDK membutuhkan bentuk yang stabil. SDK dapat menjadi lapisan adaptasi tipis di antaranya.

type ChatResponseDto = {
  conversation_id: string;
  output_text: string;
  created_at: string;
};

type ChatResponse = {
  conversationId: string;
  text: string;
  createdAt: Date;
};

Mapping ini memang menambah sedikit kode, tetapi memberi ruang bagi API server dan SDK untuk berkembang dengan ritme berbeda. Perubahan internal tidak otomatis menjadi breaking change bagi semua pengguna.

Error harus bisa dikenali

Pesan Request failed tidak cukup membantu. Pengguna perlu mengetahui apakah masalah berasal dari autentikasi, input, rate limit, atau server.

class ApiError extends Error {
  constructor(
    message: string,
    readonly status: number,
    readonly code?: string,
    readonly requestId?: string
  ) {
    super(message);
  }
}

Dengan error terstruktur, aplikasi pemakai dapat mengambil tindakan yang sesuai:

try {
  await client.chat.send(input);
} catch (error) {
  if (error instanceof ApiError && error.status === 429) {
    // Tampilkan pesan rate limit atau jadwalkan ulang request.
  }
}

SDK sebaiknya tidak melakukan retry untuk semua error. Retry hanya aman untuk operasi tertentu dan harus mempertimbangkan idempotency.

Validasi tetap diperlukan saat runtime

TypeScript hanya membantu ketika kode dikompilasi. Data dari network tetap dapat memiliki bentuk yang tidak sesuai dengan type.

Untuk response penting, gunakan schema validator atau pemeriksaan runtime sederhana. Ini sangat berguna ketika SDK terhubung dengan API yang masih aktif berkembang. Lebih baik gagal dengan pesan yang jelas daripada meneruskan undefined ke bagian aplikasi yang jauh dari sumber masalah.

Sediakan jalan keluar

Abstraksi SDK tidak akan mencakup semua kebutuhan. Berikan akses terkontrol untuk konfigurasi umum, misalnya custom fetch, timeout, header tambahan, atau base URL untuk environment pengujian.

const client = new AgentClient({
  apiKey,
  baseUrl: 'https://staging.example.com',
  fetch: instrumentedFetch
});

Kemampuan mengganti fetch juga membuat SDK lebih mudah diuji dan diintegrasikan dengan tracing yang sudah digunakan aplikasi.

Uji dari sudut pandang pengguna

Selain menguji method internal, tulis test yang menggunakan SDK melalui public API. Beberapa kasus yang paling berguna adalah:

  • Konfigurasi minimum berhasil digunakan
  • Header autentikasi terkirim
  • Request body diserialisasi dengan benar
  • Response diubah menjadi public type
  • Error API menghasilkan error terstruktur
  • Timeout dan pembatalan request bekerja

Test public API membantu menjaga refactor internal agar tidak merusak pengalaman pengguna.

Kesimpulan

SDK adalah produk kecil untuk developer. Kualitasnya terasa dari hal-hal sederhana: nama yang mudah ditebak, type yang jujur, error yang berguna, dan dokumentasi yang dapat langsung dicoba.

Saya mencoba menjaga SDK tetap tipis. Ia membantu pengguna berkomunikasi dengan API tanpa mengambil alih terlalu banyak keputusan dari aplikasi yang menggunakannya.