Pendahuluan

Nuxt 3 menyediakan Nitro sebagai runtime server yang secara otomatis menangani kompilasi API internal. Ketika aplikasi membutuhkan endpoint backend sederhana, Nitro membuat pembuatan API internal menjadi efisien tanpa menambahkan projek terpisah. Artikel ini membahas cara menyusun server API di Nuxt 3, mulai dari struktur folder, request handling, validasi input, sampai integrasi dengan database atau layanan lain.

Struktur Folder server/api

Di project Nuxt 3, Nitro mengumpulkan semua file di server/api dan menganggapnya sebagai route API. Setiap file merepresentasikan route berdasarkan struktur folder:

  • server/api/users.get.ts menjadi GET /api/users
  • server/api/products/[id].post.ts menjadi POST /api/products/:id
  • server/api/admin/metrics.get.ts jadi GET /api/admin/metrics

Selain folder, Anda dapat mengganti method HTTP dengan ekstensi file (.get.ts, .post.ts, .put.ts, .delete.ts). Ini membuat navigasi dan konvensi route mudah dipahami. Untuk route dinamis, gunakan parameter dalam nama folder/file seperti [userId].

Handling Request dan Response

Nitro menyediakan helper seperti defineEventHandler untuk menangani request. Event handler menerima objek request dan response bawaan Node.js tetapi disederhanakan. Sebagai contoh:

import { defineEventHandler } from 'h3'

export default defineEventHandler(async (event) => {
  const { req, res } = event
  const params = getQuery(event)
  return {
    message: 'API nuxt internal aktif',
    params
  }
})

Gunakan getQuery untuk mengambil query params dan readBody untuk payload JSON. Result handler dapat berupa data, promise, atau createError untuk raise error. Hasilnya akan otomatis di-serialize menjadi JSON.

Query Params dan Body Validation

Untuk query params, Nuxt/Nitro tidak menyediakan schema validation bawaan. Kode contoh berikut menunjukkan bagaimana memeriksa keberadaan parameter:

import { defineEventHandler, sendError } from 'h3'

export default defineEventHandler((event) => {
  const { id } = getQuery(event)
  if (!id) {
    return sendError(event, createError({ statusCode: 400, statusMessage: 'Parameter id dibutuhkan' }))
  }
  return { id }
})

Untuk validation body, gunakan library seperti zod atau @hapi/joi. Contoh dengan zod:

import { defineEventHandler } from 'h3'
import { z } from 'zod'

const userSchema = z.object({
  name: z.string().min(3),
  email: z.string().email()
})

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const parsed = userSchema.safeParse(body)
  if (!parsed.success) {
    return createError({ statusCode: 422, statusMessage: JSON.stringify(parsed.error.issues) })
  }
  return { data: parsed.data }
})

Dengan pendekatan ini, developer mengetahui kesalahan input segera dan response jelas.

Environment Variables di Nitro

Environment variables dikelola lewat file .env atau .env.defaults. Nuxt 3 menyediakan useRuntimeConfig() untuk mengakses nilai tersebut dengan aman:

export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      apiBase: '/api'
    },
    dbUrl: process.env.DB_URL
  }
})

Di server API:

const runtimeConfig = useRuntimeConfig()
const dbUrl = runtimeConfig.dbUrl

Karena environment variables tidak ditransfer ke client kecuali di bawah runtimeConfig.public, Anda dapat menyimpan credential sensitif dengan aman.

Koneksi ke Database atau Service Eksternal

Server API Nuxt cocok bila butuh integrasi langsung ke database atau layanan lain tanpa perlu server terpisah. Contoh menghubungkan Prisma:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export default defineEventHandler(() => {
  return prisma.user.findMany()
})

Pastikan initialization Prisma hanya sekali (misal di file global) agar tidak menimbulkan koneksi berlebih saat hot reload. Anda juga bisa menghubungkan service lain seperti API internal, Redis, atau message queue dengan pola serupa.

Contoh Endpoint CRUD Sederhana

Berikut struktur API CRUD untuk entitas task:

  • GET /api/tasks – Fetch semua task
  • POST /api/tasks – Buat task baru
  • PUT /api/tasks/[id] – Update task
  • DELETE /api/tasks/[id] – Hapus task

Contoh implentasi request POST:

import { defineEventHandler, sendError } from 'h3'

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  if (!body.title) {
    return sendError(event, createError({ statusCode: 400, statusMessage: 'Title wajib diisi' }))
  }
  const task = await prisma.task.create({
    data: {
      title: body.title,
      done: false
    }
  })
  return task
})

Gunakan modular logic agar handler tetap bersih, misal memisahkan service layer atau repository untuk operasi database.

Error Handling dan Logging Dasar

Gunakan createError atau sendError dari h3 untuk menormalkan response error:

import { defineEventHandler, createError } from 'h3'

export default defineEventHandler(() => {
  try {
    // operasi yang mungkin gagal
  } catch (err) {
    throw createError({ statusCode: 500, statusMessage: err.message })
  }
})

Untuk logging, gunakan console atau integrasi logger (misal winston). Contoh sederhana:

console.info('Request diterima', { path: event.node.req.url })
console.error('Database error', err)

Selalu sertakan informasi seperti method, path, dan status saat log error. Untuk produksi, arahkan log ke service observabilitas.

Kapan Memilih Server API Nuxt vs Backend Terpisah

Server API Nuxt ideal jika:

  • Aplikasi Anda membutuhkan API sederhana untuk CRUD internal atau data caching.
  • Anda menginginkan deployment tunggal tanpa sinkronisasi versi frontend/backend.
  • Data access terbatas dan latensi rendah menjadi prioritas.

Sementara backend terpisah lebih tepat jika:

  • Ada kebutuhan otorisasi kompleks, microservices, atau trafik tinggi.
  • Anda perlu reuse API di banyak klien (mobile, microservices).
  • Tim backend ingin lifecycle independen dari UI.

Memilih nitro server API berarti kompromi antara kemudahan integrasi dengan Nuxt terhadap skalabilitas. Untuk aplikasi kecil sampai menengah, Nitro menawarkan produktivitas tinggi. Untuk use-case enterprise dengan kebutuhan distribusi, pertimbangkan backend terpisah.

Kesimpulan

Nitro di Nuxt 3 menyederhanakan pembuatan API internal. Dengan struktur server/api, kita bisa menangani query, validasi, environment variables, koneksi database, dan logging tanpa setup terpisah. Pastikan menerapkan validating dan error handling agar endpoint tetap aman dan dapat di-debug. Gunakan server API Nuxt ketika kebutuhan backend tidak rumit dan Anda ingin kemudahan deployment; pilih backend terpisah saat skalabilitas atau reuse menjadi fokus utama.