Ir al contenido principal

Métodos HTTP

Introducción

Cuando trabajamos con aplicaciones web, la comunicación entre el navegador y el servidor se realiza mediante el protocolo HTTP (Hypertext Transfer Protocol). Este protocolo define un conjunto de métodos de petición que indican la acción que se desea realizar sobre un recurso determinado. Estos métodos, también conocidos como "verbos HTTP", son fundamentales para entender cómo funcionan las API web y cómo podemos interactuar con ellas desde JavaScript.

En el artículo anterior aprendimos sobre la API Fetch, que nos permite realizar peticiones HTTP desde JavaScript. Ahora, profundizaremos en los diferentes métodos HTTP que podemos utilizar en estas peticiones, sus propósitos específicos y cuándo es apropiado usar cada uno de ellos.

Métodos principales: GET, POST, PUT, DELETE

Los cuatro métodos HTTP más utilizados en el desarrollo web son GET, POST, PUT y DELETE. Estos métodos conforman lo que se conoce como operaciones CRUD (Create, Read, Update, Delete), que son las operaciones básicas que se pueden realizar sobre los datos.

GET

El método GET solicita una representación de un recurso específico. Las peticiones que usan este método solo deben recuperar datos y no deben tener ningún otro efecto.

// Ejemplo básico de petición GET
fetch('https://api.ejemplo.com/usuarios')
  .then(respuesta => respuesta.json())
  .then(datos => {
    console.log('Usuarios obtenidos:', datos);
  })
  .catch(error => {
    console.error('Error al obtener usuarios:', error);
  });

Características principales del método GET:

  • Es el método HTTP más común
  • Solo debe ser usado para obtener información
  • Los parámetros se envían en la URL (query string)
  • Las peticiones GET pueden ser cacheadas
  • Las peticiones GET permanecen en el historial del navegador
  • Se pueden marcar como favoritos
  • Tienen limitaciones de longitud (la URL tiene un límite de caracteres)

POST

El método POST se utiliza para enviar datos al servidor para crear un nuevo recurso. Los datos enviados se incluyen en el cuerpo de la petición.

// Ejemplo básico de petición POST
const nuevoUsuario = {
  nombre: 'Laura',
  email: 'laura@ejemplo.com',
  edad: 28
};

fetch('https://api.ejemplo.com/usuarios', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(nuevoUsuario)
})
  .then(respuesta => respuesta.json())
  .then(datosRespuesta => {
    console.log('Usuario creado:', datosRespuesta);
  })
  .catch(error => {
    console.error('Error al crear usuario:', error);
  });

Características principales del método POST:

  • Se utiliza para crear nuevos recursos
  • Los datos se envían en el cuerpo de la petición
  • No hay limitaciones en la cantidad de datos que se pueden enviar
  • Las peticiones POST no se almacenan en caché
  • Las peticiones POST no quedan en el historial del navegador
  • No se pueden marcar como favoritos
  • Son más seguras para enviar datos sensibles que GET

PUT

El método PUT se utiliza para actualizar un recurso existente o crear uno nuevo si no existe. A diferencia de POST, PUT es idempotente, lo que significa que realizar la misma petición múltiples veces tendrá el mismo resultado que hacerlo una sola vez.

// Ejemplo básico de petición PUT
const usuarioActualizado = {
  nombre: 'Laura Pérez',
  email: 'laura@ejemplo.com',
  edad: 29
};

fetch('https://api.ejemplo.com/usuarios/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(usuarioActualizado)
})
  .then(respuesta => respuesta.json())
  .then(datosRespuesta => {
    console.log('Usuario actualizado:', datosRespuesta);
  })
  .catch(error => {
    console.error('Error al actualizar usuario:', error);
  });

Características principales del método PUT:

  • Se utiliza para actualizar recursos existentes o crear nuevos si no existen
  • Es idempotente (realizar la misma petición múltiples veces tiene el mismo efecto que hacerlo una vez)
  • Generalmente requiere enviar todos los datos del recurso, no solo los campos que cambian
  • Los datos se envían en el cuerpo de la petición

DELETE

El método DELETE se utiliza para eliminar un recurso específico.

// Ejemplo básico de petición DELETE
fetch('https://api.ejemplo.com/usuarios/123', {
  method: 'DELETE'
})
  .then(respuesta => {
    if (respuesta.ok) {
      console.log('Usuario eliminado correctamente');
    } else {
      console.error('Error al eliminar usuario');
    }
  })
  .catch(error => {
    console.error('Error en la petición:', error);
  });

Características principales del método DELETE:

  • Se utiliza para eliminar recursos
  • Es idempotente (eliminar un recurso que ya no existe no debería generar un error)
  • Normalmente no incluye un cuerpo en la petición
  • Puede devolver el recurso eliminado o un estado vacío

Idempotencia y seguridad

Dos conceptos importantes relacionados con los métodos HTTP son la idempotencia y la seguridad.

Métodos seguros

Un método HTTP es seguro si no altera el estado del servidor. Es decir, es un método de "solo lectura". Los métodos seguros son:

  • GET
  • HEAD (similar a GET pero solo devuelve los encabezados, no el cuerpo)
  • OPTIONS (devuelve los métodos HTTP soportados por el recurso)

