kb.erickguedes.com
Nitro: Servidor Universal para UnJS

Fundamentos do Nitro e H3

Aula 1 de 4

Nitro vs Express

Nitro é um servidor universal para o ecossistema UnJS. Diferente do Express, ele é:

  • Universal: roda em Node.js, Edge, serverless e Deno
  • File-based routing: sem definição manual de rotas
  • Auto-imports: sem require/import de utilitários comuns
  • Tree-shakeable: apenas o código usado vai para o bundle
  • Type-safe: geração automática de tipos
# Criar projeto
npx giget nitro-app my-app
cd my-app
npm install
npm run dev
my-app/
├── server/
│   ├── routes/
│   │   └── index.ts        # GET /
│   └── api/
│       └── hello.ts        # GET /api/hello
├── public/
│   └── index.html
├── nitro.config.ts
└── package.json

H3 (HTTP Handler)

H3 é o motor HTTP do Nitro. Leve, universal e performático.

// server/api/hello.ts
export default defineEventHandler((event) => {
  return { message: 'Hello Nitro!' };
});

Event Handler

import { defineEventHandler, getQuery, readBody, send, setHeader } from 'h3';

// GET com query params
export default defineEventHandler(async (event) => {
  const query = getQuery(event);
  const name = query.name || 'Mundo';

  return {
    message: `Olá, ${name}!`,
    timestamp: new Date().toISOString()
  };
});

Requisições

// server/api/users/[id].ts
import { defineEventHandler, getRouterParam, getQuery, readBody } from 'h3';

// GET /api/users/123?include=posts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id');
  const query = getQuery(event);

  return {
    id: Number(id),
    include: query.include || 'none',
    method: event.method
  };
});
// server/api/users.post.ts (POST /api/users)
export default defineEventHandler(async (event) => {
  const body = await readBody(event);

  if (!body.name || !body.email) {
    // Erro com status code
    throw createError({
      statusCode: 400,
      statusMessage: 'Bad Request',
      message: 'Nome e email são obrigatórios'
    });
  }

  return {
    id: 1,
    ...body,
    createdAt: new Date().toISOString()
  };
});

Respostas

import {
  defineEventHandler,
  send,
  sendRedirect,
  setHeader,
  setResponseStatus
} from 'h3';

export default defineEventHandler(async (event) => {
  // JSON (default)
  return { data: 'objeto retornado é JSON automaticamente' };

  // Texto
  // return 'texto puro';

  // Status customizado
  setResponseStatus(event, 201);
  return { created: true };

  // Headers
  setHeader(event, 'X-Custom-Header', 'valor');
  setHeader(event, 'Content-Type', 'application/json');

  // Redirect
  // sendRedirect(event, '/novo-local', 302);

  // Enviar stream/arquivo
  // return send(event, 'conteúdo do arquivo');
});

Error Handling

// server/api/secure.ts
import { defineEventHandler, createError } from 'h3';

export default defineEventHandler(async (event) => {
  const auth = getHeader(event, 'authorization');

  if (!auth) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Unauthorized',
      message: 'Token de autenticação não fornecido'
    });
  }

  // Erro não tratado vira 500 automático
  throw new Error('Algo deu errado');

  // Erro tratado
  try {
    // operação arriscada
  } catch (err) {
    throw createError({
      statusCode: 500,
      message: 'Erro interno do servidor'
    });
  }
});

File-based Routing

Nitro mapeia arquivos em server/ diretamente para rotas HTTP.

ArquivoRotaMétodo
server/routes/index.ts/GET
server/routes/about.ts/aboutGET
server/api/users.ts/api/usersGET
server/api/users.post.ts/api/usersPOST
server/api/users/[id].ts/api/users/:idGET
server/api/users/[id].delete.ts/api/users/:idDELETE
// server/routes/index.ts
export default defineEventHandler(() => {
  return { app: 'Nitro API', version: '1.0.0' };
});

// server/api/users.ts — GET /api/users
export default defineEventHandler(() => {
  return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
});

// server/api/users.post.ts — POST /api/users
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  return { id: Date.now(), ...body };
});

// server/api/users/[id].ts — GET /api/users/:id
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id');
  return { id: Number(id), name: `User ${id}` };
});

// server/api/users/[id].delete.ts — DELETE /api/users/:id
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id');
  return { deleted: true, id: Number(id) };
});

Auto-imports

Nitro auto-importa funções comuns como defineEventHandler, getQuery, readBody, createError, setResponseStatus, etc. Sem necessidade de import explícito.

// Sem imports! Nitro resolve automaticamente
export default defineEventHandler((event) => {
  const query = getQuery(event);
  const body = await readBody(event);
  throw createError({ statusCode: 400, message: 'Erro' });
});

Lab: Exercício - API REST com Nitro

Crie uma API de tarefas completa:

// server/api/todos/index.ts — GET /api/todos
let todos = [
  { id: 1, title: 'Aprender Nitro', completed: false },
  { id: 2, title: 'Criar API', completed: true }
];
let nextId = 3;

export default defineEventHandler(() => {
  return todos;
});
// server/api/todos/index.post.ts — POST /api/todos
export default defineEventHandler(async (event) => {
  const body = await readBody(event);

  if (!body.title) {
    throw createError({ statusCode: 400, message: 'Título é obrigatório' });
  }

  const todo = { id: nextId++, title: body.title, completed: false };
  todos.push(todo);
  return todo;
});
// server/api/todos/[id].ts — GET /api/todos/:id
export default defineEventHandler((event) => {
  const id = Number(getRouterParam(event, 'id'));
  const todo = todos.find(t => t.id === id);

  if (!todo) {
    throw createError({ statusCode: 404, message: 'Tarefa não encontrada' });
  }

  return todo;
});
// server/api/todos/[id].delete.ts — DELETE /api/todos/:id
export default defineEventHandler((event) => {
  const id = Number(getRouterParam(event, 'id'));
  const index = todos.findIndex(t => t.id === id);

  if (index === -1) {
    throw createError({ statusCode: 404, message: 'Tarefa não encontrada' });
  }

  todos.splice(index, 1);
  return { deleted: true };
});
# Testar
curl http://localhost:3000/api/todos
curl -X POST http://localhost:3000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Nova tarefa"}'
curl http://localhost:3000/api/todos/1
curl -X DELETE http://localhost:3000/api/todos/1

Nitro usa file-based routing com sufixo .post.ts, .delete.ts para métodos HTTP. H3 é o motor universal que substitui Express. Auto-imports eliminam boilerplate. createError lança erros HTTP estruturados.