Nitro & Modules

Preview — 3 of 10 questions

What is Nitro and how does it differ from a traditional Node.js/Express server?

javascript
// nuxt.config.ts — switch deployment target by changing preset
export default defineNuxtConfig({
  nitro: {
    // Deployment presets — same code deploys everywhere
    preset: 'node-server',      // Traditional Node.js server
    // preset: 'vercel',        // Vercel serverless functions
    // preset: 'vercel-edge',   // Vercel Edge Functions (V8 isolates)
    // preset: 'cloudflare',    // Cloudflare Workers (V8 isolates, no Node.js)
    // preset: 'aws-lambda',    // AWS Lambda
    // preset: 'netlify',       // Netlify Functions
    // preset: 'bun',           // Bun runtime

    // Storage (server-side KV store)
    storage: {
      redis: {
        driver: 'redis',
        url: process.env.REDIS_URL
      }
    },

    // Route caching via CDN Cache-Control headers
    routeRules: {
      '/api/products': { cache: { maxAge: 60 } },
      '/static/**': { headers: { 'cache-control': 'max-age=31536000' } }
    }
  }
})

// server/api/example.ts — same code runs on ALL platforms
export default defineEventHandler(async (event) => {
  // useStorage — abstracted over Redis, memory, filesystem, or KV
  const storage = useStorage('redis')
  const cached = await storage.getItem('my-key')

  return { cached }
})
ANitro is Nuxt's CSS preprocessing engine — it replaces PostCSS
BNitro is the name for Nuxt's module system — replacing the @nuxtjs package namespace
CNitro is a static site generator that replaces Vite in Nuxt 3
DNitro is Nuxt's server engine that compiles server code into platform-agnostic output deployable to Node.js, Vercel, Cloudflare Workers, AWS Lambda, and other targets without code changes

How do you implement file uploads in Nitro server routes?

javascript
// server/api/upload.post.ts
import { writeFile } from 'fs/promises'
import { join } from 'path'

export default defineEventHandler(async (event) => {
  // Parse multipart form data
  const files = await readMultipartFormData(event)

  if (!files || files.length === 0) {
    throw createError({ statusCode: 400, message: 'No file uploaded' })
  }

  const processedFiles = []

  for (const file of files) {
    if (file.filename && file.data) {
      // Validate file type
      const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
      if (!allowedTypes.includes(file.type ?? '')) {
        throw createError({ statusCode: 400, message: 'Invalid file type' })
      }

      // Validate file size (5MB max)
      if (file.data.length > 5 * 1024 * 1024) {
        throw createError({ statusCode: 400, message: 'File too large' })
      }

      // For Cloudflare R2 / S3 upload:
      const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')
      const s3 = new S3Client({ ... })
      const key = `uploads/${Date.now()}-${file.filename}`

      await s3.send(new PutObjectCommand({
        Bucket: useRuntimeConfig().r2Bucket,
        Key: key,
        Body: file.data,
        ContentType: file.type
      }))

      processedFiles.push({ filename: file.filename, url: `${cdn}/${key}` })
    }
  }

  return { files: processedFiles }
})
AUse readMultipartFormData(event) from H3 (Nitro's HTTP library) to parse multipart form data
BUse express.multer() middleware — Nitro is compatible with Express middleware
CFile uploads must go directly to cloud storage — Nitro cannot process binary data
DUse the @nuxtjs/upload module — file uploads are not supported natively

What is the correct structure for a programmatic Nuxt module?

javascript
// modules/my-feature/index.ts
import { defineNuxtModule, addPlugin, addComponent, addImports,
         addServerPlugin, createResolver, installModule } from '@nuxt/kit'

export interface ModuleOptions {
  apiKey: string
  debug?: boolean
  features?: ('analytics' | 'tracking' | 'ab-testing')[]
}

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: 'my-feature',
    configKey: 'myFeature',
    compatibility: { nuxt: '>=3.0.0' }
  },

  defaults: {
    debug: false,
    features: ['analytics']
  },

  async setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    // 1. Add a client-side plugin
    addPlugin({
      src: resolve('./runtime/plugin.client.ts'),
      mode: 'client'
    })

    // 2. Auto-import composables from runtime directory
    addImports([
      { name: 'useMyFeature', from: resolve('./runtime/composables/useMyFeature') },
      { name: 'useTracking', from: resolve('./runtime/composables/useTracking') }
    ])

    // 3. Auto-register components
    addComponent({
      name: 'FeatureWidget',
      filePath: resolve('./runtime/components/FeatureWidget.vue')
    })

    // 4. Add server plugin (Nitro)
    addServerPlugin(resolve('./runtime/server/plugin.ts'))

    // 5. Inject runtime config
    nuxt.options.runtimeConfig.myFeature = { apiKey: options.apiKey }
    nuxt.options.runtimeConfig.public.myFeatureDebug = options.debug

    // 6. Install other modules as dependencies
    if (options.features?.includes('analytics')) {
      await installModule('@nuxtjs/partytown')
    }

    // 7. Hook into Nuxt build lifecycle
    nuxt.hook('build:done', () => {
      console.log('My feature module: Build complete')
    })
  }
})
AExport a function using defineNuxtModule that receives the module options and Nuxt context, and uses Nuxt kit utilities to add components, composables, plugins, and server routes
BExport a Vue component — Nuxt automatically registers it as a module
CUse module.exports = { setup(config) { ... } } — standard CommonJS module format
DCreate a .nuxtmodule configuration file in the project root

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.