tutoriales·18 min de lectura

Fetch API en JavaScript: guía Completa con Ejemplos prácticos

Aprende a usar la Fetch API de JavaScript desde cero. GET, POST, headers, errores y todo lo que necesitas saber con ejemplos reales.

Fetch API en JavaScript: guía Completa

La Fetch API en JavaScript es la forma nativa y moderna de hacer peticiones HTTP desde el navegador y Node.js. Si necesitas cargar datos de una API, enviar información a un servidor, o subir archivos, fetch es tu herramienta principal.

¿Qué es fetch?

fetch es una función integrada en JavaScript que te permite hacer peticiones HTTP a servidores. Te permite comunicarte con APIs y obtener o enviar información sin depender de librerías externas.

¿Por qué usar fetch?

Antes de fetch, usabamos XMLHttpRequest (una API antigua y complicada):

javascript
// Forma antigua con XMLHttpRequest (NO uses esto)
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://api.ejemplo.com/usuarios')
xhr.onload = function() {
  if (xhr.status === 200) {
    const datos = JSON.parse(xhr.responseText)
    console.log(datos)
  }
}
xhr.onerror = function() {
  console.error('Error')
}
xhr.send()

Complicado, verdad?

Con fetch (forma moderna):

javascript
fetch('https://api.ejemplo.com/usuarios')
  .then(response => response.json())
  .then(datos => console.log(datos))
  .catch(error => console.error('Error:', error))

Mucho más limpio y fácil de entender.

Disponibilidad

Fetch esta disponible en todos los navegadores modernos y en Node.js desde la versión 18. Es el estandar actual para hacer peticiones HTTP.

Sintaxis básica de fetch

La forma más simple de usar fetch:

javascript
fetch(url)

Esto retorna una Promise (promesa) que se resuelve con la respuesta del servidor. Si no tienes claro como funcionan las promesas y async/await, revisa la guía de async/await en JavaScript.

Ejemplo básico con async/await

javascript
async function obtenerDatos() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users')
  const datos = await response.json()
  console.log(datos)
}
 
obtenerDatos()

Desglosando el código:

  1. fetch(url) - Hace la petición al servidor
  2. await - Espera a que llegue la respuesta
  3. response.json() - Convierte la respuesta a formato JSON
  4. Los datos estan listos para usar

Ejemplo básico con .then()

Si prefieres usar Promises directamente:

javascript
fetch('https://jsonplaceholder.typicode.com/users')
  .then(response => response.json())
  .then(datos => {
    console.log(datos)
  })
  .catch(error => {
    console.error('Error:', error)
  })

Ambas formas funcionan. Async/await es generalmente más fácil de leer.

El objeto Response

Cuando haces un fetch, obtienes un objeto Response con información sobre la respuesta del servidor:

javascript
async function verificarRespuesta() {
  const response = await fetch('https://api.ejemplo.com/datos')
 
  console.log('Status:', response.status)        // 200, 404, 500, etc.
  console.log('OK:', response.ok)                // true si status 200-299
  console.log('Status Text:', response.statusText) // "OK", "Not Found", etc.
  console.log('Headers:', response.headers)      // Headers de la respuesta
  console.log('URL:', response.url)              // URL completa
}

Propiedades importantes:

PropiedadDescripciónEjemplo
response.oktrue si status 200-299true o false
response.statuscódigo de estado HTTP200, 404, 500
response.statusTextTexto del estado"OK", "Not Found"
response.headersHeaders de respuestaObjeto Headers
response.bodyCuerpo de la respuestaReadableStream

métodos HTTP: GET, POST, PUT, DELETE

HTTP tiene diferentes "verbos" o métodos para diferentes acciones:

métodopropósitoAnalogia
GETObtener datosLeer un libro
POSTCrear datos nuevosEscribir un libro nuevo
PUTActualizar datos existentesEditar un libro completo
PATCHActualizar parte de datosEditar una página del libro
DELETEEliminar datosTirar el libro

GET: Obtener datos

GET es el método por defecto de fetch. Se usa para obtener información:

