404 Not Found

404 Not Found


nginx

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


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:

TYPESCRIPT
// 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

100%
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

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: POST para Criar um Produto

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

(3) ▶Exemplo: Rota com Parâmetro Dinâmico

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

(4) ▶Exemplo: PUT para Atualizar um Produto

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

(5) ▶Exemplo: DELETE—Excluir um produto

TYPESCRIPT
// 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:

TEXT
// 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

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Middleware de autenticação

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

6. Tratamento CORS Cross-Origin

(1) ▶Exemplo: Configuração CORS Integrada do Nitro

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/api/**': { cors: true }
  }
})

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Middleware CORS Personalizado

TYPESCRIPT
// 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:

TEXT
// 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

TYPESCRIPT
// 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 }
})
TYPESCRIPT
// 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

P O código no diretório server/ é incluído no bundle do cliente?
R Não. O Nitro empacota o código do 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.
P Os sufixos .get e .post nos nomes de arquivos de rotas API são obrigatórios?
R Não, não são obrigatórios. Arquivos sem sufixo de método lidam com todos os métodos HTTP. Adicionar sufixos permite controle preciso sobre a lógica de tratamento de cada método, por isso é recomendado.
P Qual é a diferença entre createError e throw new Error?
R 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.
P Qual é a diferença entre middleware do servidor e middleware do cliente?
R Middleware do servidor está localizado em 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).
P Como usar as variáveis privadas do runtimeConfig na API?
R Basta usar useRuntimeConfig() para obtê-las; variáveis privadas estão disponíveis apenas no servidor: const config = useRuntimeConfig()config.databaseUrl.
P A API pode retornar uma resposta em stream (SSE)?
R Sim. Você pode escrever dados em chunks usando 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


📝Exercícios

  1. Exercício Básico (Dificuldade: ⭐): Crie os endpoints GET /api/products e GET /api/products/[id], que retornam dados mock de produtos.
  2. Exercício Avançado (Dificuldade: ⭐⭐): Implemente uma API CRUD completa (GET/POST/PUT/DELETE), incluindo validação de parâmetros e tratamento de erros.
  3. 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.

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%