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.
| Arquivo | Rota | Método |
|---|---|---|
server/routes/index.ts | / | GET |
server/routes/about.ts | /about | GET |
server/api/users.ts | /api/users | GET |
server/api/users.post.ts | /api/users | POST |
server/api/users/[id].ts | /api/users/:id | GET |
server/api/users/[id].delete.ts | /api/users/:id | DELETE |
// 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.tspara métodos HTTP. H3 é o motor universal que substitui Express. Auto-imports eliminam boilerplate.createErrorlança erros HTTP estruturados.