Rotas API do Lado do Servidor
Bob precisa adicionar APIs de backend à MegaShop—incluindo operações CRUD para produtos, funcionalidade de carrinho de compras e autenticação de usuários. A abordagem tradicional exigiria implantar um servidor Express separado, o que torna a integração front-end/back-end uma dor de cabeça. Charlie descobriu que o Nuxt 3 permite escrever APIs diretamente no diretório server/, lidando com front-end e back-end dentro de um único projeto.
1. O Que Você Vai Aprender
- server/api/ Definições de Rotas e Mapeamentos de Métodos HTTP
- Tratamento de eventos: defineEventHandler / readBody / getQuery / getRouterParam
- server/middleware/ Interceptação Global
- Tratamento de Cross-Domain e CORS
- Guia Prático das APIs MegaShop /api/products / /api/cart / /api/auth
2. Uma História Real de um Administrador
(1) Dor: O Pesadelo da Integração de Sistemas Front-End e Back-End Separados
Bob escreveu um servidor API standalone usando Express, rodando na porta 4000. O front-end Nuxt roda na porta 3000. Durante o desenvolvimento, ele tinha que configurar um proxy e lidar com requisições cross-origin, e o deploy exigia dois pipelines de CI/CD separados. As requisições do carrinho de compras da Alice eram ocasionalmente bloqueadas devido a problemas de cross-origin, resultando em erros em produção.
(2) Solução com a API do Servidor Nuxt
No Nuxt 3, o diretório server/ permite escrever APIs diretamente dentro do projeto; como estão no mesmo domínio e porta, não há problemas de cross-domain:
// server/api/products/index.get.ts
export default defineEventHandler(() => {
return { items: products, total: 1000000 }
})
(3) Benefícios: Solução Full-Stack Tudo-em-Um
Bob mantém apenas um projeto, com a API e o front-end implantados no mesmo domínio, então Alice nunca mais encontrará erros de cross-domain, e o processo de deploy é simplificado em 50%.
3. Definições de Rotas API
(1) Regras de Mapeamento de Roteamento por Arquivos
| Caminho do Arquivo | Método HTTP | URL da Rota |
|---|---|---|
| server/api/products.ts | ALL | /api/products |
| server/api/products/index.ts | ALL | /api/products |
| server/api/products/index.get.ts | GET | /api/products |
| server/api/products/index.post.ts | POST | /api/products |
| server/api/products/[id].get.ts | GET | /api/products/:id |
| server/api/products/[id].put.ts | PUT | /api/products/:id |
| server/api/products/[id].delete.ts | DELETE | /api/products/:id |
| server/api/auth/login.post.ts | POST | /api/auth/login |
(2) Ciclo de Vida do Processamento de Requisições API
sequenceDiagram
participant C as Cliente
participant M as Middleware do Servidor
participant H as Handler de Evento
participant D as Fonte de Dados
C->>M: Requisição HTTP
M->>M: Verificação Auth / CORS / Logging
M->>H: Passar evento
H->>D: Query / Mutation
D-->>H: Resultado de dados
H-->>M: Resposta HTTP
M-->>C: Resposta JSON
(1) ▶Exemplo: GET Lista de Produtos
// server/api/products/index.get.ts
export default defineEventHandler((event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 20
const category = query.category as string
let filtered = mockProducts
if (category) {
filtered = filtered.filter(p => p.category === category)
}
const start = (page - 1) * limit
return {
items: filtered.slice(start, start + limit),
total: filtered.length,
page,
limit
}
})
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: POST para Criar um Produto
// server/api/products/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validar campos obrigatórios
if (!body.name || !body.price) {
throw createError({
statusCode: 400,
message: 'Name and price are required'
})
}
const newProduct = {
id: mockProducts.length + 1,
name: body.name,
price: Number(body.price),
category: body.category || 'uncategorized',
inStock: body.inStock ?? true,
image: body.image || '/images/placeholder.webp'
}
mockProducts.push(newProduct)
return { product: newProduct, message: 'Product created successfully' }
})
Saída:
// Execução Bem-sucedida
(3) ▶Exemplo: Rota com Parâmetro Dinâmico
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const product = mockProducts.find(p => p.id === id)
if (!product) {
throw createError({
statusCode: 404,
message: 'Product not found'
})
}
return product
})
Saída:
// Execução Bem-sucedida
(4) ▶Exemplo: PUT para Atualizar um Produto
// server/api/products/[id].put.ts
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
const body = await readBody(event)
const index = mockProducts.findIndex(p => p.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
mockProducts[index] = { ...mockProducts[index], ...body }
return { product: mockProducts[index], message: 'Product updated' }
})
Saída:
// Execução Bem-sucedida
(5) ▶Exemplo: DELETE—Excluir um produto
// server/api/products/[id].delete.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const index = mockProducts.findIndex(p => p.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
const deleted = mockProducts.splice(index, 1)
return { product: deleted[0], message: 'Product deleted' }
})
Saída:
// Execução Bem-sucedida
4. Funções utilitárias de tratamento de eventos
(1) Referência Rápida das Ferramentas de Requisição
| Função | Propósito | Exemplo |
|---|---|---|
| getQuery(event) | Obter parâmetros de query da URL | { page: '1', limit: '20' } |
| getRouterParam(event, key) | Obter parâmetro de rota | /products/123 → '123' |
| readBody(event) | Ler corpo da requisição | { name: 'Product', price: 99 } |
| getHeader(event, key) | Obter header da requisição | 'Bearer token...' |
| getCookie(event, key) | Obter Cookie | 'session-id-xxx' |
| setCookie(event, key, val, opts) | Definir um cookie | setCookie(event, 'token', jwt, { httpOnly: true }) |
| setHeader(event, key, val) | Definir header de resposta | setHeader(event, 'x-total', '1000') |
| createError(opts) | Lançar um erro | createError({ statusCode: 404 }) |
(2) Convenções de Formato de Resposta
| Cenário | Código de Status | Corpo da Resposta |
|---|---|---|
| Consulta bem-sucedida | 200 | { items: [], total: 0 } |
| Criado com sucesso | 201 | { product: {}, message: '...' } |
| Atualização bem-sucedida | 200 | { product: {}, message: '...' } |
| Exclusão bem-sucedida | 200 | { message: '...' } |
| Erro de parâmetro | 400 | { statusCode: 400, message: '...' } |
| Não autenticado | 401 | { statusCode: 401, message: '...' } |
| Não Encontrado | 404 | { statusCode: 404, message: '...' } |
5. Middleware do Servidor
(1) ▶Exemplo: Middleware de Log Global
// server/middleware/logger.ts
export default defineEventHandler((event) => {
const start = Date.now()
const method = getMethod(event)
const url = getRequestURL(event)
// Log após a resposta
event.node.res.on('finish', () => {
const duration = Date.now() - start
const status = event.node.res.statusCode
console.log(`${method} ${url} → ${status} (${duration}ms)`)
})
})
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: Middleware de autenticação
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const protectedPaths = ['/api/admin', '/api/orders', '/api/cart']
const url = getRequestURL(event)
const isProtected = protectedPaths.some(path => url.pathname.startsWith(path))
if (!isProtected) return
const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({ statusCode: 401, message: 'Authentication required' })
}
// Verificar token JWT (simplificado)
try {
const payload = verifyToken(token)
event.context.user = payload
} catch {
throw createError({ statusCode: 401, message: 'Invalid token' })
}
})
Saída:
// Execução Bem-sucedida
6. Tratamento CORS Cross-Origin
(1) ▶Exemplo: Configuração CORS Integrada do Nitro
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/api/**': { cors: true }
}
})
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: Middleware CORS Personalizado
// server/middleware/cors.ts
export default defineEventHandler((event) => {
const origin = getHeader(event, 'origin') || '*'
setHeader(event, 'Access-Control-Allow-Origin', origin)
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
setHeader(event, 'Access-Control-Max-Age', '86400')
// Tratar preflight
if (getMethod(event) === 'OPTIONS') {
event.node.res.statusCode = 204
return ''
}
})
Saída:
// Execução Bem-sucedida
(1) Comparação de Soluções CORS
| Solução | Método de Configuração | Flexibilidade | Casos de Uso |
|---|---|---|---|
| routeRules cors: true | nuxt.config.ts | Baixa (Tudo Ligado/Tudo Desligado) | Ambiente de Desenvolvimento |
| Middleware personalizado | server/middleware/ | Alta (granular) | Produção |
| Nitro routeRules Avançado | routeRules por rota | Média | Requisitos Mistos |
7. Exemplo Completo: O Framework API da MegaShop
// server/api/cart/index.get.ts - Obter itens do carrinho
export default defineEventHandler((event) => {
const userId = event.context.user?.id
if (!userId) {
throw createError({ statusCode: 401, message: 'Login required' })
}
const cart = mockCarts.find(c => c.userId === userId)
return cart || { userId, items: [], total: 0 }
})
// server/api/cart/index.post.ts - Adicionar item ao carrinho
export default defineEventHandler(async (event) => {
const userId = event.context.user?.id
const { productId, quantity = 1 } = await readBody(event)
const product = mockProducts.find(p => p.id === productId)
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
if (!product.inStock) {
throw createError({ statusCode: 400, message: 'Product out of stock' })
}
// Adicionar ou atualizar item do carrinho
let cart = mockCarts.find(c => c.userId === userId)
if (!cart) {
cart = { userId, items: [], total: 0 }
mockCarts.push(cart)
}
const existing = cart.items.find(i => i.productId === productId)
if (existing) {
existing.quantity += quantity
} else {
cart.items.push({ productId, name: product.name, price: product.price, quantity })
}
cart.total = cart.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
return { cart, message: 'Item added to cart' }
})
❓Perguntas Frequentes
server/ é incluído no bundle do cliente?server/ separadamente, então não vaza para o cliente. O código do servidor pode usar com segurança APIs do Node.js e segredos.createError e throw new Error?createError é fornecido pelo H3 e retorna o código de status HTTP correto e uma resposta JSON. throw new Error retorna um erro interno 500. Recomendamos usar createError nas rotas API.server/middleware/ e intercepta requisições API (autorização, logging, CORS). Middleware do cliente está localizado no diretório middleware/ e intercepta transições de rotas de página (guardas de permissão/redirecionamentos).runtimeConfig na API?useRuntimeConfig() para obtê-las; variáveis privadas estão disponíveis apenas no servidor: const config = useRuntimeConfig() → config.databaseUrl.event.node.res.write() e definir Content-Type: text/event-stream para implementar SSE. O Nitro suporta totalmente a API de resposta nativa do Node.js.📖Resumo
- O diretório
server/no Nuxt 3 é a API: caminhos de arquivo mapeiam para rotas, e sufixos de método mapeiam para métodos HTTP - defineEventHandler + readBody/getQuery/getRouterParam para processar a requisição
- Middleware do servidor como interceptores globais: logging, autenticação, CORS
- Implantar dentro do mesmo domínio naturalmente evita problemas de cross-domain; se necessário, pode usar
routeRules corsou middleware personalizado. - MegaShop constrói um backend completo usando /api/products, /api/cart e /api/auth
📝Exercícios
- Exercício Básico (Dificuldade: ⭐): Crie os endpoints GET /api/products e GET /api/products/[id], que retornam dados mock de produtos.
- Exercício Avançado (Dificuldade: ⭐⭐): Implemente uma API CRUD completa (GET/POST/PUT/DELETE), incluindo validação de parâmetros e tratamento de erros.
- Desafio (Dificuldade: ⭐⭐⭐): Implemente autenticação usando middleware do lado do servidor. Retorne um erro 401 quando um usuário não autenticado acessar
/api/cart; permita operações normais após login.
---|



