Cara Membuat Reusable API Composable di Nuxt JS 4

Pelajari cara membuat reusable API composable di Nuxt JS 4 dengan useFetch agar kode fetching lebih rapi, reusable, dan mudah dipelihara.

Cara Membuat Reusable API Composable di Nuxt JS 4
Cara Membuat Reusable API Composable di Nuxt JS 4 • Tutorial

Cara Membuat Reusable API Composable di Nuxt JS 4

Setelah memahami cara mengambil data API menggunakan useFetch di Nuxt JS 4, langkah berikutnya adalah membuat kode API fetching yang lebih reusable.

Dalam aplikasi yang semakin besar, penggunaan useFetch secara langsung pada setiap halaman dapat menyebabkan kode yang sama ditulis berulang kali.

Misalnya beberapa halaman membutuhkan data artikel:

<script setup>
const config = useRuntimeConfig()

const { data, error, status } = await useFetch(
  `${config.public.apiBase}/articles`
)
</script>

Jika pola tersebut digunakan di banyak halaman, lama-kelamaan kode menjadi sulit dikelola.

Solusinya adalah membuat reusable composable.

Dengan composable, logic untuk mengambil data API dapat dipisahkan dari komponen sehingga dapat digunakan kembali di berbagai halaman atau komponen Nuxt JS 4.

Apa Itu Composable di Nuxt JS 4?

Composable adalah fungsi yang digunakan untuk mengenkapsulasi dan menggunakan kembali logic pada aplikasi Vue atau Nuxt.

Nuxt menyediakan folder khusus bernama:

composables/

File yang berada di dalam folder tersebut dapat digunakan sebagai composable dalam aplikasi.

Contohnya:

composables/
└── useArticles.ts

Kemudian composable tersebut dapat digunakan dari halaman atau komponen:

<script setup>
const { data } = await useArticles()
</script>

Pendekatan ini membuat komponen menjadi lebih sederhana karena logic API fetching tidak perlu ditulis berulang kali.

Mengapa Perlu Membuat Reusable API Composable?

Bayangkan sebuah aplikasi memiliki beberapa halaman:

  • Halaman daftar artikel

  • Halaman kategori artikel

  • Halaman pencarian artikel

  • Halaman detail artikel

  • Halaman artikel terbaru

Tanpa composable, masing-masing halaman mungkin memiliki logic seperti:

useFetch(...)

beserta konfigurasi API, query parameter, error handling, dan transformasi response.

Jika terdapat perubahan pada endpoint API, Anda mungkin harus mengubah banyak file.

Dengan reusable composable, logic tersebut dapat dipusatkan pada satu tempat.

Keuntungannya antara lain:

  • Mengurangi duplikasi kode.

  • Mempermudah maintenance.

  • Membuat halaman lebih bersih.

  • Memudahkan perubahan endpoint API.

  • Memudahkan penggunaan ulang logic.

  • Membuat struktur aplikasi lebih scalable.

Membuat Composable useArticles

Sebagai contoh, kita akan membuat composable untuk mengambil daftar artikel.

Struktur project:

app/
├── components/
├── pages/
├── composables/
│   └── useArticles.ts
└── ...

Pada composables/useArticles.ts:

export const useArticles = () => {
  const config = useRuntimeConfig()

  return useFetch(`${config.public.apiBase}/articles`)
}

Sekarang composable dapat digunakan dari halaman.

Contohnya:

<script setup lang="ts">
const { data, error, status } = await useArticles()
</script>

<template>
  <div v-if="status === 'pending'">
    Memuat artikel...
  </div>

  <div v-else-if="error">
    Gagal mengambil artikel.
  </div>

  <article
    v-else
    v-for="article in data?.data"
    :key="article.id"
  >
    <h2>{{ article.title }}</h2>
  </article>
</template>

Sekarang halaman tidak perlu mengetahui bagaimana URL API dibentuk.

Halaman hanya perlu mengetahui bahwa useArticles() digunakan untuk mengambil data artikel.

Membuat Composable dengan Query Parameter

Reusable composable akan menjadi lebih berguna ketika dapat menerima parameter.

Misalnya API artikel memiliki fitur pencarian:

/api/articles?search=nuxt

Composable dapat dibuat seperti berikut:

export const useArticles = (search?: MaybeRefOrGetter<string>) => {
  const config = useRuntimeConfig()

  return useFetch(`${config.public.apiBase}/articles`, {
    query: {
      search
    }
  })
}

Kemudian digunakan:

<script setup lang="ts">
const search = ref('nuxt')

const { data, error, status } = await useArticles(search)
</script>

Dengan pendekatan tersebut, composable tetap reusable karena nilai pencarian dapat diberikan dari halaman atau komponen yang menggunakannya.

Menggunakan Parameter untuk Pagination

Composable juga dapat menangani pagination.

Misalnya API memiliki parameter:

/api/articles?page=1&per_page=10

Composable dapat dibuat:

interface ArticleOptions {
  page?: MaybeRefOrGetter<number>
  perPage?: MaybeRefOrGetter<number>
}

