Construir

Crear una API REST con Flask paso a paso

Una API REST funcional en Flask: los cuatro métodos (GET, POST, PUT, DELETE), respuestas JSON, códigos de estado correctos y manejo de errores. Con los detalles que separan una API de juguete de una usable.

Una API REST es la forma en que tu backend le habla a otras aplicaciones: una app móvil, un frontend en Angular o React, u otro servicio. En lugar de devolver páginas HTML, devuelve datos en JSON. Aquí construimos una completa en Flask, con los cuatro métodos y las buenas prácticas que la hacen usable de verdad.

Trabajaremos con una API de tareas (todos) en memoria, para centrarnos en la estructura sin distraernos con la base de datos.

La base

from flask import Flask, jsonify, request

app = Flask(__name__)

# "Base de datos" en memoria (para el ejemplo)
tareas = [
    {"id": 1, "titulo": "Aprender Flask", "hecha": False},
    {"id": 2, "titulo": "Construir una API", "hecha": False},
]

jsonify es la pieza clave: convierte diccionarios y listas de Python en respuestas JSON con el encabezado correcto (Content-Type: application/json). No devuelvas nunca un diccionario con str() o similar; usa jsonify para que el cliente lo reciba bien.

GET — leer

Dos rutas: una para la lista completa y otra para una tarea concreta.

@app.route("/api/tareas", methods=["GET"])
def listar_tareas():
    return jsonify(tareas)

@app.route("/api/tareas/<int:tarea_id>", methods=["GET"])
def obtener_tarea(tarea_id):
    tarea = next((t for t in tareas if t["id"] == tarea_id), None)
    if tarea is None:
        return jsonify({"error": "Tarea no encontrada"}), 404
    return jsonify(tarea)

Fíjate en el <int:tarea_id>: el int: obliga a que el id sea numérico y lo convierte automáticamente. Y cuando la tarea no existe, devolvemos 404, no un 200 con lista vacía. Ese código de estado correcto es lo que distingue una API profesional: el cliente sabe qué pasó por el número, no por el texto.

POST — crear

@app.route("/api/tareas", methods=["POST"])
def crear_tarea():
    datos = request.get_json()

    if not datos or "titulo" not in datos:
        return jsonify({"error": "Falta el campo 'titulo'"}), 400

    nueva = {
        "id": max((t["id"] for t in tareas), default=0) + 1,
        "titulo": datos["titulo"],
        "hecha": False,
    }
    tareas.append(nueva)
    return jsonify(nueva), 201

Tres cosas importantes aquí. request.get_json() lee el cuerpo JSON que envía el cliente. Siempre valida que los datos existan antes de usarlos: si falta el título, devuelve 400 (petición incorrecta) en lugar de reventar con un error interno. Y al crear con éxito, el código es 201 (creado), no 200: otro detalle que un cliente bien hecho espera.

PUT — actualizar

@app.route("/api/tareas/<int:tarea_id>", methods=["PUT"])
def actualizar_tarea(tarea_id):
    tarea = next((t for t in tareas if t["id"] == tarea_id), None)
    if tarea is None:
        return jsonify({"error": "Tarea no encontrada"}), 404

    datos = request.get_json()
    tarea["titulo"] = datos.get("titulo", tarea["titulo"])
    tarea["hecha"] = datos.get("hecha", tarea["hecha"])
    return jsonify(tarea)

El datos.get("titulo", tarea["titulo"]) es un truco útil: si el cliente envía un título nuevo, lo usa; si no lo envía, conserva el actual. Así el cliente puede actualizar solo lo que quiera sin borrar el resto.

DELETE — borrar

@app.route("/api/tareas/<int:tarea_id>", methods=["DELETE"])
def borrar_tarea(tarea_id):
    global tareas
    tarea = next((t for t in tareas if t["id"] == tarea_id), None)
    if tarea is None:
        return jsonify({"error": "Tarea no encontrada"}), 404

    tareas = [t for t in tareas if t["id"] != tarea_id]
    return jsonify({"mensaje": "Tarea eliminada"}), 200

Probar la API

Sin frontend todavía, la pruebas desde la terminal con curl:

# Listar
curl http://localhost:5000/api/tareas

# Crear
curl -X POST http://localhost:5000/api/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo": "Nueva tarea"}'

# Borrar
curl -X DELETE http://localhost:5000/api/tareas/1

El encabezado -H "Content-Type: application/json" en el POST es obligatorio: sin él, request.get_json() devuelve None y la validación falla. Es el tropiezo número uno al probar APIs.

Resumen de códigos de estado

Los que usa una API REST bien hecha, y que vale la pena memorizar:

  • 200 OK — todo salió bien (GET, PUT, DELETE exitosos).
  • 201 Created — recurso creado (POST exitoso).
  • 400 Bad Request — el cliente envió datos incorrectos o incompletos.
  • 404 Not Found — el recurso pedido no existe.

Devolver el código correcto no es un adorno: es cómo tu API se comunica con quien la consume. Un frontend bien hecho reacciona distinto ante un 201 que ante un 400, y depende de que tú los devuelvas bien.

Con esto tienes una API REST completa y correcta. El siguiente paso natural es reemplazar la lista en memoria por una base de datos real, para que los datos persistan.


¿Vas a conectarla a una base de datos o a protegerla con autenticación por token? Escríbeme desde contacto y lo cubrimos.

← Más de Construir