javascript
// Sintaxis simple (método GET por defecto)
async function obtenerUsuarios() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users')
  const usuarios = await response.json()
  console.log(usuarios)
}
 
// Sintaxis explicita
async function obtenerUsuarios() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'GET'
  })
  const usuarios = await response.json()
  console.log(usuarios)
}

Ejemplo real: Obtener un usuario específico

javascript
async function obtenerUsuario(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
 
  if (!response.ok) {
    throw new Error('Usuario no encontrado')
  }
 
  const usuario = await response.json()
  return usuario
}
 
// Usar la función
obtenerUsuario(1)
  .then(usuario => console.log(usuario.name))
  .catch(error => console.error('Error:', error))

POST: Crear datos nuevos

POST se usa para enviar datos al servidor y crear recursos nuevos:

javascript
async function crearUsuario(datosUsuario) {
  const response = await fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(datosUsuario)
  })
 
  const usuarioCreado = await response.json()
  return usuarioCreado
}
 
// Usar la función
const nuevoUsuario = {
  name: 'Ana Garcia',
  email: 'ana@ejemplo.com',
  username: 'anagarcia'
}
 
crearUsuario(nuevoUsuario)
  .then(usuario => console.log('Usuario creado:', usuario))
  .catch(error => console.error('Error:', error))

Elementos importantes de POST:

  1. method: 'POST' - específica el método
  2. headers - Le dice al servidor que tipo de datos envias
  3. body - Los datos que envias (convertidos a string con JSON.stringify)

PUT: Actualizar datos completos

PUT se usa para actualizar un recurso completo:

javascript
async function actualizarUsuario(id, datosActualizados) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(datosActualizados)
  })
 
  const usuarioActualizado = await response.json()
  return usuarioActualizado
}
 
// Usar la función
const datosActualizados = {
  id: 1,
  name: 'Ana Garcia Actualizada',
  email: 'ana.nueva@ejemplo.com',
  username: 'anagarcia'
}
 
actualizarUsuario(1, datosActualizados)
  .then(usuario => console.log('Usuario actualizado:', usuario))

PATCH: Actualizar datos parciales

PATCH se usa para actualizar solo algunos campos:

javascript
async function actualizarEmail(id, nuevoEmail) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: nuevoEmail  // Solo actualizar el email
    })
  })
 
  const usuarioActualizado = await response.json()
  return usuarioActualizado
}
 
// Usar la función
actualizarEmail(1, 'nuevo@ejemplo.com')
  .then(usuario => console.log('Email actualizado:', usuario))

Diferencia entre PUT y PATCH:

  • PUT: Reemplaza el recurso completo (debes enviar todos los campos)
  • PATCH: Actualiza solo los campos que especifiques

DELETE: Eliminar datos

DELETE se usa para eliminar recursos:

javascript
async function eliminarUsuario(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
    method: 'DELETE'
  })
 
  if (response.ok) {
    console.log('Usuario eliminado exitosamente')
  }
}
 
// Usar la función
eliminarUsuario(1)
  .then(() => console.log('Usuario eliminado'))
  .catch(error => console.error('Error:', error))

Headers (Encabezados)

Los headers son información adicional que envias con tu petición. Piensalos como el sobre de una carta que contiene datos sobre el remitente, destinatario, etc.

Headers comunes

javascript
const response = await fetch('https://api.ejemplo.com/datos', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',      // Tipo de datos que envias
    'Accept': 'application/json',            // Tipo de datos que aceptas
    'Authorization': 'Bearer tu-token-aquí', // Token de autenticación
    'X-Custom-Header': 'valor-personalizado' // Header personalizado
  },
  body: JSON.stringify({ dato: 'valor' })
})

Headers más usados:

HeaderpropósitoEjemplo
Content-TypeTipo de datos que enviasapplication/json
AcceptTipo de datos que aceptasapplication/json
AuthorizationToken de autenticaciónBearer abc123...
User-AgentInformación del navegadorMozilla/5.0...