export const useArticles = (options: ArticleOptions = {}) => {
  const config = useRuntimeConfig()

  return useFetch(`${config.public.apiBase}/articles`, {
    query: {
      page: options.page,
      per_page: options.perPage
    }
  })
}

Kemudian:

<script setup lang="ts">
const page = ref(1)

const { data, error, status } = await useArticles({
  page,
  perPage: 10
})
</script>

Ketika nilai page berubah, dependency reactive tersebut dapat digunakan oleh useFetch untuk memperbarui request.

Pendekatan seperti ini sangat berguna untuk halaman artikel, produk, pengguna, transaksi, dan berbagai resource lain yang menggunakan pagination.

Membuat Composable untuk Detail Artikel

Selain daftar artikel, kita juga dapat membuat composable khusus untuk mengambil detail berdasarkan slug.

Misalnya endpoint:

/api/articles/belajar-nuxt-js-4

Buat file:

composables/
└── useArticle.ts

Kemudian:

export const useArticle = (
  slug: MaybeRefOrGetter<string>
) => {
  const config = useRuntimeConfig()

  return useFetch(
    () => `${config.public.apiBase}/articles/${toValue(slug)}`
  )
}

Composable tersebut dapat digunakan pada halaman detail:

<script setup lang="ts">
const route = useRoute()

const { data, error, status } = await useArticle(
  () => String(route.params.slug)
)
</script>

Kemudian template:

<template>
  <div v-if="status === 'pending'">
    Memuat artikel...
  </div>

  <div v-else-if="error">
    Artikel tidak ditemukan.
  </div>

  <article v-else-if="data">
    <h1>{{ data.data.title }}</h1>

    <div v-html="data.data.content"></div>
  </article>
</template>

Dengan cara ini, logic pengambilan artikel berdasarkan slug tidak perlu ditulis ulang.

Memisahkan Logic API dengan Tampilan

Salah satu manfaat terbesar reusable composable adalah pemisahan tanggung jawab.

Tanpa composable, sebuah halaman dapat berisi:

Page
 ├── API URL
 ├── Query
 ├── Fetching
 ├── Error handling
 ├── Loading state
 └── Template

Dengan composable:

Composable
 ├── API URL
 ├── Query
 └── Fetching

Page
 ├── Menampilkan loading
 ├── Menampilkan error
 └── Menampilkan data

Komponen menjadi lebih fokus pada presentation layer.

Sementara logic pengambilan data berada di composable.

Pendekatan ini sangat membantu ketika aplikasi mulai memiliki banyak halaman dan resource API.

Membuat Composable yang Lebih Fleksibel

Pada aplikasi yang lebih besar, sebaiknya composable tidak terlalu spesifik jika logic yang digunakan sebenarnya sama.

Misalnya daripada membuat banyak fungsi:

useArticles()
useProducts()
useUsers()
useCategories()

untuk logic yang benar-benar identik, Anda dapat membuat abstraction tertentu sesuai kebutuhan.

Namun jangan terlalu cepat membuat generic composable.

Abstraction yang terlalu kompleks justru dapat membuat kode lebih sulit dipahami.

Contoh yang sederhana dan mudah dipelihara sering kali lebih baik:

export const useArticles = (
  options: ArticleOptions = {}
) => {
  const config = useRuntimeConfig()

  return useFetch(`${config.public.apiBase}/articles`, {
    query: {
      search: options.search,
      page: options.page,
      per_page: options.perPage
    }
  })
}

Pendekatan tersebut masih mudah dibaca sekaligus cukup fleksibel untuk kebutuhan aplikasi.

Menambahkan TypeScript pada API Response

Jika backend menggunakan response JSON yang konsisten, penggunaan TypeScript dapat membuat composable lebih aman.

Misalnya backend mengembalikan:

{
  "data": [
    {
      "id": 1,
      "title": "Belajar Nuxt JS 4"
    }
  ]
}

Buat interface:

interface Article {
  id: number
  title: string
}

interface ArticleResponse {
  data: Article[]
}

Kemudian gunakan generic pada useFetch:

export const useArticles = () => {
  const config = useRuntimeConfig()

  return useFetch<ArticleResponse>(
    `${config.public.apiBase}/articles`
  )
}

Sekarang editor dapat membantu memberikan autocomplete dan mendeteksi kesalahan tipe data.

Contohnya:

<script setup lang="ts">
const { data } = await useArticles()

const articles = computed(() => data.value?.data ?? [])
</script>

Penggunaan TypeScript semakin bermanfaat ketika struktur response API semakin kompleks.

Reusable Composable untuk API Laravel

Jika Nuxt JS 4 digunakan sebagai frontend dan Laravel sebagai backend API, struktur seperti berikut dapat digunakan:

Nuxt JS 4
│
├── pages/
├── components/
├── composables/
│   ├── useArticles.ts
│   ├── useArticle.ts
│   └── useCategories.ts
│
└── runtimeConfig
        │
        ▼
