Nuxt Nitro dan Server Routes di 2026: Full-Stack Vue dan API Endpoints

Pelajari cara menguasai Nuxt Nitro server routes untuk membangun aplikasi Vue full-stack. Tutorial lengkap API endpoints, middleware, integrasi database, dan deployment production dengan Nuxt 4.

Diagram arsitektur Nuxt Nitro server routes menunjukkan alur API endpoints dan middleware

Nuxt Nitro mengubah aplikasi Vue menjadi platform full-stack yang powerful dengan menyediakan engine server universal yang menangani API routes, middleware, dan logika server-side bersamaan dengan komponen frontend. Dengan Nuxt 4.3 dan Nitro v3 yang akan datang, membangun backend siap-production tidak pernah semudah ini terintegrasi dengan pengembangan Vue.

Full-Stack dalam Satu Repository

Nitro memungkinkan berbagi tipe TypeScript antara frontend dan backend, menghilangkan perbedaan kontrak API dan mengurangi boilerplate. Server routes berada di server/api/ dan otomatis tersedia di endpoint /api/*.

Memahami Arsitektur Nuxt Nitro Server Engine

Nitro berfungsi sebagai runtime server JavaScript universal yang menggerakkan direktori server Nuxt. Berbeda dengan server Node.js tradisional, Nitro mengkompilasi menjadi bundle yang dioptimalkan untuk platform dan dapat di-deploy dengan mulus ke Vercel, Netlify, Cloudflare Workers, dan lingkungan Node.js standar tanpa perubahan kode.

Arsitekturnya memisahkan concerns melalui konvensi direktori:

server/api/users.get.tstypescript
export default defineEventHandler(async (event) => {
  // Nitro auto-serializes return values to JSON
  // HTTP method suffix (.get.ts) restricts to GET requests
  const users = await getUsersFromDatabase()
  return { users, count: users.length }
})

Nitro memindai empat direktori: server/api untuk API routes dengan prefix /api, server/routes untuk routes tanpa prefix, server/middleware untuk request interceptors, dan server/utils untuk shared server utilities. File di luar direktori ini tetap tidak terlihat oleh server runtime.

Membuat RESTful API Endpoints dengan defineEventHandler

Server routes mengikuti konvensi file-based routing dimana nama file langsung dipetakan ke URL paths. Suffix HTTP method mengontrol request mana yang diterima setiap handler.

server/api/products/[id].get.tstypescript
// Handles GET /api/products/123
export default defineEventHandler(async (event) => {
  // Extract route parameter from URL
  const id = getRouterParam(event, 'id')
  
  if (!id) {
    throw createError({
      status: 400, // Nuxt 4 uses Web API naming (not statusCode)
      statusText: 'Product ID required' // Not statusMessage
    })
  }
  
  const product = await fetchProduct(id)
  
  if (!product) {
    throw createError({
      status: 404,
      statusText: 'Product not found'
    })
  }
  
  return product
})

Nuxt 4 mengadopsi konvensi penamaan Web API sebagai persiapan untuk Nitro v3. Property statusCode menjadi status, dan statusMessage menjadi statusText. Meskipun property lama masih berfungsi, migrasi ke penamaan baru memastikan kompatibilitas dengan Nuxt 5.

Menangani POST requests dengan validasi body:

server/api/products/index.post.tstypescript
// Handles POST /api/products
import { z } from 'zod'

const ProductSchema = z.object({
  name: z.string().min(1).max(200),
  price: z.number().positive(),
  category: z.enum(['electronics', 'clothing', 'food'])
})

export default defineEventHandler(async (event) => {
  // readBody automatically parses JSON
  const body = await readBody(event)
  
  // Validate with Zod schema
  const result = ProductSchema.safeParse(body)
  
  if (!result.success) {
    throw createError({
      status: 422,
      statusText: 'Validation failed',
      data: result.error.flatten()
    })
  }
  
  const product = await createProduct(result.data)
  
  // Set 201 Created status for resource creation
  setResponseStatus(event, 201)
  return product
})

Server Middleware untuk Autentikasi dan Logging

File middleware dieksekusi sebelum route handlers, memungkinkan cross-cutting concerns seperti autentikasi, logging, dan transformasi request. Berbeda dengan route handlers, middleware tidak mengembalikan response secara langsung.

server/middleware/auth.tstypescript
export default defineEventHandler(async (event) => {
  // Skip auth for public routes
  const publicRoutes = ['/api/health', '/api/auth/login']
  if (publicRoutes.includes(event.path)) {
    return // Continue to next handler
  }
  
  const authHeader = getHeader(event, 'authorization')
  
  if (!authHeader?.startsWith('Bearer ')) {
    throw createError({
      status: 401,
      statusText: 'Authentication required'
    })
  }
  
  const token = authHeader.slice(7)
  
  try {
    // Verify JWT and attach user to event context
    const user = await verifyToken(token)
    event.context.user = user
  } catch {
    throw createError({
      status: 401,
      statusText: 'Invalid or expired token'
    })
  }
})

Objek event.context bertahan di seluruh middleware dan handlers dalam satu request, menyediakan cara type-safe untuk berbagi data. Definisikan tipe context dalam file deklarasi:

server/types/context.d.tstypescript
declare module 'h3' {
  interface H3EventContext {
    user?: {
      id: string
      email: string
      role: 'admin' | 'user'
    }
  }
}

Untuk persiapan wawancara tentang konsep Nuxt server, tinjau modul pertanyaan wawancara Nuxt Server Routes.

Siap menguasai wawancara Vue.js / Nuxt.js Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Integrasi Database dengan Prisma dan Server Utils

Server utilities di server/utils/ auto-import di seluruh kode server, membuat database clients dan logika bersama dapat diakses tanpa import eksplisit.

server/utils/db.tstypescript
import { PrismaClient } from '@prisma/client'

// Singleton pattern prevents multiple instances in development
const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient({
  log: process.env.NODE_ENV === 'development' 
    ? ['query', 'error', 'warn'] 
    : ['error']
})

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma
}

Dengan database client tersedia secara global, route handlers dapat mengaksesnya langsung:

server/api/posts/index.get.tstypescript
export default defineEventHandler(async (event) => {
  // Query parameters for pagination
  const query = getQuery(event)
  const page = Number(query.page) || 1
  const limit = Math.min(Number(query.limit) || 10, 100)
  const skip = (page - 1) * limit
  
  // Parallel queries for data and count
  const [posts, total] = await Promise.all([
    prisma.post.findMany({
      skip,
      take: limit,
      orderBy: { createdAt: 'desc' },
      include: { author: { select: { name: true, avatar: true } } }
    }),
    prisma.post.count()
  ])
  
  return {
    posts,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit)
    }
  }
})

Server Plugins untuk Inisialisasi Runtime

Server plugins di server/plugins/ dieksekusi sekali saat server dimulai, ideal untuk menginisialisasi koneksi, menjadwalkan tasks, atau menyiapkan monitoring.

server/plugins/database.tstypescript
export default defineNitroPlugin(async (nitroApp) => {
  // Verify database connection on startup
  try {
    await prisma.$connect()
    console.log('Database connected successfully')
  } catch (error) {
    console.error('Database connection failed:', error)
    process.exit(1) // Fail fast if DB unavailable
  }
  
  // Graceful shutdown hook
  nitroApp.hooks.hook('close', async () => {
    await prisma.$disconnect()
    console.log('Database disconnected')
  })
})

Sistem plugin terintegrasi dengan Nitro hooks untuk lifecycle management. Hooks diaktifkan pada titik tertentu: request sebelum handling, beforeResponse sebelum mengirim, dan close saat shutdown.

Panggilan API Type-Safe dari Vue Components

Utility $fetch Nuxt menyediakan type inference dari server routes saat menggunakan alias ~/server. Dikombinasikan dengan composable useFetch, komponen mengkonsumsi API dengan dukungan TypeScript penuh.

vue
<script setup lang="ts">
// Inferred return type from server/api/products/[id].get.ts
const route = useRoute()
const { data: product, status, error } = await useFetch(
  `/api/products/${route.params.id}`
)

// Mutation with optimistic updates
const { execute: updateProduct, status: updateStatus } = useFetch(
  `/api/products/${route.params.id}`,
  {
    method: 'PATCH',
    immediate: false, // Don't fetch on mount
    watch: false // Don't refetch on dependency change
  }
)

async function handleUpdate(updates: Partial<Product>) {
  await updateProduct({ body: updates })
  // Refetch to sync state
  await refreshNuxtData(`product-${route.params.id}`)
}
</script>

<template>
  <div v-if="status === 'pending'">Loading...</div>
  <div v-else-if="error">{{ error.message }}</div>
  <ProductDetail v-else :product="product" @update="handleUpdate" />
</template>

Strategi Caching untuk Server Routes

Nuxt 4.3 memperkenalkan kontrol caching yang ditingkatkan melalui routeRules di nuxt.config.ts. Server routes mendapat manfaat dari infrastruktur caching yang sama seperti pages.

nuxt.config.tstypescript
export default defineNuxtConfig({
  routeRules: {
    // Cache product listings for 5 minutes
    '/api/products': { 
      cache: { 
        maxAge: 300,
        staleMaxAge: 600, // Serve stale while revalidating
        swr: true 
      } 
    },
    // No cache for user-specific data
    '/api/user/**': { cache: false },
    // Pre-render static API responses at build time
    '/api/categories': { prerender: true }
  }
})

Untuk invalidasi cache dinamis, gunakan wrapper defineCachedEventHandler:

server/api/trending.get.tstypescript
export default defineCachedEventHandler(async (event) => {
  const trending = await calculateTrendingProducts()
  return trending
}, {
  maxAge: 60 * 5, // 5 minutes
  name: 'trending-products',
  getKey: () => 'trending', // Cache key for invalidation
  shouldBypassCache: (event) => {
    // Bypass for admin users
    return event.context.user?.role === 'admin'
  }
})

Penanganan Error dan Format Response

Response error yang konsisten meningkatkan usability API. Buat utilitas error handler bersama:

server/utils/errors.tstypescript
import { H3Error } from 'h3'

export function handleDatabaseError(error: unknown): never {
  console.error('Database error:', error)
  
  // Prisma-specific error handling
  if (error instanceof Error && 'code' in error) {
    const prismaError = error as { code: string }
    
    if (prismaError.code === 'P2002') {
      throw createError({
        status: 409,
        statusText: 'Resource already exists'
      })
    }
    
    if (prismaError.code === 'P2025') {
      throw createError({
        status: 404,
        statusText: 'Resource not found'
      })
    }
  }
  
  throw createError({
    status: 500,
    statusText: 'Internal server error'
  })
}

Untuk pola Vue.js yang komprehensif dan state management, jelajahi artikel perbandingan Pinia vs Vuex.

Checklist Production

Sebelum men-deploy Nitro servers: validasi environment variables dengan skema Zod, implementasikan rate limiting middleware, tambahkan request logging untuk debugging, dan konfigurasi CORS headers untuk akses API cross-origin.

Deployment Lintas Platform

Sistem preset Nitro menghasilkan build yang dioptimalkan untuk setiap platform:

nuxt.config.tstypescript
export default defineNuxtConfig({
  nitro: {
    // Auto-detected on Vercel/Netlify, or specify manually
    preset: 'node-server', // 'vercel', 'netlify', 'cloudflare-workers'
    
    // Compress responses
    compressPublicAssets: true,
    
    // External packages not to bundle
    externals: {
      external: ['@prisma/client']
    }
  }
})

Untuk deployment Node.js, output mencakup server standalone:

bash
# Build for production
npm run build

# Start the server
node .output/server/index.mjs
Nuxt 3 End of Life

Nuxt 3 mencapai end-of-life pada 31 Juli 2026. Proyek harus bermigrasi ke Nuxt 4 menggunakan panduan migrasi resmi. Nuxt 5 dengan Nitro v3 menyusul segera setelahnya.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Kesimpulan

  • Server routes di server/api/ otomatis dipetakan ke endpoint /api/* dengan suffix HTTP method (.get.ts, .post.ts) mengontrol tipe request
  • Middleware di server/middleware/ menangani autentikasi, logging, dan transformasi request sebelum routes dieksekusi
  • Objek event.context membagikan data yang di-type antara middleware dan handlers dalam satu request
  • Server utils di server/utils/ auto-import di seluruh kode server, ideal untuk database clients dan logika bersama
  • Nuxt 4 mengadopsi penamaan Web API (status/statusText) sebagai persiapan untuk Nitro v3
  • defineCachedEventHandler menyediakan caching level route dengan kontrol invalidasi
  • Preset Nitro memungkinkan deployment tanpa konfigurasi ke Vercel, Netlify, Cloudflare, dan server Node.js

Tag

#nuxt
#nitro
#vue
#full-stack
#api
#server-routes

Bagikan

Artikel terkait