Cuando trabajas con tokens de autenticación y API keys en tus headers, Asegúrate de no exponerlos en tu repositorio. Herramientas como datahogo escanean tu repo y detectan credenciales expuestas automáticamente.

Leer headers de la respuesta

javascript
async function verHeaders() {
  const response = await fetch('https://api.ejemplo.com/datos')
 
  // Obtener un header específico
  const contentType = response.headers.get('content-type')
  console.log('Content-Type:', contentType)
 
  // Iterar sobre todos los headers
  response.headers.forEach((valor, nombre) => {
    console.log(`${nombre}: ${valor}`)
  })
}

Diferentes tipos de datos

JSON (lo más común)

javascript
async function enviarJSON() {
  const response = await fetch('https://api.ejemplo.com/usuarios', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      nombre: 'Ana',
      edad: 25
    })
  })
 
  const datos = await response.json()
  console.log(datos)
}

Texto plano

javascript
async function obtenerTexto() {
  const response = await fetch('https://ejemplo.com/archivo.txt')
  const texto = await response.text()
  console.log(texto)
}

FormData (para formularios y archivos)

javascript
async function subirArchivo(archivo) {
  const formData = new FormData()
  formData.append('archivo', archivo)
  formData.append('descripción', 'Mi archivo')
 
  const response = await fetch('https://api.ejemplo.com/upload', {
    method: 'POST',
    body: formData  // NO pongas Content-Type, se añade automáticamente
  })
 
  const resultado = await response.json()
  console.log(resultado)
}
 
// Usar con un input de archivo
const input = document.querySelector('input[type="file"]')
input.addEventListener('change', (e) => {
  const archivo = e.target.files[0]
  subirArchivo(archivo)
})

Blob (para imágenes, videos, etc.)

javascript
async function descargarImagen() {
  const response = await fetch('https://ejemplo.com/imagen.jpg')
  const blob = await response.blob()
 
  // Crear URL temporal para la imagen
  const url = URL.createObjectURL(blob)
 
  // Mostrar la imagen
  const img = document.createElement('img')
  img.src = url
  document.body.appendChild(img)
}

Manejo de errores

Fetch tiene una peculiaridad: solo rechaza la promesa en errores de red. Si el servidor responde con error 404 o 500, fetch NO lo considera error.

Verificar response.ok

javascript
async function obtenerDatos() {
  try {
    const response = await fetch('https://api.ejemplo.com/datos')
 
    // Verificar si la respuesta fue exitosa
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
 
    const datos = await response.json()
    return datos
  } catch (error) {
    console.error('Error:', error.message)
    throw error
  }
}

Manejo completo de errores

javascript
async function obtenerUsuario(id) {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
 
    // Error 404, 500, etc.
    if (!response.ok) {
      if (response.status === 404) {
        throw new Error('Usuario no encontrado')
      } else if (response.status === 500) {
        throw new Error('Error del servidor')
      } else {
        throw new Error(`Error: ${response.status}`)
      }
    }
 
    const usuario = await response.json()
    return usuario
 
  } catch (error) {
    // Errores de red (sin internet, servidor caido, etc.)
    if (error instanceof TypeError) {
      console.error('Error de red:', error.message)
      throw new Error('No se pudo conectar al servidor')
    }
 
    // Otros errores
    console.error('Error:', error.message)
    throw error
  }
}
 
// Usar la función
obtenerUsuario(999)
  .then(usuario => console.log(usuario))
  .catch(error => console.error('Fallo:', error.message))
Fetch no rechaza en errores HTTP

A diferencia de otras librerías, fetch solo rechaza la promesa en errores de red (sin internet, DNS fallo, etc.). Respuestas con status 404, 500, etc. se consideran "exitosas" y debes verificar response.ok manualmente.

Opciones de configuración

Fetch acepta un segundo parámetro con opciones:

