{"id":57,"date":"2025-06-06T09:51:28","date_gmt":"2025-06-06T09:51:28","guid":{"rendered":"https:\/\/vicfolio.com\/blog\/?p=57"},"modified":"2025-06-06T09:51:28","modified_gmt":"2025-06-06T09:51:28","slug":"construye-tu-primera-api-rest-con-fastapi-guia-paso-a-paso-desde-cero","status":"publish","type":"post","link":"https:\/\/vicfolio.com\/blog\/?p=57","title":{"rendered":"Construye tu primera API REST con FastAPI: Gu\u00eda paso a paso desde cero"},"content":{"rendered":"\n<p>En esta gu\u00eda, te mostrar\u00e9 c\u00f3mo crear una API REST completa utilizando el framework Python FastAPI. Nuestro proyecto consistir\u00e1 en un sistema de gesti\u00f3n de tareas (TODO API) con funcionalidades CRUD (Create, Read, Update, Delete), autenticaci\u00f3n JWT, validaci\u00f3n de datos con Pydantic y almacenamiento de datos en una base de datos SQLite.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Setup del entorno e instalaci\u00f3n<\/h2>\n\n\n\n<p>Para comenzar, necesitamos instalar los siguientes paquetes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>FastAPI<\/li>\n\n\n\n<li>Pydantic<\/li>\n\n\n\n<li>sqlite3<\/li>\n\n\n\n<li>uvicorn (para ejecutar nuestra API)<\/li>\n<\/ul>\n\n\n\n<p>Puedes instalarlos mediante pip:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install fastapi pydantic sqlite3 uvicorn<\/code><\/pre>\n\n\n\n<p>Crea un nuevo directorio para tu proyecto y agrega un archivo <code>main.py<\/code> vac\u00edo. Este archivo ser\u00e1 el punto de entrada de nuestra aplicaci\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Primera ruta y \u00abHello World\u00bb<\/h2>\n\n\n\n<p>En este paso, creamos nuestra primera ruta en la API. Agrega el siguiente c\u00f3digo a <code>main.py<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get(\"\/\")\nasync def root():\n    return {\"message\": \"Hello World\"}<\/code><\/pre>\n\n\n\n<p>Esto define una ruta GET en <code>\/<\/code> que devuelve un objeto JSON con un mensaje de bienvenida. Ejecuta la aplicaci\u00f3n con:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>uvicorn main:app --host 0.0.0.0 --port 8000<\/code><\/pre>\n\n\n\n<p>Luego, accede a http:\/\/localhost:8000\/ para ver el resultado.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Modelos de datos con Pydantic<\/h2>\n\n\n\n<p>Para definir nuestros modelos de datos, crearemos un archivo <code>models.py<\/code> y agregaremos los siguientes modelos:<\/p>\n\n\n\n<pre class=\"wp-block-code has-base-color has-text-color has-link-color wp-elements-c61eb6f094207f727e09b11e89314db8\"><code>from pydantic import BaseModel\n\nclass Task(BaseModel):\n    id: int\n    title: str\n    description: str\n\nclass User(BaseModel):\n    id: int\n    username: str\n    password: str<\/code><\/pre>\n\n\n\n<p>Estos modelos definen las estructuras de los datos que se van a utilizar en nuestra API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Conexi\u00f3n a base de datos<\/h2>\n\n\n\n<p>Creamos un archivo <code>database.py<\/code> y agregamos la siguiente conexi\u00f3n a una base de datos SQLite:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import sqlite3\n\nclass Database:\n    def __init__(self):\n        self.conn = sqlite3.connect(\"tasks.db\")\n        self.cursor = self.conn.cursor()\n\n    def create_table(self):\n        self.cursor.execute(\"\"\"\n            CREATE TABLE IF NOT EXISTS tasks (\n                id INTEGER PRIMARY KEY,\n                title TEXT NOT NULL,\n                description TEXT NOT NULL\n            );\n        \"\"\")\n\n    def insert_task(self, task: Task):\n        self.cursor.execute(\n            \"INSERT INTO tasks (title, description) VALUES (:title, :description)\",\n            {\"title\": task.title, \"description\": task.description}\n        )\n        self.conn.commit()\n\n    def get_tasks(self):\n        self.cursor.execute(\"SELECT * FROM tasks\")\n        return &#91;Task(**row) for row in self.cursor.fetchall()]<\/code><\/pre>\n\n\n\n<p>Este archivo define una clase <code>Database<\/code> que se encarga de conectarse a la base de datos, crear una tabla para almacenar tareas y insertar y obtener tareas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Endpoints CRUD con validaciones<\/h2>\n\n\n\n<p>Agregamos los siguientes endpoints a <code>main.py<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from fastapi import FastAPI, HTTPException\nfrom pydantic import ValidationError\nfrom models import Task\nfrom database import Database\n\napp = FastAPI()\n\ndb = Database()\ndb.create_table()\n\n@app.post(\"\/tasks\")\nasync def create_task(task: Task):\n    try:\n        db.insert_task(task)\n        return {\"message\": \"Task created successfully\"}\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n@app.get(\"\/tasks\")\nasync def get_tasks():\n    tasks = db.get_tasks()\n    return &#91;{\"id\": task.id, \"title\": task.title, \"description\": task.description} for task in tasks]\n\n@app.get(\"\/tasks\/{task_id}\")\nasync def get_task(task_id: int):\n    try:\n        task = next((task for task in db.get_tasks() if task.id == task_id), None)\n        return {\"id\": task.id, \"title\": task.title, \"description\": task.description}\n    except StopIteration:\n        raise HTTPException(status_code=404, detail=\"Task not found\")\n\n@app.put(\"\/tasks\/{task_id}\")\nasync def update_task(task_id: int, task: Task):\n    try:\n        db.insert_task(task)\n        return {\"message\": \"Task updated successfully\"}\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n@app.delete(\"\/tasks\/{task_id}\")\nasync def delete_task(task_id: int):\n    try:\n        db.cursor.execute(\"DELETE FROM tasks WHERE id = :id\", {\"id\": task_id})\n        db.conn.commit()\n        return {\"message\": \"Task deleted successfully\"}\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))<\/code><\/pre>\n\n\n\n<p>Estos endpoints permiten crear, leer, actualizar y eliminar tareas. Se utilizan las clases de modelo <code>Task<\/code> y la clase <code>Database<\/code> para interactuar con la base de datos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Sistema de autenticaci\u00f3n<\/h2>\n\n\n\n<p>Creamos un archivo <code>auth.py<\/code> y agregamos el siguiente c\u00f3digo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from fastapi import FastAPI, HTTPException\nfrom pydantic import ValidationError\nfrom models import User\n\napp = FastAPI()\n\n@app.post(\"\/login\")\nasync def login(username: str, password: str):\n    try:\n        user = next((user for user in users if user.username == username and user.password == password), None)\n        if user:\n            return {\"access_token\": \"secret\"}\n        else:\n            raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n    except StopIteration:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n@app.get(\"\/protected\")\nasync def protected_route(access_token: str):\n    if access_token == \"secret\":\n        return {\"message\": \"Hello, authenticated user!\"}\n    else:\n        raise HTTPException(status_code=403, detail=\"Forbidden\")<\/code><\/pre>\n\n\n\n<p>Este sistema de autenticaci\u00f3n utiliza endpoints para loguearse y obtener un token de acceso. El endpoint <code>\/protected<\/code> solo permite acceder a aquellos que tienen el token correcto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Manejo de errores HTTP<\/h2>\n\n\n\n<p>En lugar de manejar errores con <code>try\/except<\/code>, podemos utilizar las excepciones HTTP definidas en FastAPI. Por ejemplo, en los endpoints CRUD, podemos lanzar una excepci\u00f3n HTTP 404 si no se encuentra la tarea:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@app.get(\"\/tasks\/{task_id}\")\nasync def get_task(task_id: int):\n    try:\n        task = next((task for task in db.get_tasks() if task.id == task_id), None)\n        return {\"id\": task.id, \"title\": task.title, \"description\": task.description}\n    except StopIteration:\n        raise HTTPException(status_code=404, detail=\"Task not found\")<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">8. Testing de la API<\/h2>\n\n\n\n<p>Creamos un archivo <code>test_api.py<\/code> y agregamos los siguientes tests con pytest:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pytest\nfrom fastapi.testclient import TestClient\n\n@pytest.fixture\ndef client():\n    return TestClient(app)\n\ndef test_root(client):\n    response = client.get(\"\/\")\n    assert response.status_code == 200\n    assert response.json() == {\"message\": \"Hello World\"}\n\ndef test_create_task(client, task: Task):\n    response = client.post(\"\/tasks\", json=task.dict())\n    assert response.status_code == 201\n    assert response.json() == {\"message\": \"Task created successfully\"}\n\ndef test_get_tasks(client):\n    response = client.get(\"\/tasks\")\n    assert response.status_code == 200\n    assert response.json() == &#91;]\n\ndef test_login(client, username: str, password: str):\n    response = client.post(\"\/login\", json={\"username\": username, \"password\": password})\n    assert response.status_code == 200\n    assert response.json() == {\"access_token\": \"secret\"}\n\ndef test_protected_route(client):\n    response = client.get(\"\/protected\")\n    assert response.status_code == 403<\/code><\/pre>\n\n\n\n<p>Estos tests verifican que los endpoints est\u00e9n funcionando correctamente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9. Documentaci\u00f3n y deployment<\/h2>\n\n\n\n<p>Para generar documentaci\u00f3n autom\u00e1tica, podemos utilizar la biblioteca Swagger UI. Agregamos el siguiente c\u00f3digo a <code>main.py<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from fastapi import FastAPI, OpenAPI\nfrom uvicorn.main import run\n\napp = FastAPI()\n\napp.openapi = OpenAPI(\n    title=\"Todo API\",\n    description=\"A simple Todo API built with FastAPI and SQLite\",\n    version=\"1.0\"\n)\n\nif __name__ == \"__main__\":\n    run(app, host=\"0.0.0.0\", port=8000)<\/code><\/pre>\n\n\n\n<p>Para deployment, podemos crear un archivo <code>docker-compose.yml<\/code> y agregar el siguiente contenido:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>version: \"3\"\n\nservices:\n  app:\n    build: .\n    ports:\n      - \"8000:8000\"\n    environment:\n      DATABASE_URL: sqlite:\/\/\/tasks.db\n    depends_on:\n      - db\n\n  db:\n    image: postgres\n    volumes:\n      - tasks.db:\/var\/lib\/postgresql\/data\/tasks.db\n\nvolumes:\n  tasks.db:<\/code><\/pre>\n\n\n\n<p>Este archivo define un servicio <code>app<\/code> que se encarga de construir la imagen del contenedor y expone el puerto 8000. El servicio <code>db<\/code> se encarga de crear una base de datos PostgreSQL y montarla en el volumen <code>tasks.db<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10. README detallado<\/h2>\n\n\n\n<p>Agrega un archivo <code>README.md<\/code> con informaci\u00f3n detallada sobre c\u00f3mo ejecutar la aplicaci\u00f3n, como instalar los paquetes necesarios, c\u00f3mo configurar la base de datos y c\u00f3mo correr los tests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusi\u00f3n<\/h2>\n\n\n\n<p>\u00a1Eso es todo! Con este proyecto, hemos creado una API REST completa utilizando FastAPI y Pydantic. Espero que hayas disfrutado de esta gu\u00eda paso a paso y que te haya sido \u00fatil para aprender sobre APIs y desarrollo web con Python.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>En esta gu\u00eda, te mostrar\u00e9 c\u00f3mo crear una API REST completa utilizando el framework Python FastAPI. Nuestro proyecto consistir\u00e1 en un sistema de gesti\u00f3n de tareas (TODO API) con funcionalidades CRUD (Create, Read, Update, Delete), autenticaci\u00f3n JWT, validaci\u00f3n de datos con Pydantic y almacenamiento de datos en una base de datos SQLite. 1. Setup del [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":58,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[16,17,5],"class_list":["post-57","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programacion","tag-api","tag-fastapi","tag-python"],"_links":{"self":[{"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/57","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=57"}],"version-history":[{"count":1,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/57\/revisions"}],"predecessor-version":[{"id":59,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/57\/revisions\/59"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/58"}],"wp:attachment":[{"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=57"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=57"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vicfolio.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=57"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}