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

Nuxt.jsは2026年現在、フルスタックVue開発のデファクトスタンダードとして確固たる地位を築いています。その中核を担うNitroサーバーエンジンは、Server Routesを通じてフロントエンドとバックエンドを統一されたコードベースで開発することを可能にします。Nuxt 4の正式リリースとNitro 3の安定化により、TypeScriptファーストなAPI開発、エッジコンピューティング対応、そしてゼロコンフィグデプロイメントが実現しました。本記事では、Nuxt Server Routesの基礎から実践的なAPIエンドポイント構築まで、2026年の技術面接で求められる知識を体系的に解説します。
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全体の設計を把握できます。
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を直感的に構築できます。
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でパラメータを取得できます。
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/ディレクトリに配置したファイルがすべてのリクエストに対して実行されます。認証、ロギング、レート制限などの横断的関心事を一元管理できます。
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ユーティリティも提供されています。
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のホットリロード機能により、開発中のデータベーススキーマ変更も即座に反映されます。
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メソッドを活用します。
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との組み合わせにより、タイプセーフなバリデーションを実現できます。
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レスポンスを効率的にキャッシュできます。特に読み取り頻度の高いエンドポイントでは、キャッシングによりレスポンス時間を大幅に短縮できます。
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
})キャッシュの無効化は、関連するデータが更新された際にプログラムから実行できます。
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ハンドラを配置することで、リアルタイム通信を実装できます。
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では、ミドルウェアとコンテキストを活用してテナント分離を実現できます。
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.tsでnitro.presetを指定するだけで、各プラットフォームに最適化されたビルドが生成されます。
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
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 /app/.output ./
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server/index.mjs"]テスト戦略
Server Routesのテストは、Vitestを使用して単体テストと統合テストを記述できます。Nitroの$fetchユーティリティを活用することで、実際のHTTPリクエストをシミュレートできます。
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によるタイプセーフティ、そしてエッジコンピューティング対応のデプロイメント柔軟性により、開発者はビジネスロジックに集中できます。本記事で解説した認証パターン、データベース統合、キャッシング戦略、テスト手法を組み合わせることで、スケーラブルで保守性の高いアプリケーションを構築できます。技術面接では、これらの概念を実際のコード例とともに説明できることが求められます。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
共有
関連記事

2026年のVue 3とTypeScript: 型安全なProps・Emits・コンポーザブル
TypeScriptで型安全なVue 3コンポーネントを習得: ジェネリックdefineProps、タプル形式のdefineEmits、型付きコンポーザブル、defineModel、InjectionKey、面接質問まで解説。

2026年のVue 3テスト入門:Vitest、Vue Test Utils、そして面接質問
2026年のVueテストを実践的に解説します。Vitestの設定、Vue Test Utilsによるコンポーネントのマウント、コンポーザブルやPiniaストアのテスト、APIのモック、カバレッジ計測、そして採用チームが実際に尋ねる面接質問までを網羅します。

2026年のVue 3パフォーマンス最前線:Vapor Mode、Alien Signalsと面接対策
Vue 3.6 Vapor Modeは仮想DOMを排除し、直接DOM操作を実現する。ベンチマーク結果、Alien Signalsリアクティビティ、移行ガイド、面接対策まで徹底解説。