javascript
const response = await fetch(url, {
  method: 'GET',           // GET, POST, PUT, DELETE, etc.
  headers: {},             // Headers personalizados
  body: null,              // Datos a enviar (no en GET)
  mode: 'cors',            // cors, no-cors, same-origin
  credentials: 'include',  // omit, same-origin, include
  cache: 'default',        // default, no-cache, reload, force-cache
  redirect: 'follow',      // follow, error, manual
  referrer: 'client',      // URL del referrer
  signal: null             // AbortSignal para cancelar
})

Opciones comunes explicadas

mode:

javascript
// cors: Permite peticiones a otros dominios (lo más común)
fetch('https://api.ejemplo.com/datos', {
  mode: 'cors'
})
 
// same-origin: Solo permite peticiones al mismo dominio
fetch('/api/datos', {
  mode: 'same-origin'
})

credentials:

javascript
// include: Envia cookies incluso a otros dominios
fetch('https://api.ejemplo.com/datos', {
  credentials: 'include'
})
 
// same-origin: Solo envia cookies al mismo dominio (default)
fetch('/api/datos', {
  credentials: 'same-origin'
})
 
// omit: No envia cookies
fetch('https://api.ejemplo.com/datos', {
  credentials: 'omit'
})

cache:

javascript
// no-cache: Siempre pide datos frescos
fetch('https://api.ejemplo.com/datos', {
  cache: 'no-cache'
})
 
// force-cache: Usa cache si esta disponible
fetch('https://api.ejemplo.com/datos', {
  cache: 'force-cache'
})

Cancelar peticiones con AbortController

A veces necesitas cancelar una petición (usuario cambia de página, timeout, etc.):

javascript
async function buscarConTimeout() {
  // Crear un controlador para cancelar
  const controller = new AbortController()
  const signal = controller.signal
 
  // Cancelar después de 5 segundos
  const timeout = setTimeout(() => controller.abort(), 5000)
 
  try {
    const response = await fetch('https://api.ejemplo.com/datos', {
      signal: signal  // Pasar el signal
    })
 
    clearTimeout(timeout)  // Cancelar el timeout si termina a tiempo
    const datos = await response.json()
    return datos
 
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Petición cancelada')
    } else {
      console.error('Error:', error)
    }
  }
}

Ejemplo práctico: búsqueda con cancelación

javascript
let controladorActual = null
 
async function buscar(query) {
  // Cancelar búsqueda anterior si existe
  if (controladorActual) {
    controladorActual.abort()
  }
 
  // Crear nuevo controlador
  controladorActual = new AbortController()
 
  try {
    const response = await fetch(`/api/buscar?q=${query}`, {
      signal: controladorActual.signal
    })
 
    const resultados = await response.json()
    mostrarResultados(resultados)
 
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('búsqueda cancelada')
    }
  }
}
 
// En un input de búsqueda
input.addEventListener('input', (e) => {
  buscar(e.target.value)
})

Esto cancela búsquedas anteriores si el usuario sigue escribiendo.

Ejemplos prácticos completos

Ejemplo 1: Cargar y mostrar lista de usuarios

javascript
async function cargarUsuarios() {
  const contenedor = document.getElementById('usuarios')
  contenedor.innerHTML = '<p>Cargando...</p>'
 
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users')
 
    if (!response.ok) {
      throw new Error('Error al cargar usuarios')
    }
 
    const usuarios = await response.json()
 
    // Limpiar contenedor
    contenedor.innerHTML = ''
 
    // Crear HTML para cada usuario
    usuarios.forEach(usuario => {
      const div = document.createElement('div')
      div.innerHTML = `
        <h3>${usuario.name}</h3>
        <p>Email: ${usuario.email}</p>
        <p>Ciudad: ${usuario.address.city}</p>
        <hr>
      `
      contenedor.appendChild(div)
    })
 
  } catch (error) {
    contenedor.innerHTML = `<p>Error: ${error.message}</p>`
  }
}
 
// Llamar al cargar la página
cargarUsuarios()

Ejemplo 2: Formulario de contacto

