API RESTful (API)
Acceso a Datos — 2º DAM
Jaume Llinàs Sansó
- Descripción general
- Estructura del proyecto
- Requisitos
- Variables de entorno (.env)
- Modelos de datos (Schemas)
- Endpoints principales
- Implementación de GraphQL
- Despliegue en local
- Despliegue en entorno cloud
- Uso de IA y recursos
Note
Esta aplicación está desarrollada con FastAPI, un framework de Python.
El proyecto sakilaAPI tiene como objetivo exponer una API RESTful sobre la base de datos sakila, que permite a un usuario autenticado realizar operaciones CRUD sobre clientes (customers) y reservas (rentals).
El acceso a la API se controla mediante autenticación con tokens JWT, generados al iniciar sesión con credenciales válidas.
Important
Para poder implementar autenticación en este proyecto, deberemos crear una tabla users en la propia base de datos sakila. Yo he seguido este esquema:
CREATE TABLE user (
user_id int NOT NULL AUTO_INCREMENT,
username varchar(56) NOT NULL,
email varchar(104) NOT NULL,
hashed_password varchar(255) NOT NULL,
disabled tinyint(1) DEFAULT '0',
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
last_update timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (user_id),
UNIQUE KEY username (username),
UNIQUE KEY email (email)
);sakilaAPI/
├── app/
│ ├── api/
│ │ ├── v1/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ ├── customers.py
│ │ │ └── rentals.py
│ │ └── deps.py
│ ├── core/
│ │ ├── config.py
│ │ └── security.py
│ ├── db/
│ │ └── database.py
│ └── schemas/
│ ├── customer.py
│ ├── rental.py
│ └── user.py
├── Dockerfile
├── README.md
├── docker-compose.yaml
├── main.py
└── requirements.txt
- Python 3.12+
- fastapi
- uvicorn
- mariadb o mysqlclient
- pydantic
- python-dotenv
- passlib
- python-jose
Important
Esta guía de instalación asume que el usuario ya dispone de una base de datos a la que conectarse. Por tanto, toda la parte de instalación de MariaDB y el volcado de las bases de datos al programa es omitida.
- MARIADB_USER
- MARIADB_PASSWORD
- MARIADB_HOST
- MARIADB_PORT
- MARIADB_DATABASE
- FASTAPI_PORT
- SECRET_KEY
- ACCESS_TOKEN_EXPIRE_MINUTES
Los modelos están definidos en app/schemas/ y controlan la validación de datos en las operaciones de entrada/salida.
- customer: creación, actualización y respuesta de clientes.
- rental: creación y respuesta de reservas.
- user: registro y respuesta de usuarios autenticados.
{
"store_id": 1,
"first_name": "Miquel Àngel",
"last_name": "Montero",
"email": "mamonterop@santjosepobrer.es",
"address_id": 5,
"active": true
}{
"rental_date": "2024-12-02T10:00:00",
"inventory_id": 1,
"customer_id": 1,
"staff_id": 1
}{
"username": "jllinass",
"email": "jllinass@alumnes.santjosepobrer.es",
"password": "miquelangelponmeun10"
}app/api/v1/auth.py
| Método | Endpoint | Descripción |
|---|---|---|
POST |
/auth/register |
Crea un nuevo usuario en la base de datos |
POST |
/auth/login |
Autentica un usuario y devuelve un token JWT |
a]. Registrar usuario
POST /auth/register
{
"username": "jllinass",
"email": "jllinass@alumnes.santjosepobrer.es",
"password": "miquelangelponmeun10"
}{
"user_id": 1,
"username": "jllinass",
"email": "jllinass@alumnes.santjosepobrer.es",
"disabled": false
}b]. Login
POST /auth/login
{
"username": "jllinass",
"password": "miquelangelponmeun10"
}{
"access_token": "eyJhbGci...vdCI6IkpXVCJ9...",
"token_type": "bearer"
}Important
Este token JWT debe incluirse en la cabecera de autorización de todas las peticiones protegidas:
Authorization: Bearer <token>
app/api/v1/customers.py
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/customers/ |
Devuelve la lista de todos los clientes |
GET |
/customers/{customer_id} |
Devuelve los datos de un cliente específico |
POST |
/customers/ |
Crea un nuevo cliente |
PUT |
/customers/{customer_id} |
Actualiza los datos de un cliente existente |
DELETE |
/customers/{customer_id} |
Elimina un cliente |
c]. Obtener todos los clientes
GET /customers/
{
"customer_id": 1,
"store_id": 1,
"first_name": "MARY",
"last_name": "SMITH",
"email": "MARY.SMITH@sakilacustomer.org",
"address_id": 5,
"active": true,
"create_date": "2006-02-14T22:04:36",
"last_update": "2025-12-28T11:07:09"
}d]. Obtener cliente específico
GET /customers/84
{
"customer_id": 84,
"store_id": 2,
"first_name": "SARA",
"last_name": "PERRY",
"email": "SARA.PERRY@sakilacustomer.org",
"address_id": 88,
"active": true,
"create_date": "2006-02-14T22:04:36",
"last_update": "2006-02-15T04:57:20"
}e]. Crear nuevo cliente
POST /customers/
{
"store_id": 1,
"first_name": "Beatriz",
"last_name": "Morales",
"email": "bmoralesg@santjosepobrer.es",
"address_id": 8,
"active": true
}{
"customer_id": 46,
"store_id": 1,
"first_name": "Beatriz",
"last_name": "Morales",
"email": "bmoralesg@santjosepobrer.es",
"address_id": 8,
"active": true,
"create_date": "2024-12-02T10:00:00",
"last_update": "2024-12-02T10:00:00"
}f]. Actualizar cliente
PUT /customers/601
{
"store_id": 1,
"first_name": "Jaume",
"last_name": "Llinàs Sansó",
"email": "jaume@llinassanso.com",
"address_id": 5,
"active": false
}{
"customer_id": 601,
"store_id": 1,
"first_name": "Jaume",
"last_name": "Llinàs Sansó",
"email": "jaume@llinassanso.com",
"address_id": 5,
"active": false,
"create_date": "2025-12-28T13:48:25",
"last_update": "2026-01-08T16:04:48"
}g]. Eliminar cliente concreto
DELETE /customers/601
{
"Cliente eliminado correctamente."
}app/api/v1/rentals.py
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/rentals/ |
Lista todas las reservas |
GET |
/rentals/{rental_id} |
Devuelve una reserva concreta |
POST |
/rentals/ |
Crea una nueva reserva |
DELETE |
/rentals/{rental_id} |
Elimina una reserva |
h]. Listar todas las reservas
GET /rentals
{
"rental_id": 16050,
"rental_date": "2025-12-28T13:49:23",
"inventory_id": 2134,
"customer_id": 601,
"return_date": "2025-12-28T13:50:10",
"staff_id": 2,
"last_update": "2025-12-28T13:50:10"
},
{
"rental_id": 11739,
"rental_date": "2006-02-14T15:16:03",
"inventory_id": 4568,
"customer_id": 373,
"return_date": null,
"staff_id": 2,
"last_update": "2006-02-15T21:30:53"
}i]. Devuelve una reserva concreta
GET /rentals/16050
{
"rental_id": 16050,
"rental_date": "2025-12-28T13:49:23",
"inventory_id": 2134,
"customer_id": 601,
"return_date": "2025-12-28T13:50:10",
"staff_id": 2,
"last_update": "2025-12-28T13:50:10"
}j]. - Crear nueva reserva
POST /rentals/
{
"rental_date": "2024-12-02T10:00:00",
"inventory_id": 10,
"customer_id": 3,
"staff_id": 2
}{
"rental_id": 78,
"rental_date": "2024-12-02T10:00:00",
"inventory_id": 10,
"customer_id": 3,
"return_date": null,
"staff_id": 2,
"last_update": "2024-12-02T10:00:00"
}k]. Eliminar una reserva
DELETE /rentals/14023
{
"Reserva eliminada correctamente."
}Tras investigar el propósito de GraphQL y analizar los requisitos de este proyecto, hemos llegado a la conclusión inicial de que implementar GraphQL en el mismo sería añadir una capa de complejidad innecesaria al proyecto, ya que de momento nuestra API no es usada en aplicaciones móviles ni recibe muchísimas peticiones simultáneas.
No obstante, si en un futuro el videoclub "sakila" se expandiera de forma considerable y nuestra API per se no diera abasto, sería interesante estudiar la implementación de esta tecnología.
Para documentar el despliegue de nuestras apps, asumiremos que el usuario no tiene ninguno de nuestros requisitos instalados excepto tener un despliegue operativo de una base de datos MariaDB o MySQL.
El proceso para llevar a cabo el despliegue en local consiste en:
- Instalar la última versión de Python.
- Instalar el conector para C de MariaDB o MySQL.
- Clonar nuestro proyecto de GitHub en la máquina.
- Abrir una terminal y navegar hasta el directorio donde se encuentra nuestra app.
- En dicho directorio, crear un
venvy activarlo. - Instalar los requisitos con
pip install -r requirements.txt - Rellenar el archivo .env del proyecto con las variables necesarias.
- Ejecutar el comando
uvicorn main:app --reload.
Asumiendo que en las variables de entorno hemos escogido el puerto 8000 para FastAPI, este proceso levantará nuestra API en la URL http://127.0.0.1:8000.
De forma extra y siendo realizada esta parte tras haber acabado la actividad en sí, he dockerizado y desplegado la aplicación en un servidor VPS propio. Mi entorno de despliegue consiste en tres contenedores:
sakila-apimysql-servernginx-proxy-manager
Para lograr conexión entre todas las partes, he creados dos redes internas: sql y inter. En la primera red están conectados sakila-api y sql para poder intercambiar información entre sí. En la segunda red están conectados sakila-api y nginx-proxy-manager, consiguiendo desplegar nuestra aplicación hacia Internet sin tener que exponer nuestra propia red interna. Además, usando Nginx agilizo la generación de certificados SSL a través del Certbot de Let's Encrypt.
Explicado esto, el proceso de despliegue es el siguiente:
- Clonar nuestro proyecto de GitHub en el servidor.
- Abrir una terminal y navegar hasta el directorio donde se encuentra nuestra app.
- En dicho directorio, crear un archivo
.envy rellenarlo con nuestras variables de entorno. - Ejecutar el comando
docker compose build --no-cache. - Cuando el proyecto termine de descargarse y configurarse, deployear la aplicación con el comando
docker compose up.
Con estos pasos, tendríamos nuestra aplicación corriendo en el puerto 8081 de nuestro Docker. Para acceder a la aplicación en sí, accederemos al dashboard de Nginx Proxy Manager y crearemos una redirección de la URL http://sakilaweb-app-1:8081 a la URL de nuestra elección.
En mi caso, y aprovechando un dominio que reservo para este tipo de experimentos, mi contenedor con la API apunta a https://apisakila.jaume.wtf.
Este proyecto ha sido mayoritariamente elaborado guiándome por posts de foros del estilo StackOverflow, así como la documentación oficial de FastAPI. No obstante, para funciones específicas como el solucionado de problemas con los tokens JWT o el afinado final de la documentación con Swagger se ha usado la inteligencia artificial Claude a modo de corrector, pasándole el script entero y comentando (no corrigiendo directamente) las partes que no le gustaban.