2026年版 Nuxt NitroとServer Routes完全ガイド:フルスタックVue開発とAPIエンドポイント構築

Nuxt 4とNitro 3を使用したフルスタックVue開発の実践ガイド。Server RoutesによるタイプセーフなAPIエンドポイント構築、ミドルウェア設計、データベース統合、デプロイ戦略を詳しく解説します。

2026年版 Nuxt NitroとServer Routes完全ガイド:フルスタックVue開発とAPIエンドポイント構築

Nuxt.jsは2026年現在、フルスタックVue開発のデファクトスタンダードとして確固たる地位を築いています。その中核を担うNitroサーバーエンジンは、Server Routesを通じてフロントエンドとバックエンドを統一されたコードベースで開発することを可能にします。Nuxt 4の正式リリースとNitro 3の安定化により、TypeScriptファーストなAPI開発、エッジコンピューティング対応、そしてゼロコンフィグデプロイメントが実現しました。本記事では、Nuxt Server Routesの基礎から実践的なAPIエンドポイント構築まで、2026年の技術面接で求められる知識を体系的に解説します。

NitroとServer Routesの位置づけ

NitroはNuxtのサーバーエンジンであり、Server Routesはその上で動作するAPIエンドポイント定義の仕組みです。従来のExpress.jsやFastifyを別途セットアップする必要がなく、server/api/ディレクトリにファイルを配置するだけでAPIルートが自動生成されます。Nitro 3ではNode.js、Deno、Bun、Cloudflare Workers、Vercel Edge Functionsなど、あらゆるランタイムで同一コードが動作します。

Server Routesの基礎:ファイルベースルーティング

Nuxt Server Routesは、server/api/ディレクトリの構造がそのままAPIエンドポイントのパスにマッピングされるファイルベースルーティングを採用しています。この設計により、ルーティング設定を別途記述する必要がなく、ディレクトリ構造を見るだけでAPI全体の設計を把握できます。

server/api/users/index.get.tstypescript
export default defineEventHandler(async (event) => {
  const users = await prisma.user.findMany({
    select: {
      id: true,
      name: true,
      email: true,
      createdAt: true
    }
  })
  return users
})

ファイル名の.get.tsサフィックスはHTTPメソッドを指定します。同様に.post.ts.put.ts.delete.ts.patch.tsを使用することで、RESTful APIを直感的に構築できます。

server/api/users/index.post.tstypescript
import { z } from 'zod'

const createUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  password: z.string().min(8)
})

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const validated = createUserSchema.parse(body)

  const hashedPassword = await hashPassword(validated.password)
  
  const user = await prisma.user.create({
    data: {
      name: validated.name,
      email: validated.email,
      password: hashedPassword
    }
  })

  return {
    id: user.id,
    name: user.name,
    email: user.email
  }
})

動的パラメータは角括弧を使用してファイル名に含めます。[id].get.ts/api/users/123のようなパスにマッチし、event.context.params.idでパラメータを取得できます。

server/api/users/[id].get.tstypescript
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  
  const user = await prisma.user.findUnique({
    where: { id: parseInt(id!) },
    include: {
      posts: {
        orderBy: { createdAt: 'desc' },
        take: 10
      }
    }
  })

  if (!user) {
    throw createError({
      statusCode: 404,
      statusMessage: 'User not found'
    })
  }

  return user
})

ミドルウェアと認証の実装

Server Routesでは、server/middleware/ディレクトリに配置したファイルがすべてのリクエストに対して実行されます。認証、ロギング、レート制限などの横断的関心事を一元管理できます。

server/middleware/auth.tstypescript
export default defineEventHandler(async (event) => {
  const publicPaths = ['/api/auth/login', '/api/auth/register', '/api/health']
  const path = getRequestURL(event).pathname

  if (publicPaths.some(p => path.startsWith(p))) {
    return
  }

  const token = getHeader(event, 'authorization')?.replace('Bearer ', '')

  if (!token) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Authentication required'
    })
  }

  try {
    const payload = await verifyJWT(token)
    event.context.user = payload
  } catch {
    throw createError({
      statusCode: 401,
      statusMessage: 'Invalid token'
    })
  }
})