javascript
async function enviarFormulario(evento) {
  evento.preventDefault()
 
  // Obtener datos del formulario
  const formulario = evento.target
  const datos = {
    nombre: formulario.nombre.value,
    email: formulario.email.value,
    mensaje: formulario.mensaje.value
  }
 
  // Deshabilitar boton mientras envia
  const boton = formulario.querySelector('button')
  boton.disabled = true
  boton.textContent = 'Enviando...'
 
  try {
    const response = await fetch('/api/contacto', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(datos)
    })
 
    if (!response.ok) {
      throw new Error('Error al enviar mensaje')
    }
 
    const resultado = await response.json()
    alert('Mensaje enviado exitosamente')
    formulario.reset()
 
  } catch (error) {
    alert('Error: ' + error.message)
  } finally {
    boton.disabled = false
    boton.textContent = 'Enviar'
  }
}
 
// Conectar con el formulario
document.getElementById('formulario-contacto')
  .addEventListener('submit', enviarFormulario)

Ejemplo 3: Subir imagen con preview

javascript
async function subirImagen(archivo) {
  // Mostrar preview
  const preview = document.getElementById('preview')
  const reader = new FileReader()
  reader.onload = (e) => {
    preview.src = e.target.result
  }
  reader.readAsDataURL(archivo)
 
  // Preparar FormData
  const formData = new FormData()
  formData.append('imagen', archivo)
  formData.append('título', 'Mi imagen')
 
  try {
    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData
    })
 
    if (!response.ok) {
      throw new Error('Error al subir imagen')
    }
 
    const resultado = await response.json()
    console.log('Imagen subida:', resultado.url)
    alert('Imagen subida exitosamente')
 
  } catch (error) {
    alert('Error: ' + error.message)
  }
}
 
// Conectar con input file
document.getElementById('input-imagen')
  .addEventListener('change', (e) => {
    const archivo = e.target.files[0]
    if (archivo) {
      subirImagen(archivo)
    }
  })

Ejemplo 4: CRUD completo

javascript
// CRUD = Create, Read, Update, Delete
 
const API_URL = 'https://jsonplaceholder.typicode.com/users'
 
// CREATE - Crear usuario
async function crearUsuario(datos) {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(datos)
  })
  return await response.json()
}
 
// READ - Leer usuarios
async function obtenerUsuarios() {
  const response = await fetch(API_URL)
  return await response.json()
}
 
// READ - Leer un usuario
async function obtenerUsuario(id) {
  const response = await fetch(`${API_URL}/${id}`)
  return await response.json()
}
 
