はじめに
正直なところ、サーバーサイドの開発って環境ごとにコード書き直すのが面倒だったんですよね。
Vercelにデプロイするときはこう、Cloudflare Workersだとこう、AWSだとこう...という話。同じロジックなのにプラットフォームごとにアダプタ書いたり、設定変えたりするのが地味にストレスでした。
そんな悩みを解決してくれるのがNitroです。
NitroはGitHubで約10,000スターを獲得している、次世代のサーバーツールキット。Nuxt 3のバックエンドエンジンとして開発されましたが、今では単体でも使える汎用的なサーバーフレームワークになっています。
「一度書けば、どこにでもデプロイできる」が本当に実現できているんですよ、これ。
Nitroとは
Nitroは、UnJSチームが開発するユニバーサルサーバーツールキットです。330人以上のコントリビューターによって活発に開発が続いており、3,350以上のコミットが積み重ねられています。
最大の特徴はプラットフォーム非依存。同じコードをNode.js、Bun、Deno、Cloudflare Workers、AWS Lambda、Vercel、Netlifyなど、20以上のプロバイダーにそのままデプロイできます。
Nuxt 3を使ったことがある人なら、すでにNitroの恩恵を受けています。server/api/ディレクトリにファイルを置くだけでAPIが動く、あの快適さの裏側にいるのがNitroです。
公式サイト: https://nitro.build GitHub: https://github.com/nitrojs/nitro
特徴・メリット
1. ゼロコンフィグでHMR対応
設定ファイルなしで開発を始められます。そしてホットモジュールリプレースメント(HMR)が標準で動く。
npm run dev
これだけで開発サーバーが立ち上がり、コードを変更すればすぐに反映されます。30代になって思うのは、こういう「すぐ動く」体験がどれだけ開発のモチベーションに影響するかということ。
2. 1MB未満の軽量出力
ビルド後の出力サイズが1MB未満というコンパクトさ。これ、意外と他のフレームワークにはない強みなんですよ。
さらに非同期チャンク読み込みによる高速スタートアップも実現しています。コールドスタートが気になるサーバーレス環境では、この軽さが大きなアドバンテージになります。
3. ファイルシステムベースのルーティング
server/
├── routes/
│ ├── index.ts → /
│ ├── users.ts → /users
│ └── users/
│ └── [id].ts → /users/:id
├── api/
│ └── hello.ts → /api/hello
├── utils/
│ └── db.ts → 自動インポート
└── plugins/
└── auth.ts → プラグイン
ディレクトリ構造がそのままルーティングになります。Next.jsやNuxtのApp Routerに慣れている人なら、この直感的な設計がすぐに理解できるはずです。
4. 自動インポート
server/utils/に置いたユーティリティ関数は、import文なしで使えます。
// server/utils/db.ts
export function getUser(id: string) {
return { id, name: 'User' }
}
// server/routes/users/[id].ts
// importなしで使える
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return getUser(id) // 自動でインポートされる
})
コスパ的にこれはかなりデカい。import文を書く手間が省けるだけで、開発体験がかなり向上します。
5. 20以上のデプロイプロバイダー対応
個人的にはこれが一番の推しポイントです。
- Node.js
- Bun
- Deno
- Cloudflare Workers / Pages
- AWS Lambda
- Vercel
- Netlify
- Deno Deploy
- Azure Functions
- その他多数...
同じコードをそのままデプロイできます。途中でホスティング先を変えたくなっても、コードの書き直しが不要。これは運用面で非常に助かります。
6. 組み込みキャッシュAPI
export default defineCachedEventHandler(
async (event) => {
// 重い処理
const data = await fetchHeavyData()
return data
},
{
maxAge: 60 * 60, // 1時間キャッシュ
staleMaxAge: 60 * 60 * 24 // 24時間はstaleを許容
}
)
キャッシュの設定もシンプル。パフォーマンスチューニングが手軽にできます。
インストール方法
新規プロジェクトの作成(推奨)
npx giget@latest nitro nitro-app --install
cd nitro-app
これだけで完了です。時短になりますね。
既存プロジェクトへの追加
npm install nitropack
基本的な使い方
開発サーバーの起動
npm run dev
http://localhost:3000/ でサーバーが起動します。
Hello World
// server/routes/index.ts
export default defineEventHandler(() => {
return { message: 'Hello Nitro!' }
})
defineEventHandlerでハンドラーを定義するだけ。シンプルですね。
ルーティング
// server/routes/users/index.ts
export default defineEventHandler(() => {
return [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
]
})
// server/routes/users/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return { id, name: `User ${id}` }
})
ファイル名に[param]を使えば動的ルーティングになります。
リクエストボディの取得
// server/routes/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return { created: true, user: body }
})
ファイル名に.postをつけるとPOSTメソッド専用になります。.get、.put、.deleteなども同様です。
クエリパラメータ
// server/routes/search.ts
export default defineEventHandler((event) => {
const query = getQuery(event)
// /search?q=keyword → { q: 'keyword' }
return { query }
})
ヘッダーとステータスコード
export default defineEventHandler((event) => {
setResponseStatus(event, 201)
setHeader(event, 'X-Custom-Header', 'value')
return { created: true }
})
エラーハンドリング
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({
statusCode: 400,
message: 'ID is required'
})
}
return { id }
})
実践的なユースケース
1. REST APIサーバー
// server/routes/api/todos/index.ts
let todos = [
{ id: 1, title: 'Learn Nitro', completed: false }
]
let nextId = 2
// GET /api/todos
export default defineEventHandler(() => {
return todos
})
// server/routes/api/todos/index.post.ts
// POST /api/todos
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const todo = {
id: nextId++,
title: body.title,
completed: false
}
todos.push(todo)
setResponseStatus(event, 201)
return todo
})
// server/routes/api/todos/[id].patch.ts
// PATCH /api/todos/:id
export default defineEventHandler(async (event) => {
const id = parseInt(getRouterParam(event, 'id'))
const body = await readBody(event)
const todo = todos.find(t => t.id === id)
if (!todo) {
throw createError({ statusCode: 404, message: 'Not found' })
}
Object.assign(todo, body)
return todo
})
// server/routes/api/todos/[id].delete.ts
// DELETE /api/todos/:id
export default defineEventHandler((event) => {
const id = parseInt(getRouterParam(event, 'id'))
const index = todos.findIndex(t => t.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Not found' })
}
todos.splice(index, 1)
return { success: true }
})
2. ミドルウェアの活用
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const authHeader = getHeader(event, 'Authorization')
// 認証が必要なパスをチェック
if (event.path.startsWith('/api/admin')) {
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
// トークン検証ロジック
}
})
server/middleware/に置いたファイルは、すべてのリクエストで実行されます。
3. 外部APIとの連携
// server/routes/api/weather.ts
export default defineCachedEventHandler(
async (event) => {
const query = getQuery(event)
const city = query.city || 'Tokyo'
const response = await $fetch(
`https://api.weather.example.com/current`,
{
query: { city }
}
)
return response
},
{
maxAge: 60 * 10 // 10分キャッシュ
}
)
$fetchはNitroに組み込まれているHTTPクライアント。外部APIの呼び出しも簡単です。
4. ストレージの活用
// nitro.config.ts
export default defineNitroConfig({
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
})
// server/routes/api/cache.ts
export default defineEventHandler(async (event) => {
const storage = useStorage('cache')
// 値の設定
await storage.setItem('key', { data: 'value' })
// 値の取得
const value = await storage.getItem('key')
return { value }
})
Redis、Cloudflare KV、メモリなど、複数のストレージドライバーに対応しています。
5. 本番ビルド
# ビルド
npm run build
# プレビュー
npm run preview
出力は.outputディレクトリに生成されます。Node.jsサーバーとしてそのまま起動可能です。
6. 特定プロバイダーへのデプロイ
// nitro.config.ts
export default defineNitroConfig({
preset: 'cloudflare-pages' // デプロイ先を指定
})
presetを変えるだけで、各プラットフォーム用のビルドが生成されます。
Nuxtとの関係
NitroはNuxt 3のサーバーエンジンとして開発されました。Nuxt 3を使う場合、server/ディレクトリの機能はすべてNitroが提供しています。
nuxt-project/
└── server/
├── api/ ← これはNitro
├── routes/ ← これもNitro
└── middleware/ ← これもNitro
Nuxt 3ユーザーなら、すでにNitroを使っているわけです。単体で使いたい場合は、Nitroだけをインストールして使うこともできます。
注意点
いいことばかり書いてきましたが、注意点もあります。
1. 学習リソース
ExpressやFastifyと比べると、英語・日本語ともに情報量は少なめです。ただし公式ドキュメントは充実しているので、そこを見れば大体のことはわかります。
2. フレームワークとしての位置づけ
NitroはHonoやElysiaのようなWebフレームワークとは少し位置づけが違います。どちらかというと「サーバー構築のための抽象化レイヤー」という感じ。APIのルーティングは得意ですが、複雑なミドルウェアチェーンを組みたい場合はHonoなどと組み合わせることも検討したほうがいいかもしれません。
3. Nuxtとの依存関係
単体で使う場合は問題ありませんが、NitroのバージョンはNuxtのリリースサイクルに影響を受けることがあります。
まとめ
正直なところ、「どこにでもデプロイできるサーバー」を作りたいなら、Nitroは有力な選択肢ですね。
以下の条件に当てはまるなら、試してみる価値は間違いなくあります:
- サーバーレス環境で軽量なAPIを動かしたい
- デプロイ先を柔軟に変えられるようにしたい
- ファイルベースルーティングが好き
- Nuxt 3を使っていて、サーバー部分を深く理解したい
- 設定より規約派
Nuxt 3のバックエンドとして実績を積んできた信頼性と、UnJSエコシステムの一部としての将来性。この2つが大きな安心材料です。
個人的には、Nuxt 3を使うなら自動的にNitro、単体でAPIを作るならHonoやElysia、という使い分けをしています。どれも素晴らしいツールです。
まずは公式のスターターで触ってみてください。ファイルを置くだけでルーティングが完成する体験は、QOL上がること間違いなしです。
公式サイト: https://nitro.build GitHub: https://github.com/nitrojs/nitro ドキュメント: https://nitro.build/guide
