En este tutorial crearemos un proyecto web FullStack usando Python en el backend junto a su Framework llamado FastAPI y a Javascript en el frontend junto a su Framework llamado React. Ademas que tambien usaremos una base de datos como lo es Mongodb.
Requerimientos
Antes de hacer este tutorial es esencial contar con las siguientes bases:
- Conocer las bases de Python
- Tener Mongodb instalado
- FastAPI, saber que es FastAPI
creacion del proyecto
python -m venv venv
Active el entorno virtual o si estas usando VSCode simplemente selecciona la carpeta venv
pip install fastapi uvicorn motor
Incialización del servidor
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost:5173",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def welcome():
return {"message": "Welcome to the API!"}
uvicorn main:app --reload
Añade estas rutas
@app.get('/api/tasks')
async def get_tasks():
return {"data": "All tasks"}
@app.get('/api/tasks/{id}')
async def get_task(id: int):
return {"data": id}
@app.post('/api/tasks')
async def create_task():
return {"data": "Task created"}
@app.put('/api/tasks/{id}')
async def update_task(id: int, data):
return {"data": f"Task {id} has been updated"}
@app.delete('/api/tasks/{id}')
async def delete_task(id: int):
return {"data": f"Task {id} has been deleted"}
Puedes ver estas rutas si vas en /docs
Mongodb Queries
Luego de crear las rutas vamos a crear una conexion con Mongodb y vamos a definir las rutas que necesitamos crear, en un archivo llamado database.py
async def get_one_task(id):
task = await collection.find_one({"_id": id})
return task
Luego tambien vamos a crear un archivo Models:
from typing import Optional
from pydantic import BaseModel, Field
from bson import ObjectId
class PyObjectId(ObjectId):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if not ObjectId.is_valid(v):
raise ValueError('Invalid ObjectId')
return str(v)
class Task(BaseModel):
id: Optional[PyObjectId] = Field(alias='_id')
title: str
description: str
completed: bool = False
class Config:
orm_mode = True
allow_population_by_field_name = True
json_encoders = {
ObjectId: str
}
class UpdateTask(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
completed: Optional[bool] = None
Actualizacion de Rutas
Una vez tengamos los modelos y las consultas de Mongodb podemos actualizar nuestras rutas de esta forma:
@app.get("/")
def welcome():
return {"message": "Welcome to the API!"}
@app.get('/api/tasks')
async def get_tasks():
response = await get_all_tasks()
return response
@app.get('/api/tasks/{id}', response_model=Task)
async def get_task(id: int):
response = await get_one_task(id)
if response:
return response
raise HTTPException(404, f"There is no task with the id {id}")
@app.post('/api/tasks', response_model=Task)
async def save_task(task: Task):
taskFound = await get_one_task(task.title)
if taskFound:
raise HTTPException(409, "Task already exists")
response = await create_task(task.dict())
print(response)
if response:
return response
raise HTTPException(400, "Something went wrong")
@app.put('/api/tasks/{id}', response_model=Task)
async def put_task(id: int, data):
response = await update_task(id, data)
if response:
return response
raise HTTPException(404, f"There is no task with the id {id}")
@app.delete('/api/tasks/{id}')
async def remove_task(id: int):
response = await delete_task(id)
if response:
return "Successfully deleted task"
raise HTTPException(404, f"There is no task with the id {id}")
Separacion de Rutas
Una vez creadas la rutas es buena idea separarlas en su propio archivo, debido a que luego podamos necesitar crear mas rutas o endpoints.
Crea una carpeta llamada routes
en la raiz del proyecto y dentro un archivo llamado task.py
con el siguiente código:
@app.get("/")
def welcome():
return {"message": "Welcome to the API!"}
@app.get('/api/tasks')
async def get_tasks():
response = await get_all_tasks()
return response
@app.get('/api/tasks/{id}', response_model=Task)
async def get_task(id: int):
response = await get_one_task(id)
if response:
return response
raise HTTPException(404, f"There is no task with the id {id}")
@app.post('/api/tasks', response_model=Task)
async def save_task(task: Task):
taskFound = await get_one_task(task.title)
if taskFound:
raise HTTPException(409, "Task already exists")
response = await create_task(task.dict())
print(response)
if response:
return response
raise HTTPException(400, "Something went wrong")
@app.put('/api/tasks/{id}', response_model=Task)
async def put_task(id: int, data):
response = await update_task(id, data)
if response:
return response
raise HTTPException(404, f"There is no task with the id {id}")
@app.delete('/api/tasks/{id}')
async def remove_task(id: int):
response = await delete_task(id)
if response:
return "Successfully deleted task"
raise HTTPException(404, f"There is no task with the id {id}")
Luego en cuanto al archivo main.py, actualicemoslo de esta forma:
Despliegue
Para desplegar este proyecto en Railway, vamos a tener que considerar que tenemos dos aplicaciones, pero ambas estan en un mismo repositorio de Git, lo que significa que tenemos un monorepo, asi que para desplegarlo railway nos permite especifica que carpeta dentro del repositorio queremos desplegar y cada uno ira con su propio archivo de configuracion de railway, en donde una puede usar Nodejs, y la otra Python, y ejecutando indpendientemente sus modulos y comandos.
Los pasos que hare son:
- Crear una base de datos de Mongodb en Producción
- Configurar y desplegar Backend
- Configurar y desplegar Frontend
- Actualizar variables de entorno
pip freeze > requirements.txt
añade railway.json en la raiz de tu carpeta backend
:
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS"
},
"deploy": {
"startCommand": "uvicorn main:app --host 0.0.0.0 --port $PORT",
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}
Luego en Railway, tenemos que establecer las variables de entorno de nuestra aplicacion de backend:
FRONTEND_URL=http://localhost:5173
MONGO_URL=${{MongoDB.MONGO_URL}}
ve en railway.com
Tambien este es una plantilla de ejemplo de que configuración añadir: https://github.com/railwayapp-templates/fastapi
En cuanto al Frontend
Para desplegar el frontend, solo tenemos que crear un servidor HTTP:
npm i serve
Y luego añadir esta configuracion en nuestro package.json:
"start": "serve build -s -n -L -p $PORT",
Tambien recuerda que el Frontend es necesario que sepa a donde pedir datos, por lo que tambien tenemos que configurar su variable de entorno:
VITE_APP=http://railwaybackendurl.com
Tambien tenemos una plantilla de ejemplo de configuración
Más Recursos
- Monorepos en Railway, Un recurso de Railway para aprender a desplegar monorepos,
- https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/#creating-the-application
- https://stackoverflow.com/questions/69504352/fastapi-get-request-results-in-typeerror-value-is-not-a-valid-dict
- https://github.com/railwayapp-templates/fastapi
- https://stackoverflow.com/questions/62928450/how-to-put-backend-and-frontend-together-returning-react-frontend-from-fastapi