// UPDATE - Actualizar usuario
async function actualizarUsuario(id, datos) {
  const response = await fetch(`${API_URL}/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(datos)
  })
  return await response.json()
}
 
// DELETE - Eliminar usuario
async function eliminarUsuario(id) {
  const response = await fetch(`${API_URL}/${id}`, {
    method: 'DELETE'
  })
  return response.ok
}
 
// Usar las funciones
async function ejemploCRUD() {
  // Crear
  const nuevoUsuario = await crearUsuario({
    name: 'Ana Garcia',
    email: 'ana@ejemplo.com'
  })
  console.log('Usuario creado:', nuevoUsuario)
 
  // Leer todos
  const usuarios = await obtenerUsuarios()
  console.log('Usuarios:', usuarios.length)
 
  // Leer uno
  const usuario = await obtenerUsuario(1)
  console.log('Usuario 1:', usuario.name)
 
  // Actualizar
  const actualizado = await actualizarUsuario(1, {
    name: 'Ana Garcia Actualizada',
    email: 'ana.nueva@ejemplo.com'
  })
  console.log('Usuario actualizado:', actualizado)
 
  // Eliminar
  const eliminado = await eliminarUsuario(1)
  console.log('Usuario eliminado:', eliminado)
}

Patrones útiles

Patron 1: Función wrapper reutilizable

javascript
async function fetchJSON(url, opciones = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        ...opciones.headers
      },
      ...opciones
    })
 
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
 
    return await response.json()
  } catch (error) {
    console.error('Error en fetch:', error)
    throw error
  }
}
 
// Usar la función wrapper
const usuarios = await fetchJSON('https://api.ejemplo.com/usuarios')
const usuario = await fetchJSON('https://api.ejemplo.com/usuarios/1')

Patron 2: Retry automático

javascript
async function fetchConReintentos(url, opciones = {}, intentos = 3) {
  for (let i = 0; i < intentos; i++) {
    try {
      const response = await fetch(url, opciones)
 
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
 
      return await response.json()
    } catch (error) {
      // Si es el último intento, lanzar el error
      if (i === intentos - 1) throw error
 
      // Esperar antes de reintentar (backoff exponencial)
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
    }
  }
}
 
// Usar con reintentos
const datos = await fetchConReintentos('https://api.ejemplo.com/datos', {}, 3)

Patron 3: Cache simple

javascript
const cache = new Map()
 
async function fetchConCache(url, tiempoCache = 60000) {
  // Verificar si esta en cache y no ha expirado
  if (cache.has(url)) {
    const { datos, timestamp } = cache.get(url)
    if (Date.now() - timestamp < tiempoCache) {
      console.log('Usando cache')
      return datos
    }
  }
 
  // Hacer fetch y guardar en cache
  console.log('Haciendo fetch')
  const response = await fetch(url)
  const datos = await response.json()
 
  cache.set(url, {
    datos,
    timestamp: Date.now()
  })
 
  return datos
}
 
// Primera llamada: hace fetch
const datos1 = await fetchConCache('https://api.ejemplo.com/datos')
 
// Segunda llamada (dentro de 60s): usa cache
const datos2 = await fetchConCache('https://api.ejemplo.com/datos')

Patron 4: Progreso de descarga

javascript
async function descargarConProgreso(url) {
  const response = await fetch(url)
  const total = response.headers.get('content-length')
 
  let descargado = 0
  const chunks = []
 
  const reader = response.body.getReader()
 
  while (true) {
    const { done, value } = await reader.read()
 
    if (done) break
 
    chunks.push(value)
    descargado += value.length
 
    // Calcular progreso
    const progreso = total ? (descargado / total) * 100 : 0
    console.log(`Descargado: ${progreso.toFixed(2)}%`)
  }
 
  // Combinar todos los chunks
  const blob = new Blob(chunks)
  return blob
}
 
// Usar con una imagen
descargarConProgreso('https://ejemplo.com/imagen-grande.jpg')
  .then(blob => {
    const url = URL.createObjectURL(blob)
    console.log('Descarga completa:', url)
  })

Errores comunes

Error 1: Olvidar await en .json()

javascript
// Incorrecto
async function obtenerDatos() {
  const response = await fetch('url')
  const datos = response.json()  // Falta await
  console.log(datos)  // Imprime una Promise, no los datos
}
 
// Correcto
async function obtenerDatos() {
  const response = await fetch('url')
  const datos = await response.json()
  console.log(datos)
}

Error 2: No verificar response.ok

javascript
// Incorrecto
async function obtenerDatos() {
  const response = await fetch('url')
  const datos = await response.json()
  return datos
}
 
// Correcto
async function obtenerDatos() {
  const response = await fetch('url')
 
  if (!response.ok) {
    throw new Error('Error en la petición')
  }
 
  const datos = await response.json()
  return datos
}

Error 3: Enviar body en GET

javascript
// Incorrecto: GET no debe tener body
fetch('url', {
  method: 'GET',
  body: JSON.stringify({ dato: 'valor' })
})
 
// Correcto: Usa query parameters
fetch('url?dato=valor', {
  method: 'GET'
})

Error 4: Olvidar Content-Type en POST

javascript
// Incorrecto
fetch('url', {
  method: 'POST',
  body: JSON.stringify({ dato: 'valor' })
  // Falta Content-Type header
})
 
// Correcto
fetch('url', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ dato: 'valor' })
})

Error 5: Usar .json() en respuestas no-JSON

javascript
// Incorrecto: Si la respuesta es texto plano
async function obtenerTexto() {
  const response = await fetch('archivo.txt')
  const datos = await response.json()  // Error: no es JSON
}
 
// Correcto
async function obtenerTexto() {
  const response = await fetch('archivo.txt')
  const texto = await response.text()  // Usar .text() para texto plano
}

Fetch en diferentes entornos

En Node.js

Fetch esta disponible nativamente desde Node.js 18:

javascript
// Node.js 18+
const response = await fetch('https://api.ejemplo.com/datos')
const datos = await response.json()

Para versiones anteriores, instala node-fetch:

bash
npm install node-fetch
javascript
// Node.js < 18
import fetch from 'node-fetch'
 
const response = await fetch('https://api.ejemplo.com/datos')
const datos = await response.json()

En NextJS (Server Components)

javascript
// app/page.tsx (Server Component)
export default async function Page() {
  const response = await fetch('https://api.ejemplo.com/productos')
  const productos = await response.json()
 
  return (
    <div>
      {productos.map(p => (
        <div key={p.id}>{p.nombre}</div>
      ))}
    </div>
  )
}

En React (Client Components)

javascript
'use client'
 
import { useState, useEffect } from 'react'
 
export default function Productos() {
  const [productos, setProductos] = useState([])
  const [cargando, setCargando] = useState(true)
 
  useEffect(() => {
    async function cargar() {
      const response = await fetch('https://api.ejemplo.com/productos')
      const datos = await response.json()
      setProductos(datos)
      setCargando(false)
    }
 
    cargar()
  }, [])
 
  if (cargando) return <div>Cargando...</div>
 
  return (
    <div>
      {productos.map(p => (
        <div key={p.id}>{p.nombre}</div>
      ))}
    </div>
  )
}

Resumen

Puntos clave sobre Fetch:

  1. Fetch es la forma moderna de hacer peticiones HTTP
  2. Retorna una Promise que se resuelve con un objeto Response
  3. Debes llamar .json(), .text(), o .blob() para obtener los datos
  4. Siempre verifica response.ok - fetch no rechaza en errores HTTP
  5. Usa method para especificar GET, POST, PUT, DELETE
  6. Usa headers para enviar información adicional
  7. Usa body para enviar datos (no en GET)
  8. Siempre maneja errores con try/catch
  9. Puedes cancelar peticiones con AbortController
  10. Funciona en navegadores y Node.js 18+

Tabla de referencia rápida:

Accióncódigo
GET básicofetch(url)
POST con JSONfetch(url, { method: 'POST', body: JSON.stringify(data) })
Obtener JSONawait response.json()
Obtener textoawait response.text()
Verificar éxitoif (!response.ok) throw new Error()
Con headersfetch(url, { headers: { 'Authorization': 'Bearer token' } })
Cancelarfetch(url, { signal: controller.signal })

Si necesitas funcionalidades más avanzadas como interceptors, timeouts integrados y conversion automática de JSON, revisa la guía completa de Axios en JavaScript.

Cuando tus respuestas de API incluyen datos que necesitas validar antes de usar en tu aplicación, Zod es una excelente opción para validar esos datos de forma segura con TypeScript.


Recursos adicionales:

#javascript#fetch#http#web apis

Preguntas frecuentes

¿Qué es la Fetch API y cómo se usa en JavaScript?

La Fetch API es una función nativa de JavaScript que permite hacer peticiones HTTP a servidores. Se usa con fetch(url) que retorna una Promise, y puedes encadenar .then() o usar async/await para obtener los datos de la respuesta.

¿Cómo manejar errores con fetch en JavaScript?

Fetch solo rechaza la promesa en errores de red, no en errores HTTP como 404 o 500. Debes verificar response.ok manualmente y lanzar un error si es false. Combina esto con try/catch para manejar tanto errores de red como errores HTTP.

¿Cuál es la diferencia entre fetch y axios?

Fetch es nativo del navegador y no requiere dependencias, pero necesitas convertir manualmente a JSON y verificar errores HTTP. Axios es una librería externa que convierte JSON automáticamente, lanza errores en status 4xx/5xx, y ofrece interceptors y timeouts integrados.