Métodos idempotentes

Un método HTTP es idempotente si el resultado de realizar una petición múltiples veces es el mismo que hacerlo una sola vez. Los métodos idempotentes son:

  • GET
  • HEAD
  • PUT
  • DELETE
  • OPTIONS

El método POST no es idempotente, ya que cada vez que se realiza una petición POST, se crea un nuevo recurso.

// Ejemplo de idempotencia: múltiples peticiones PUT tienen el mismo resultado
const actualizarUsuario = () => {
  const datos = {
    nombre: 'Carlos',
    activo: true
  };
  
  fetch('https://api.ejemplo.com/usuarios/456', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(datos)
  });
};

// Ejecutar varias veces tiene el mismo resultado que ejecutar una vez
actualizarUsuario();
actualizarUsuario();
actualizarUsuario();

Headers HTTP comunes

Los headers (encabezados) HTTP son pares clave-valor que se envían en las peticiones y respuestas HTTP. Permiten transmitir información adicional sobre la petición o la respuesta.

Headers de petición comunes

fetch('https://api.ejemplo.com/datos', {
  headers: {
    'Content-Type': 'application/json', // Indica el formato de los datos enviados
    'Authorization': 'Bearer token123', // Para autenticación
    'Accept': 'application/json', // Formato de respuesta esperado
    'User-Agent': 'MiAplicacion/1.0', // Información sobre el cliente
    'Cache-Control': 'no-cache' // Instrucciones de caché
  }
});
  • Content-Type: Indica el tipo de medio de los datos enviados en el cuerpo
  • Authorization: Contiene credenciales para autenticar al cliente
  • Accept: Especifica los tipos de contenido aceptables para la respuesta
  • User-Agent: Información sobre el cliente que hace la petición
  • Cache-Control: Directivas para mecanismos de caché

Headers de respuesta comunes

fetch('https://api.ejemplo.com/recursos')
  .then(respuesta => {
    console.log('Tipo de contenido:', respuesta.headers.get('Content-Type'));
    console.log('Servidor:', respuesta.headers.get('Server'));
    console.log('Fecha:', respuesta.headers.get('Date'));
    return respuesta.json();
  });
  • Content-Type: Tipo de medio del cuerpo de la respuesta
  • Content-Length: Tamaño del cuerpo de la respuesta en bytes
  • Server: Información sobre el software del servidor
  • Date: Fecha y hora en que se originó la respuesta
  • Cache-Control: Directivas para el almacenamiento en caché de la respuesta

Códigos de estado HTTP

Los códigos de estado HTTP indican el resultado de una petición HTTP. Se dividen en cinco categorías:

1xx - Información

Estos códigos indican que la petición ha sido recibida y el proceso continúa.

  • 100 Continue: El servidor ha recibido los encabezados y el cliente debe proceder a enviar el cuerpo.

2xx - Éxito

Estos códigos indican que la petición ha sido recibida, entendida y aceptada correctamente.

  • 200 OK: La petición ha tenido éxito.
  • 201 Created: La petición ha sido completada y un nuevo recurso ha sido creado.
  • 204 No Content: La petición se ha completado con éxito pero no hay contenido para devolver.
fetch('https://api.ejemplo.com/usuarios', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({nombre: 'Ana'})
})
  .then(respuesta => {
    if (respuesta.status === 201) {
      console.log('Usuario creado correctamente');
    } else {
      console.log('Estado de la respuesta:', respuesta.status);
    }
    return respuesta.json();
  });

3xx - Redirección

Estos códigos indican que se necesita una acción adicional para completar la petición.

  • 301 Moved Permanently: El recurso se ha movido permanentemente a otra URL.
  • 302 Found: El recurso se ha movido temporalmente a otra URL.
  • 304 Not Modified: El recurso no ha sido modificado desde la última petición.

4xx - Error del cliente

Estos códigos indican que ha habido un error por parte del cliente.

  • 400 Bad Request: La petición tiene una sintaxis incorrecta.
  • 401 Unauthorized: La petición requiere autenticación.
  • 403 Forbidden: El servidor ha entendido la petición pero se niega a autorizarla.
  • 404 Not Found: El recurso solicitado no existe.
fetch('https://api.ejemplo.com/recurso-inexistente')
  .then(respuesta => {
    if (respuesta.status === 404) {
      console.log('El recurso no existe');
    } else if (respuesta.status === 401) {
      console.log('Necesitas autenticarte');
    }
    return respuesta.json().catch(() => null);
  });

5xx - Error del servidor

Estos códigos indican que ha habido un error por parte del servidor.

  • 500 Internal Server Error: Error genérico del servidor.
  • 502 Bad Gateway: El servidor actúa como puerta de enlace y recibió una respuesta inválida.
  • 503 Service Unavailable: El servidor no está disponible temporalmente.

Solicitudes con y sin cuerpo

No todos los métodos HTTP requieren o permiten un cuerpo en la petición.

