kb.erickguedes.com
Nitro: Servidor Universal para UnJS

Storage, Tasks e Plugins

Aula 3 de 4

Storage

Nitro possui um sistema de storage unificado que funciona em múltiplos ambientes.

Configuração

// nitro.config.ts
import { defineNitroConfig } from 'nitro/config';

export default defineNitroConfig({
  storage: {
    // Storage local (filesystem)
    'data': {
      driver: 'fs',
      base: './data'
    },

    // Cache em Redis
    'cache': {
      driver: 'redis',
      host: process.env.REDIS_HOST || 'localhost',
      port: 6379,
      ttl: 3600
    },

    // Cloudflare KV (quando deploy no Cloudflare)
    'kv': {
      driver: 'cloudflare-kv-binding',
      binding: 'MY_KV'
    }
  }
});

Uso do Storage

// server/api/data.ts
export default defineEventHandler(async (event) => {
  const storage = useStorage('data');

  // Escrever
  await storage.setItem('users:1', {
    name: 'Alice',
    email: '[email protected]'
  });

  await storage.setItem('visits', 0, { ttl: 3600 });

  // Ler
  const user = await storage.getItem('users:1');
  const visits = await storage.getItem('visits');

  // Listar
  const keys = await storage.getKeys('users:');

  // Remover
  await storage.removeItem('users:1');

  // Limpar
  await storage.clear();

  return { user };
});

Storage Filesystem

// server/api/notes.ts
export default defineEventHandler(async (event) => {
  const storage = useStorage('data');

  switch (event.method) {
    case 'GET': {
      const notes = await storage.getKeys('notes:');
      const data = await Promise.all(
        notes.map(async (key) => ({
          id: key.split(':')[1],
          ...(await storage.getItem(key))
        }))
      );
      return data;
    }

    case 'POST': {
      const body = await readBody(event);
      const id = Date.now().toString();
      await storage.setItem(`notes:${id}`, {
        title: body.title,
        content: body.content,
        createdAt: new Date().toISOString()
      });
      return { id, ...body };
    }
  }
});

Storage no Filesystem

# Estrutura criada em ./data/
data/
├── notes:1700000000000.json
├── notes:1700000000001.json
└── users:1.json

Scheduled Tasks (Cron)

Nitro suporta tarefas agendadas que executam em intervalos definidos.

// server/tasks/cleanup.ts
export default defineTask({
  meta: {
    name: 'cleanup',
    description: 'Limpa dados antigos'
  },
  async run({ payload }) {
    console.log('Running cleanup task...');
    const storage = useStorage('data');
    const keys = await storage.getKeys('temp:');

    for (const key of keys) {
      const item = await storage.getItem(key);
      if (item && item.expiresAt < Date.now()) {
        await storage.removeItem(key);
      }
    }

    return { result: `Cleaned ${keys.length} items` };
  }
});
// server/tasks/sync.ts
export default defineTask({
  meta: {
    name: 'sync-external',
    description: 'Sincroniza dados com API externa'
  },
  async run({ payload }) {
    const response = await fetch('https://api.externa.com/data');
    const data = await response.json();

    const storage = useStorage('data');
    await storage.setItem('synced-data', data);

    return { result: 'Sync completed', records: data.length };
  }
});

Configuração de Cron

// nitro.config.ts
export default defineNitroConfig({
  scheduledTasks: {
    // Executa a cada 1 hora
    'cleanup': '0 * * * *',

    // Executa todo dia às 3:00 AM
    'sync-external': '0 3 * * *',

    // A cada 5 minutos
    'health-check': '*/5 * * * *'
  }
});

Executar Tarefa Manualmente

// server/api/tasks/[name].post.ts
export default defineEventHandler(async (event) => {
  const name = getRouterParam(event, 'name');

  if (!name) {
    throw createError({ statusCode: 400, message: 'Task name is required' });
  }

  const result = await runTask(name);
  return result;
});
curl -X POST http://localhost:3000/api/tasks/cleanup

Server Plugins

Plugins permitem executar código na inicialização do servidor e adicionar hooks.

// server/plugins/database.ts
export default defineNitroPlugin(async (nitroApp) => {
  console.log('Inicializando conexão com banco...');

  // Conectar ao banco
  const db = await connectToDatabase();

  // Tornar disponível globalmente
  nitroApp.db = db;

  // Hook de requisição
  nitroApp.hooks.hook('request', (event) => {
    event.context.db = db;
  });

  // Hook de erro
  nitroApp.hooks.hook('error', (error, { event }) => {
    console.error('Erro na aplicação:', error);
  });
});
// server/plugins/security.ts
export default defineNitroPlugin((nitroApp) => {
  // Adicionar headers de segurança globalmente
  nitroApp.hooks.hook('render:response', (response, { event }) => {
    response.headers = {
      ...response.headers,
      'X-Frame-Options': 'DENY',
      'X-Content-Type-Options': 'nosniff',
      'Referrer-Policy': 'strict-origin-when-cross-origin'
    };
  });
});

Hooks do Nitro

// server/plugins/hooks.ts
export default defineNitroPlugin((nitroApp) => {
  // Disparado antes de cada requisição
  nitroApp.hooks.hook('request', (event) => {
    event.context.startTime = Date.now();
  });

  // Disparado após response ser enviada
  nitroApp.hooks.hook('afterResponse', (event, { response }) => {
    const duration = Date.now() - event.context.startTime;
    console.log(`${event.method} ${event.path} - ${duration}ms`);
  });

  // Disparado quando um erro ocorre
  nitroApp.hooks.hook('error', (error, { event }) => {
    // Enviar para Sentry/Datadog
    console.error(`Error in ${event.path}:`, error);
  });

  // Disparado antes de renderizar HTML
  nitroApp.hooks.hook('render:response', (response, { event }) => {
    // Modificar response antes de enviar
  });
});

Runtime Config

// nitro.config.ts
export default defineNitroConfig({
  runtimeConfig: {
    appVersion: '1.0.0',
    public: {
      siteUrl: process.env.SITE_URL || 'http://localhost:3000'
    },
    private: {
      apiKey: process.env.API_KEY,
      databaseUrl: process.env.DATABASE_URL
    }
  }
});
// server/api/config.ts
export default defineEventHandler(async () => {
  const config = useRuntimeConfig(event);

  return {
    version: config.appVersion,
    siteUrl: config.public.siteUrl,
    // Não expor config.private!
  };
});

Lab: Exercício - Sistema de Cache e Tasks

// nitro.config.ts
export default defineNitroConfig({
  storage: {
    'cache': {
      driver: 'fs',
      base: './cache'
    }
  },
  scheduledTasks: {
    'clean-cache': '0 */6 * * *' // a cada 6 horas
  },
  runtimeConfig: {
    cacheTtl: 300
  }
});
// server/tasks/clean-cache.ts
export default defineTask({
  meta: { name: 'clean-cache', description: 'Limpa cache expirado' },
  async run() {
    const storage = useStorage('cache');
    const keys = await storage.getKeys('');
    let removed = 0;

    for (const key of keys) {
      const item = await storage.getItem(key);
      if (item && item.ttl && item.ttl < Date.now()) {
        await storage.removeItem(key);
        removed++;
      }
    }

    return { result: `Removed ${removed} expired cache entries` };
  }
});
# Executar task manualmente
curl -X POST http://localhost:3000/api/tasks/clean-cache

Storage do Nitro é unificado e portável entre ambientes (fs, redis, cloudflare KV). Scheduled tasks com cron automatizam operações. Plugins permitem hooks globais. Runtime config separa config pública e privada.