Nuxt Nitro và Server Routes năm 2026: Full-Stack Vue và API Endpoints

Làm chủ Nuxt Nitro server routes để xây dựng ứng dụng Vue full-stack. Hướng dẫn chi tiết về API endpoints, middleware, tích hợp database và triển khai production với Nuxt 4.

Sơ đồ kiến trúc Nuxt Nitro server routes hiển thị luồng API endpoints và middleware

Nuxt Nitro biến đổi các ứng dụng Vue thành nền tảng full-stack mạnh mẽ bằng cách cung cấp engine server đa năng xử lý API routes, middleware và logic server-side song song với các component frontend. Với Nuxt 4.3 và Nitro v3 sắp ra mắt, việc xây dựng backend sẵn sàng cho production chưa bao giờ tích hợp tốt hơn với phát triển Vue.

Full-Stack trong Một Repository

Nitro cho phép chia sẻ TypeScript types giữa frontend và backend, loại bỏ sự khác biệt trong API contract và giảm boilerplate. Server routes nằm trong server/api/ và tự động khả dụng tại các endpoint /api/*.

Hiểu về Kiến trúc Nuxt Nitro Server Engine

Nitro đóng vai trò là runtime server JavaScript đa năng cung cấp sức mạnh cho thư mục server của Nuxt. Khác với server Node.js truyền thống, Nitro biên dịch thành các bundle được tối ưu hóa cho từng platform và có thể triển khai liền mạch tới Vercel, Netlify, Cloudflare Workers và môi trường Node.js tiêu chuẩn mà không cần thay đổi code.

Kiến trúc phân tách các concerns thông qua các quy ước thư mục:

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 quét bốn thư mục: server/api cho API routes với prefix /api, server/routes cho routes không có prefix, server/middleware cho request interceptors, và server/utils cho các server utilities dùng chung. Các file nằm ngoài những thư mục này sẽ không hiển thị với server runtime.

Tạo RESTful API Endpoints với defineEventHandler

Server routes tuân theo quy ước file-based routing trong đó tên file được ánh xạ trực tiếp tới URL paths. Hậu tố HTTP method kiểm soát request nào mỗi handler chấp nhận.

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 áp dụng quy ước đặt tên Web API để chuẩn bị cho Nitro v3. Property statusCode trở thành status, và statusMessage trở thành statusText. Mặc dù các property cũ vẫn hoạt động, việc chuyển sang đặt tên mới đảm bảo tương thích với Nuxt 5.

Xử lý POST requests với validation 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 cho Authentication và Logging

Các file middleware được thực thi trước route handlers, cho phép các cross-cutting concerns như authentication, logging và biến đổi request. Khác với route handlers, middleware không trả về response trực tiếp.

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'
    })
  }
})

Đối tượng event.context tồn tại xuyên suốt middleware và handlers trong một request duy nhất, cung cấp cách type-safe để chia sẻ dữ liệu. Định nghĩa context types trong file khai báo:

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

Để chuẩn bị phỏng vấn về các khái niệm Nuxt server, hãy xem module câu hỏi phỏng vấn Nuxt Server Routes.

Sẵn sàng chinh phục phỏng vấn Vue.js / Nuxt.js?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Tích hợp Database với Prisma và Server Utils

Server utilities trong server/utils/ tự động import xuyên suốt toàn bộ server code, giúp database clients và logic dùng chung có thể truy cập mà không cần import rõ ràng.

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
}

Với database client có sẵn toàn cục, route handlers có thể truy cập trực tiếp:

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 cho Runtime Initialization

Server plugins trong server/plugins/ được thực thi một lần khi server khởi động, lý tưởng để khởi tạo kết nối, lập lịch tasks, hoặc thiết lập 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')
  })
})

Hệ thống plugin tích hợp với Nitro hooks để quản lý lifecycle. Hooks được kích hoạt tại các điểm cụ thể: request trước khi xử lý, beforeResponse trước khi gửi, và close trong khi shutdown.

Gọi API Type-Safe từ Vue Components

Utility $fetch của Nuxt cung cấp type inference từ server routes khi sử dụng alias ~/server. Kết hợp với composable useFetch, các component sử dụng APIs với hỗ trợ TypeScript đầy đủ.

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>

Chiến lược Caching cho Server Routes

Nuxt 4.3 giới thiệu các điều khiển caching nâng cao thông qua routeRules trong nuxt.config.ts. Server routes được hưởng lợi từ cùng một cơ sở hạ tầng caching như 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 }
  }
})

Để invalidate cache động, sử dụng 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'
  }
})

Xử lý Error và Định dạng Response

Các error response nhất quán cải thiện khả năng sử dụng API. Tạo một utility error handler dùng chung:

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'
  })
}

Để tìm hiểu toàn diện về các pattern Vue.js và state management, hãy khám phá bài viết so sánh Pinia vs Vuex.

Checklist Production

Trước khi triển khai Nitro servers: validate environment variables với Zod schemas, implement rate limiting middleware, thêm request logging để debugging, và cấu hình CORS headers cho truy cập API cross-origin.

Triển khai Đa Nền tảng

Hệ thống preset của Nitro tạo ra các build được tối ưu hóa cho từng 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']
    }
  }
})

Đối với triển khai Node.js, output bao gồm một server standalone:

bash
# Build for production
npm run build

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

Nuxt 3 đạt end-of-life vào ngày 31 tháng 7 năm 2026. Các dự án nên migrate sang Nuxt 4 sử dụng hướng dẫn migration chính thức. Nuxt 5 với Nitro v3 sẽ theo sau ngay sau đó.

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Kết luận

  • Server routes trong server/api/ tự động ánh xạ tới các endpoint /api/* với hậu tố HTTP method (.get.ts, .post.ts) kiểm soát loại request
  • Middleware trong server/middleware/ xử lý authentication, logging và biến đổi request trước khi routes thực thi
  • Đối tượng event.context chia sẻ dữ liệu có type giữa middleware và handlers trong một request
  • Server utils trong server/utils/ tự động import xuyên suốt server code, lý tưởng cho database clients và logic dùng chung
  • Nuxt 4 áp dụng đặt tên Web API (status/statusText) để chuẩn bị cho Nitro v3
  • defineCachedEventHandler cung cấp caching cấp route với các điều khiển invalidation
  • Preset Nitro cho phép triển khai không cần cấu hình tới Vercel, Netlify, Cloudflare và server Node.js

Thẻ

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

Chia sẻ

Bài viết liên quan