Laravel API
│
├── /api/articles
├── /api/articles/{slug}
└── /api/categories

Base URL Laravel dapat disimpan pada environment:

NUXT_PUBLIC_API_BASE=https://api.example.com/api

Kemudian:

const config = useRuntimeConfig()

digunakan oleh composable.

Dengan demikian, ketika domain API berubah antara development dan production, konfigurasi dapat diubah melalui environment tanpa mengubah setiap halaman.

Jangan Menaruh Semua Logic ke Dalam Satu Composable

Reusable bukan berarti semua API harus berada dalam satu file.

Hindari membuat:

composables/
└── useApi.ts

yang berisi ratusan fungsi untuk seluruh resource aplikasi.

Struktur tersebut dapat menjadi sulit dipelihara ketika aplikasi berkembang.

Lebih baik mengelompokkan composable berdasarkan domain atau resource:

composables/
├── useArticles.ts
├── useArticle.ts
├── useCategories.ts
├── useProducts.ts
└── useUsers.ts

Dengan struktur tersebut, setiap composable memiliki tanggung jawab yang lebih jelas.

useFetch atau $fetch di Dalam Composable?

Pemilihan useFetch dan $fetch tetap bergantung pada kebutuhan.

Gunakan useFetch ketika composable digunakan untuk mengambil data yang menjadi bagian dari state halaman dan membutuhkan integrasi dengan lifecycle data fetching Nuxt.

Contohnya:

export const useArticles = () => {
  const config = useRuntimeConfig()

  return useFetch(`${config.public.apiBase}/articles`)
}

Sedangkan $fetch lebih cocok untuk operasi langsung seperti:

export const createArticle = async (
  payload: CreateArticlePayload
) => {
  const config = useRuntimeConfig()

  return $fetch(`${config.public.apiBase}/articles`, {
    method: 'POST',
    body: payload
  })
}

Contoh tersebut dapat digunakan ketika pengguna melakukan submit form.

Jangan menggunakan $fetch dan useFetch hanya karena salah satunya terlihat lebih sederhana. Pilih berdasarkan lifecycle dan kebutuhan data pada aplikasi.

Hal yang Perlu Diperhatikan Saat Membuat Composable

Ada beberapa prinsip yang sebaiknya diperhatikan.

1. Hindari Duplikasi Logic

Jika logic fetching yang sama digunakan pada beberapa halaman, pertimbangkan untuk memindahkannya ke composable.

2. Jangan Membuat Abstraction Berlebihan

Tidak semua API request membutuhkan generic composable.

Jika abstraction membuat kode lebih sulit dibaca, gunakan composable yang lebih spesifik.

3. Gunakan TypeScript

Untuk aplikasi berskala menengah hingga besar, typing pada request dan response dapat mengurangi error ketika struktur API berubah.

4. Pisahkan Data Layer dari UI

Composables dapat menjadi tempat yang baik untuk menempatkan logic data fetching sehingga halaman lebih fokus pada tampilan.

5. Perhatikan SSR

Composable yang menggunakan useFetch tetap perlu dirancang dengan mempertimbangkan SSR, terutama ketika endpoint membutuhkan authentication atau informasi khusus dari request pengguna.

6. Jangan Hardcode Environment

Base URL API sebaiknya tetap berasal dari runtimeConfig, bukan ditulis berulang kali pada setiap composable.

Kesimpulan

Reusable API composable merupakan langkah penting setelah memahami useFetch di Nuxt JS 4.

Dengan memindahkan logic API fetching ke composable, kode aplikasi menjadi lebih terstruktur dan lebih mudah digunakan kembali.

Beberapa hal penting yang perlu diingat:

  • Gunakan folder composables/ untuk reusable logic.

  • Gunakan useFetch untuk data fetching yang terintegrasi dengan Nuxt.

  • Gunakan parameter reactive untuk membuat composable lebih fleksibel.

  • Gunakan runtimeConfig untuk base URL API.

  • Gunakan TypeScript untuk membantu menjaga konsistensi response API.

  • Pisahkan logic data fetching dari presentation layer.

  • Hindari abstraction yang terlalu kompleks.

  • Gunakan composable berdasarkan domain atau resource ketika aplikasi semakin besar.

Setelah memahami reusable API composable, tahap berikutnya adalah membuat API layer yang lebih terstruktur, misalnya memisahkan HTTP client, authentication, interceptor, error handling, dan endpoint API agar aplikasi Nuxt JS 4 tetap mudah dikembangkan ketika jumlah fitur semakin besar.

Baca juga artikel serupa :

  1. Cara Install Nuxt.js Terbaru (Nuxt 4) untuk Pemula

  2. Cara Mengambil Data API di Nuxt JS 4 dengan useFetch

Tautan Artikel:
Ananda Resa Ardiansyah
Profil Penulis
Ananda Resa Ardiansyah

Web Developer

Fullstack Web Developer specializing in Laravel, React, and Next.js, with a focus on performance, scalability, and clean architecture.