特定のルートにのみ適用するミドルウェアは、ルートハンドラ内で直接実行するパターンが推奨されます。Nitro 3ではミドルウェアチェーンを構築するcreateMiddlewareユーティリティも提供されています。

server/utils/middleware.tstypescript
import { H3Event } from 'h3'

export const requireAdmin = async (event: H3Event) => {
  const user = event.context.user
  
  if (!user || user.role !== 'admin') {
    throw createError({
      statusCode: 403,
      statusMessage: 'Admin access required'
    })
  }
}

// server/api/admin/users.get.ts
export default defineEventHandler(async (event) => {
  await requireAdmin(event)
  
  return await prisma.user.findMany({
    include: { _count: { select: { posts: true } } }
  })
})

Vue.js / Nuxt.jsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

データベース統合とORM連携

Nuxt Server Routesは、Prisma、Drizzle、Kysely などの主要なORMとシームレスに統合できます。Nitroのホットリロード機能により、開発中のデータベーススキーマ変更も即座に反映されます。

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

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
}

トランザクション処理も直感的に記述できます。複数のデータベース操作をアトミックに実行する必要がある場合、Prismaの$transactionメソッドを活用します。

server/api/orders/index.post.tstypescript
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const userId = event.context.user.id

  const order = await prisma.$transaction(async (tx) => {
    // Create order
    const newOrder = await tx.order.create({
      data: {
        userId,
        status: 'pending',
        totalAmount: body.totalAmount
      }
    })

    // Create order items
    await tx.orderItem.createMany({
      data: body.items.map((item: any) => ({
        orderId: newOrder.id,
        productId: item.productId,
        quantity: item.quantity,
        price: item.price
      }))
    })

    // Update inventory
    for (const item of body.items) {
      await tx.product.update({
        where: { id: item.productId },
        data: {
          stock: { decrement: item.quantity }
        }
      })
    }

    return newOrder
  })

  return order
})

エラーハンドリングとバリデーション

プロダクション品質のAPIでは、適切なエラーハンドリングとバリデーションが不可欠です。Zodとの組み合わせにより、タイプセーフなバリデーションを実現できます。

server/utils/validation.tstypescript
import { z, ZodError } from 'zod'
import { H3Event } from 'h3'

export async function validateBody<T extends z.ZodTypeAny>(
  event: H3Event,
  schema: T
): Promise<z.infer<T>> {
  const body = await readBody(event)
  
  try {
    return schema.parse(body)
  } catch (error) {
    if (error instanceof ZodError) {
      throw createError({
        statusCode: 400,
        statusMessage: 'Validation failed',
        data: {
          errors: error.errors.map(e => ({
            path: e.path.join('.'),
            message: e.message
          }))
        }
      })
    }
    throw error
  }
}

// server/api/products/index.post.ts
const productSchema = z.object({
  name: z.string().min(1).max(200),
  description: z.string().optional(),
  price: z.number().positive(),
  categoryId: z.number().int().positive()
})

export default defineEventHandler(async (event) => {
  const data = await validateBody(event, productSchema)
  
  return await prisma.product.create({ data })
})

キャッシングとパフォーマンス最適化

Nitroは組み込みのキャッシング機能を提供しており、APIレスポンスを効率的にキャッシュできます。特に読み取り頻度の高いエンドポイントでは、キャッシングによりレスポンス時間を大幅に短縮できます。

server/api/products/featured.get.tstypescript
export default defineCachedEventHandler(async () => {
  const products = await prisma.product.findMany({
    where: { featured: true },
    include: {
      category: true,
      _count: { select: { reviews: true } }
    },
    orderBy: { createdAt: 'desc' },
    take: 12
  })

  return products
}, {
  maxAge: 60 * 5, // 5 minutes
  staleMaxAge: 60 * 60, // 1 hour stale-while-revalidate
  swr: true
})

キャッシュの無効化は、関連するデータが更新された際にプログラムから実行できます。

server/api/products/[id].put.tstypescript
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  const body = await readBody(event)

  const product = await prisma.product.update({
    where: { id: parseInt(id!) },
    data: body
  })

  // Invalidate related caches
  await useStorage('cache').removeItem('nitro:handlers:products:featured')

  return product
})

