Nitro Server Engine
Charlie needs to add a caching layer to MegaShop—frequent database queries on the page listing millions of products are too slow. Bob discovered that storage methods differ across deployment environments (Node, Docker, Vercel). The Nitro engine provides a unified storage abstraction layer and caching API, allowing a single codebase to work across all environments.
1. What You'll Learn
- Nitro Architecture: Rollup Build + H3 Routing + Runtime Decoupling
- Storage layer: useStorage() + multi-driver support for KV, Redis, FileSystem, and OSS
- Caching API: cachedEventHandler / defineCachedFunction
- Event Hooks: nitro.hooks Lifecycle
- MegaShop Product Caching + Inventory Updates + Deployment with Multiple Presets
2. A True Story of an Architect
(1) Pain Point: Queries on millions of products are overwhelming the database
The MegaShop product detail page performs 2,000 database queries per second with a response time of 500 ms. Bob added a Redis cache, but the code is tightly coupled with the deployment environment—it uses file-based caching locally and Redis in production, requiring two separate sets of code changes.
(2) Solutions for the Nitro Storage Abstraction Layer
Nitro's useStorage() unified storage interface lets you switch drivers without changing your code:
// Same code, different driver based on preset
const storage = useStorage('products')
await storage.setItem('product:123', productData)
(3) Benefits: Unified interface + 5 ms response time
The storage layer code is standardized: memory/fs is used for local development, and Redis/KV is used in production. When a product cache hit occurs, the response time is 5 ms, and database queries are reduced by 95%.
3. The Nitro Architecture
(1) An Overview of the Nitro Architecture
graph TB
A[Nitro Engine] --> B[Rollup Build]
A --> C[H3 HTTP Framework]
A --> D[Storage Abstraction]
A --> E[Cache System]
A --> F[Hook System]
A --> G[Multi-Preset Deploy]
B --> B1[Server bundle]
B --> B2[Tree-shaking unused code]
C --> C1[defineEventHandler]
C --> C2[Router / Middleware]
D --> D1[Memory Driver]
D --> D2[FileSystem Driver]
D --> D3[Redis Driver]
D --> D4[Cloud KV Driver]
E --> E1[cachedEventHandler]
E --> E2[defineCachedFunction]
G --> G1[Node Server]
G --> G2[Vercel / Cloudflare]
G --> G3[Docker / Lambda]
(2) Comparison of Nitro Presets
| Presets | Runtime Environment | Storage Driver | Deployment Method |
|---|---|---|---|
| node-server | Node.js | fs/memory/redis | PM2/Docker |
| Vercel | Vercel Serverless | Vercel KV | git push |
| vercel-edge | Vercel Edge | Edge KV | git push |
| cloudflare-pages | Cloudflare Workers | KV/R2 | wrangler deploy |
| netlify | Netlify Functions | Netlify Blobs | git push |
| bun | Bun runtime | fs/memory | bun run |
(1) ▶ Example: Configuring a Nitro Preset
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
compressPublicAssets: true,
storage: {
// Development: memory driver
cache: { driver: 'memory' },
// Production: configured via environment
products: { driver: process.env.STORAGE_DRIVER || 'memory' }
}
}
})
Output:
// Execution Successful
4. Storage Abstraction Layer
(1) useStorage API
| Method | Description | Example |
|---|---|---|
| setItem(key, value) | Write | await storage.setItem('product:1', data) |
| getItem(key) | Read | await storage.getItem('product:1') |
| removeItem(key) | Delete | await storage.removeItem('product:1') |
| getKeys(base) | List keys | await storage.getKeys('product:') |
| hasItem(key) | Exists | await storage.hasItem('product:1') |
| clear(base) | Clear | await storage.clear('product:') |
(1) ▶ Example: Product Cache Storage
// server/api/products/[id].get.ts - With storage cache
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const storage = useStorage('products')
// Try cache first
const cached = await storage.getItem(`product:${id}`)
if (cached) return cached
// Cache miss: fetch from database
const product = await fetchProductFromDB(Number(id))
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
// Store in cache with TTL (1 hour)
await storage.setItem(`product:${id}`, product, { ttl: 3600 })
return product
})
Output:
// Execution Successful
(2) ▶ Example: Redis Storage Configuration
// nuxt.config.ts - Production Redis storage
export default defineNuxtConfig({
nitro: {
storage: {
products: {
driver: 'redis',
url: process.env.REDIS_URL || 'redis://localhost:6379',
prefix: 'megashop:products:'
},
cache: {
driver: 'redis',
url: process.env.REDIS_URL || 'redis://localhost:6379',
prefix: 'megashop:cache:'
}
}
}
})
Output:
// Execution Successful
(2) Comparison of Storage Drivers
| Drive | Speed | Durability | Distributed | Use Cases |
|---|---|---|---|---|
| memory | ⚡⚡⚡ | ❌ Lost on reboot | ❌ | Development/Testing |
| fs | ⚡⚡ | ✅ Local | ❌ | Standalone deployment |
| redis | ⚡⚡ | ✅ | ✅ | Production Cluster |
| cloudflare-kv | ⚡ | ✅ | ✅ | Cloudflare |
| vercel-kv | ⚡ | ✅ | ✅ | Vercel |
5. Caching API
(1) ▶ Example: cachedEventHandler
// server/api/products/featured.get.ts
export default cachedEventHandler(
async () => {
// This handler result is cached
const products = await $fetch('/api/internal/products/featured')
return products
},
{
maxAge: 60 * 60, // Cache for 1 hour
swr: true, // Serve stale while revalidating
staleMaxAge: 60 * 60 * 4, // Stale valid for 4 hours
getKey: () => 'featured-products',
varies: ['Accept-Language'] // Per-language cache
}
)
Output:
// Execution Successful
(2) ▶ Example: defineCachedFunction
// server/utils/cachedProduct.ts
export const getCachedProduct = defineCachedFunction(
async (id: number) => {
return await fetchProductFromDB(id)
},
{
maxAge: 60 * 60,
swr: true,
getKey: (id) => `product:${id}`,
name: 'cachedProduct'
}
)
// Usage in API handler
// server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
return await getCachedProduct(id)
})
Output:
// Execution Successful
6. Event Hooks
(1) Nitro Lifecycle Hooks
| Hook | Triggering Condition | Purpose |
|---|---|---|
| close | Stop Service | Release Resources |
| error | Unhandled error | Error reporting |
| request | Request Start | Log/Authentication | | response | Response Sent | Performance Statistics | | beforeResponse | Before Response | Modify Response |
(1) ▶ Example: Inventory Update Event Hook
// server/plugins/stock.ts
export default defineNitroPlugin((nitroApp) => {
// Listen for stock update events
nitroApp.hooks.hook('request', async (event) => {
const url = getRequestURL(event)
if (url.pathname === '/api/orders' && getMethod(event) === 'POST') {
// Before order: check stock availability
const body = await readBody(event)
const outOfStock = await checkStockAvailability(body.items)
if (outOfStock.length > 0) {
throw createError({
statusCode: 400,
message: `Items out of stock: ${outOfStock.join(', ')}`
})
}
}
})
// After order: invalidate product cache
nitroApp.hooks.hook('afterResponse', async (event) => {
const url = getRequestURL(event)
if (url.pathname === '/api/orders' && getMethod(event) === 'POST') {
const body = await readBody(event)
const storage = useStorage('products')
// Invalidate cached products
for (const item of body.items) {
await storage.removeItem(`product:${item.productId}`)
}
}
})
})
Output:
// Execution Successful
7. Comprehensive Example: MegaShop Product Caching System
// nuxt.config.ts - Production Nitro config
export default defineNuxtConfig({
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
compressPublicAssets: true,
storage: {
products: {
driver: process.env.NODE_ENV === 'production' ? 'redis' : 'memory',
url: process.env.REDIS_URL
}
},
cache: {
pages: ['/products/', '/categories/']
}
}
})
// server/api/products/[id].get.ts - Full caching strategy
export default cachedEventHandler(
async (event) => {
const id = Number(getRouterParam(event, 'id'))
const product = await fetchProductFromDB(id)
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
return product
},
{
maxAge: 60 * 60, // 1 hour cache
swr: true, // Stale while revalidate
staleMaxAge: 60 * 60 * 24, // Stale valid 24 hours
getKey: (event) => `product:${getRouterParam(event, 'id')}`,
varies: ['Accept-Language']
}
)
// server/api/admin/invalidate-cache.post.ts - Manual cache invalidation
export default defineEventHandler(async (event) => {
const { productId, scope } = await readBody(event)
const storage = useStorage('products')
if (scope === 'all') {
// Invalidate all product caches
const keys = await storage.getKeys()
for (const key of keys) {
await storage.removeItem(key)
}
return { message: 'All caches invalidated' }
}
if (productId) {
await storage.removeItem(`product:${productId}`)
return { message: `Cache invalidated for product ${productId}` }
}
throw createError({ statusCode: 400, message: 'Specify productId or scope=all' }
})
❓ FAQ
useStorage memory driver?nuxt.config.ts—no code changes are required.cachedEventHandler and routeRules in SWR?routeRules in SWR caches the entire page's HTML, while cachedEventHandler caches API response data. Use routeRules at the page level and cachedEventHandler at the API level.plugins/) run within the Vue application and handle components, Composables, and third-party SDKs.useStorage to provide a unified abstraction across multiple environments.📖 Summary
- Nitro is the Nuxt 3 server-side engine: build + H3 routing + storage + caching + multiple deployment presets
- useStorage() provides a unified storage interface; use memory for development and Redis/KV for production, with no code changes required
cachedEventHandlercaches API responses;defineCachedFunctioncaches function results- Nitro hooks monitor the request/response lifecycle to implement authentication, logging, and cache invalidation
- MegaShop uses Nitro Storage for product caching + event hooks to automatically invalidate the cache when inventory is updated
📝 Exercises
- Basic Exercise (Difficulty: ⭐): Configure the useStorage memory driver to implement simple read and write operations for product data caching
- Advanced Exercise (Difficulty: ⭐⭐): Use
cachedEventHandlerto implement caching for the product list API, and verify that the response is faster when the cache is hit. - Challenge (Difficulty: ⭐⭐⭐): Implement a complete cache invalidation mechanism—automatically invalidate the cache for related products when an order is created; a manual API should allow clearing the cache by product ID or clearing the entire cache.
---|