Métodos que normalmente no incluyen cuerpo

  • GET
  • HEAD
  • DELETE (aunque puede incluirlo en algunas implementaciones)
// Petición sin cuerpo
fetch('https://api.ejemplo.com/productos?categoria=electronicos', {
  method: 'GET'
})
  .then(respuesta => respuesta.json())
  .then(datos => console.log(datos));

Métodos que normalmente incluyen cuerpo

  • POST
  • PUT
  • PATCH
// Petición con cuerpo
const producto = {
  nombre: 'Teclado mecánico',
  precio: 89.99,
  disponible: true
};

fetch('https://api.ejemplo.com/productos', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(producto)
})
  .then(respuesta => respuesta.json())
  .then(datos => console.log(datos));

RESTful API basics

REST (Representational State Transfer) es un estilo arquitectónico para diseñar servicios web. Una API RESTful utiliza los métodos HTTP de manera significativa para operaciones sobre recursos.

Principios de REST

  • Recursos identificados por URLs: Cada recurso está identificado por una URL única.
  • Operaciones mediante métodos HTTP: Usar métodos HTTP para indicar la operación a realizar.
  • Representaciones: Los recursos pueden tener múltiples representaciones (JSON, XML, etc.).
  • Sin estado: Cada petición debe contener toda la información necesaria.

Mapeo típico de operaciones CRUD a métodos HTTP

Operación Método HTTP URL Descripción
Crear POST /recursos Crear un nuevo recurso
Leer GET /recursos /recursos/:id Obtener todos los recursos o uno específico
Actualizar PUT/PATCH /recursos/:id Actualizar un recurso existente
Eliminar DELETE /recursos/:id Eliminar un recurso específico
// Ejemplo de operaciones CRUD en una API RESTful

// Crear un recurso (CREATE)
fetch('https://api.ejemplo.com/tareas', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({titulo: 'Aprender REST', completada: false})
});

// Obtener recursos (READ)
fetch('https://api.ejemplo.com/tareas');
fetch('https://api.ejemplo.com/tareas/123');

// Actualizar un recurso (UPDATE)
fetch('https://api.ejemplo.com/tareas/123', {
  method: 'PUT',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({titulo: 'Aprender REST', completada: true})
});

// Eliminar un recurso (DELETE)
fetch('https://api.ejemplo.com/tareas/123', {
  method: 'DELETE'
});

Buenas prácticas en la comunicación HTTP

Seguir estas buenas prácticas mejorará la calidad y mantenibilidad de tu código cuando trabajes con peticiones HTTP:

1. Usar los métodos HTTP de manera semántica

// Correcto: Usar GET para obtener datos
fetch('/api/productos', { method: 'GET' });

// Correcto: Usar POST para crear
fetch('/api/productos', { 
  method: 'POST', 
  body: JSON.stringify({nombre: 'Producto nuevo'}) 
});

// Incorrecto: Usar GET para modificar datos
fetch('/api/eliminar-producto?id=123', { method: 'GET' });

2. Manejar adecuadamente los errores

fetch('https://api.ejemplo.com/datos')
  .then(respuesta => {
    if (!respuesta.ok) {
      throw new Error(`Error HTTP: ${respuesta.status}`);
    }
    return respuesta.json();
  })
  .then(datos => {
    console.log('Datos recibidos:', datos);
  })
  .catch(error => {
    console.error('Problema con la petición:', error);
  });

3. Usar headers apropiados

fetch('https://api.ejemplo.com/datos', {
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  }
});

4. Validar datos de entrada y salida

const enviarFormulario = (datos) => {
  // Validar antes de enviar
  if (!datos.nombre || !datos.email) {
    console.error('Datos incompletos');
    return Promise.reject('Datos incompletos');
  }
  
  return fetch('/api/usuarios', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(datos)
  });
};

5. Implementar timeout para peticiones

const fetchConTimeout = (url, opciones, tiempo = 5000) => {
  // Creamos una promesa que se rechaza después del tiempo especificado
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Tiempo de espera agotado')), tiempo);
  });
  
  // Competimos entre la petición real y el timeout
  return Promise.race([
    fetch(url, opciones),
    timeoutPromise
  ]);
};

fetchConTimeout('https://api.ejemplo.com/datos', {}, 3000)
  .then(respuesta => respuesta.json())
  .then(datos => console.log(datos))
  .catch(error => console.error('Error:', error));

Resumen

En este artículo hemos explorado los métodos HTTP que son fundamentales para la comunicación entre clientes y servidores web. Hemos visto los cuatro métodos principales (GET, POST, PUT y DELETE) y comprendido sus propósitos, así como los conceptos de idempotencia y seguridad. También hemos aprendido sobre los headers HTTP comunes, los códigos de estado, y cómo todo esto se aplica en el diseño de APIs RESTful.

Dominar estos conceptos es esencial para desarrollar aplicaciones web modernas, ya que la mayoría de las interacciones entre el frontend y el backend se basan en estos principios. En el próximo artículo, profundizaremos en el consumo de APIs externas, donde aplicaremos estos conocimientos para interactuar con servicios web de terceros.