WebSocketとリアルタイム通信

Nitro 3では、WebSocketサポートが大幅に強化されました。server/routes/ディレクトリにWebSocketハンドラを配置することで、リアルタイム通信を実装できます。

server/routes/_ws.tstypescript
export default defineWebSocketHandler({
  open(peer) {
    console.log('Client connected:', peer.id)
    peer.subscribe('notifications')
  },

  message(peer, message) {
    const data = JSON.parse(message.text())
    
    if (data.type === 'subscribe') {
      peer.subscribe(data.channel)
    } else if (data.type === 'broadcast') {
      peer.publish(data.channel, JSON.stringify(data.payload))
    }
  },

  close(peer) {
    console.log('Client disconnected:', peer.id)
  }
})

Vue.js / Nuxt.jsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

マルチテナントアーキテクチャ

企業向けアプリケーションでは、マルチテナント対応が求められることが多くあります。Server Routesでは、ミドルウェアとコンテキストを活用してテナント分離を実現できます。

server/middleware/tenant.tstypescript
export default defineEventHandler(async (event) => {
  const host = getRequestHost(event)
  const subdomain = host.split('.')[0]

  if (subdomain && subdomain !== 'www') {
    const tenant = await prisma.tenant.findUnique({
      where: { subdomain }
    })

    if (!tenant) {
      throw createError({
        statusCode: 404,
        statusMessage: 'Tenant not found'
      })
    }

    event.context.tenant = tenant
  }
})

// server/api/projects/index.get.ts
export default defineEventHandler(async (event) => {
  const tenantId = event.context.tenant?.id

  return await prisma.project.findMany({
    where: { tenantId },
    orderBy: { createdAt: 'desc' }
  })
})

デプロイメント戦略

Nitroの最大の強みは、同一コードをあらゆるプラットフォームにデプロイできる点です。nuxt.config.tsnitro.presetを指定するだけで、各プラットフォームに最適化されたビルドが生成されます。

nuxt.config.tstypescript
export default defineNuxtConfig({
  nitro: {
    preset: 'cloudflare-pages', // or 'vercel', 'netlify', 'node-server'
    
    // Storage configuration
    storage: {
      cache: {
        driver: 'cloudflare-kv-binding',
        binding: 'CACHE'
      }
    },

    // Prerender static pages
    prerender: {
      routes: ['/about', '/pricing', '/docs'],
      crawlLinks: true
    }
  }
})

Node.jsサーバーとしてデプロイする場合、PM2やDockerと組み合わせることで本番環境を構築できます。

dockerfile
# Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.output ./
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server/index.mjs"]

テスト戦略

Server Routesのテストは、Vitestを使用して単体テストと統合テストを記述できます。Nitroの$fetchユーティリティを活用することで、実際のHTTPリクエストをシミュレートできます。

tests/api/users.test.tstypescript
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { setup, $fetch } from '@nuxt/test-utils'

describe('Users API', async () => {
  await setup({ server: true })

  it('returns user list', async () => {
    const users = await $fetch('/api/users')
    expect(Array.isArray(users)).toBe(true)
  })

  it('creates a new user', async () => {
    const user = await $fetch('/api/users', {
      method: 'POST',
      body: {
        name: 'Test User',
        email: 'test@example.com',
        password: 'securepassword123'
      }
    })

    expect(user).toHaveProperty('id')
    expect(user.name).toBe('Test User')
  })

  it('returns 400 for invalid input', async () => {
    await expect(
      $fetch('/api/users', {
        method: 'POST',
        body: { name: '' }
      })
    ).rejects.toThrow()
  })
})

まとめ

Nuxt Server RoutesとNitroの組み合わせは、2026年のフルスタックVue開発において最も効率的なアプローチです。ファイルベースルーティングによる直感的なAPI設計、TypeScriptによるタイプセーフティ、そしてエッジコンピューティング対応のデプロイメント柔軟性により、開発者はビジネスロジックに集中できます。本記事で解説した認証パターン、データベース統合、キャッシング戦略、テスト手法を組み合わせることで、スケーラブルで保守性の高いアプリケーションを構築できます。技術面接では、これらの概念を実際のコード例とともに説明できることが求められます。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

共有

関連記事