La pila FARM è una soluzione di sviluppo web moderna che comprende tre tecnologie potenti: FastAPI, React e MongoDB. Questa soluzione full-stack fornisce agli sviluppatori un set robusto di strumenti per costruire applicazioni web scalabili, efficienti e ad alte prestazioni.

In questo articolo, vi darò un’introduzione a ciascuna delle tecnologie chiave, e poi costruiremo un progetto utilizzando la pila FARM e Docker così da vedere come tutto funziona insieme.

Questo articolo è basato su un corso che ho creato sul canale YouTube di freeCodeCamp.org. Guardatelo qui:

Introduzione alla pila FARM

La FARM in pila FARM sta per:

  • F: FastAPI (Backend)

  • R: React (Frontend)

  • M: MongoDB (Database)

La pila FARM è progettata per sfruttare le virtù di ciascun componente, permettendo agli sviluppatori di creare applicazioni ricche di funzionalità con un’esperienza di sviluppo fluida.

Componenti della pila FARM

  1. FastAPI: FastAPI è una moderna piattaforma web per Python ad alta performance per la costruzione di API. È progettato per essere facile da usare, veloce da codificare e pronto per gli ambienti di produzione. FastAPI è costruito sopra Starlette per le parti web e Pydantic per le parti dati, rendendolo una scelta potente per la costruzione di servizi backend robusti.

  2. React: React è una popolare libreria JavaScript per la costruzione di interfacce utente. Sviluppato e mantenuto da Facebook, React permette ai sviluppatori di creare componenti UI riutilizzabili che aggiornano e renderizzano efficientemente i dati in mutamento. La sua architettura basata su componenti e il suo DOM virtuale lo rendono un’ottima scelta per la costruzione di applicazioni frontend dinamiche e responsive.

  3. MongoDB: MongoDB è un database NoSQL orientato ai documenti. Memorizza i dati in documenti flessibili, simili ai JSON, il che significa che i campi possono variare da documento a documento e la struttura dati può essere cambiata nel tempo. Questa flessibilità rende MongoDB un’ideale scelta per applicazioni che necessitano di evolversi velocemente e di gestire diversi tipi di dati.

Vantaggi dell’utilizzo della pila FARM

  1. Alta Performance: FastAPI è uno dei framework Python più veloci disponibili, mentre il virtual DOM di React garantisce aggiornamenti efficienti dell’UI. Il modello documentale di MongoDB consente letture e scritture veloci.

  2. Scalabilità: tutti i componenti della pila FARM sono progettati per essere scalabili. FastAPI può gestire efficientemente richieste concorrenti, le applicazioni React possono gestire UIs complessi, e MongoDB può distribuire i dati su multipli server.

  3. Community e Ecosistema: tutte e tre le tecnologie hanno comunità grandi e attive e un ricco ecosistema di librerie e strumenti.

  4. Flessibilità: Lo stack FARM è abbastanza flessibile da poter ospitare vari tipi di applicazioni web, dalle semplici app CRUD ai sistemi complessi e ad alta intensità di dati.

Combina queste tecnologie, lo stack FARM fornisce una soluzione completa per la creazione di applicazioni web moderne. Consente agli sviluppatori di creare backend veloci e scalabili con FastAPI, frontend intuitivi e responsivi con React e archiviazione dati flessibile ed efficiente con MongoDB. Questo stack è particolarmente adatto per applicazioni che richiedono aggiornamenti in tempo reale, modelli di dati complessi e elevate prestazioni.

Panoramica del progetto: Applicazione Todo

Nel corso video, approfondisco meglio ogni singola tecnologia dello stack FARM. Ma in questo articolo, passeremo direttamente a un progetto per mettere tutto insieme.

Creeremo un’applicazione Todo per aiutarci a capire lo stack FARM. Prima di iniziare a creare l’applicazione, discutiamo di più sulle funzionalità e l’architettura del software.

Funzionalità dell’applicazione Todo

La nostra applicazione Todo dello stack FARM includerà le seguenti funzionalità:

  1. Multiple liste di Todo:

    • Gli utenti possono creare, visualizzare, aggiornare ed eliminare più liste di Todo.

    • Ogni lista ha un nome e contiene più elementi di Todo.

  2. Elementi da compiere:

    • In ogni lista, gli utenti possono aggiungere, visualizzare, aggiornare e eliminare elementi da compiere.

    • Ogni elemento ha una etichetta, un stato di spunta/non spunta e appartiene a una specifica lista.

  3. Aggiornamenti in tempo reale:

    • L’interfaccia è aggiornata in tempo reale quando viene effettuato un cambiamento nelle liste o negli elementi.
  4. Progetto flessibile:

    • L’applicazione sarà flessibile e funzionerà bene sia su desktop che su dispositivi mobili.

Architettura del sistema

La nostra applicazione per le liste da compiere seguirà una tipica architettura FARM stack:

  1. Frontend (React):

    • Fornisce l’interfaccia utente per l’interazione con le liste e gli elementi da fare.

    • Comunica con il backend tramite chiamate API RESTful.

  2. Backend (FastAPI):

    • Gestisce le richieste API provenienti dal frontend.

    • Implementa la logica aziendale per la gestione delle liste e degli elementi da fare.

    • Interagisce con il database MongoDB per la persistenza dei dati.

  3. Database (MongoDB):

    • Memorizza le liste e gli elementi da fare.

    • Fornisce query efficienti e aggiornamenti dei dati delle liste da fare.

  4. Docker:

    • Containerizza ogni componente (frontend, backend, database) per facilitare lo sviluppo e il deploy.

Progettazione del modello dati

Il nostro modello dati MongoDB conterrà due strutture principali:

  1. Lista delle cose da fare:
   {
     "_id": ObjectId,
     "name": String,
     "items": [
       {
         "id": String,
         "label": String,
         "checked": Boolean
       }
     ]
   }
  1. Riepilogo della lista (per la visualizzazione nella lista di tutte le liste delle cose da fare):
   {
     "_id": ObjectId,
     "name": String,
     "item_count": Integer
   }

Progettazione degli endpoint API

Il nostro backend FastAPI esporrà i seguenti endpoint RESTful:

  1. Liste delle cose da fare:

    • GET /api/lists: Recupera tutte le liste delle cose da fare (vista sommario)

    • POST /api/lists: Crea una nuova lista delle cose da fare

    • GET /api/lists/{list_id}: Recupera una specifica lista delle cose da fare con tutti gli elementi

    • DELETE /api/lists/{list_id}: Elimina una specifica lista delle cose da fare

  2. Elementi Todo:

    • POST /api/lists/{list_id}/items: Aggiungi un nuovo elemento a una lista specifica

    • PATCH /api/lists/{list_id}/checked_state: Aggiorna lo stato di spunta di un elemento

    • DELETE /api/lists/{list_id}/items/{item_id}: Elimina un elemento specifico da una lista

Questo progetto fornirà una solida base nello sviluppo dello stack FARM e nella containerizzazione Docker, che potrai poi estendere per applicazioni più complesse in futuro.

Quindi iniziamo con il progetto.

Guida al Progetto

Impostazione del Progetto e Sviluppo del Backend

Passo 1: Imposta la struttura del progetto

Crea una nuova directory per il tuo progetto:

   mkdir farm-stack-todo
   cd farm-stack-todo

Crea sottodirectory per il backend e il frontend:

   mkdir backend frontend

Passo 2: Imposta l’ambiente del backend

Navighi nella directory del backend:

   cd backend

Crea un ambiente virtuale e attivalo:

   python -m venv venv
   source venv/bin/activate  # On Windows, use: venv\Scripts\activate

Creare i seguenti file nel directory backend:

    • Dockerfile

      • pyproject.toml

Nel tuo terminale, installare i pacchetti necessari:

pip install "fastapi[all]" "motor[srv]" beanie aiostream

Generare il file requirements.txt:

pip freeze > requirements.txt

Dopo aver creato il file requirements.txt (o tramite pip-compile o manualmente), puoi installare le dipendenze usando:

   pip install -r requirements.txt

Aggiungere il seguente contenuto a Dockerfile:

   FROM python:3

   WORKDIR /usr/src/app
   COPY requirements.txt ./

   RUN pip install --no-cache-dir --upgrade -r ./requirements.txt

   EXPOSE 3001

   CMD [ "python", "./src/server.py" ]

Aggiungere il seguente contenuto a pyproject.toml:

   [tool.pytest.ini_options]
   pythonpath = "src"

Step 4: Configurare la struttura backend

Creare una directory src dentro il directory backend:

   mkdir src

Creare i seguenti file all’interno della directory src:

Step 5: Implementare la Data Access Layer (DAL)

Apri src/dal.py e aggiungi il seguente contenuto:

from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorCollection
from pymongo import ReturnDocument

from pydantic import BaseModel

from uuid import uuid4

class ListSummary(BaseModel):
  id: str
  name: str
  item_count: int

  @staticmethod
  def from_doc(doc) -> "ListSummary":
      return ListSummary(
          id=str(doc["_id"]),
          name=doc["name"],
          item_count=doc["item_count"],
      )

class ToDoListItem(BaseModel):
  id: str
  label: str
  checked: bool

  @staticmethod
  def from_doc(item) -> "ToDoListItem":
      return ToDoListItem(
          id=item["id"],
          label=item["label"],
          checked=item["checked"],
      )

class ToDoList(BaseModel):
  id: str
  name: str
  items: list[ToDoListItem]

  @staticmethod
  def from_doc(doc) -> "ToDoList":
      return ToDoList(
          id=str(doc["_id"]),
          name=doc["name"],
          items=[ToDoListItem.from_doc(item) for item in doc["items"]],
      )

class ToDoDAL:
  def __init__(self, todo_collection: AsyncIOMotorCollection):
      self._todo_collection = todo_collection

  async def list_todo_lists(self, session=None):
      async for doc in self._todo_collection.find(
          {},
          projection={
              "name": 1,
              "item_count": {"$size": "$items"},
          },
          sort={"name": 1},
          session=session,
      ):
          yield ListSummary.from_doc(doc)

  async def create_todo_list(self, name: str, session=None) -> str:
      response = await self._todo_collection.insert_one(
          {"name": name, "items": []},
          session=session,
      )
      return str(response.inserted_id)

  async def get_todo_list(self, id: str | ObjectId, session=None) -> ToDoList:
      doc = await self._todo_collection.find_one(
          {"_id": ObjectId(id)},
          session=session,
      )
      return ToDoList.from_doc(doc)

  async def delete_todo_list(self, id: str | ObjectId, session=None) -> bool:
      response = await self._todo_collection.delete_one(
          {"_id": ObjectId(id)},
          session=session,
      )
      return response.deleted_count == 1

  async def create_item(
      self,
      id: str | ObjectId,
      label: str,
      session=None,
  ) -> ToDoList | None:
      result = await self._todo_collection.find_one_and_update(
          {"_id": ObjectId(id)},
          {
              "$push": {
                  "items": {
                      "id": uuid4().hex,
                      "label": label,
                      "checked": False,
                  }
              }
          },
          session=session,
          return_document=ReturnDocument.AFTER,
      )
      if result:
          return ToDoList.from_doc(result)

  async def set_checked_state(
      self,
      doc_id: str | ObjectId,
      item_id: str,
      checked_state: bool,
      session=None,
  ) -> ToDoList | None:
      result = await self._todo_collection.find_one_and_update(
          {"_id": ObjectId(doc_id), "items.id": item_id},
          {"$set": {"items.$.checked": checked_state}},
          session=session,
          return_document=ReturnDocument.AFTER,
      )
      if result:
          return ToDoList.from_doc(result)

  async def delete_item(
      self,
      doc_id: str | ObjectId,
      item_id: str,
      session=None,
  ) -> ToDoList | None:
      result = await self._todo_collection.find_one_and_update(
          {"_id": ObjectId(doc_id)},
          {"$pull": {"items": {"id": item_id}}},
          session=session,
          return_document=ReturnDocument.AFTER,
      )
      if result:
          return ToDoList.from_doc(result)

Questo conclude la Parte 1 del tutorial, in cui abbiamo impostato la struttura del progetto e implementato la Data Access Layer per la nostra applicazione todo dello stack FARM. Nella prossima parte, implementeremo il server FastAPI e creeremo gli endpoint API.

Implementazione del server FastAPI

Step 6: Implementa il server FastAPI

Apri src/server.py e aggiungi il seguente contenuto:

from contextlib import asynccontextmanager
from datetime import datetime
import os
import sys

from bson import ObjectId
from fastapi import FastAPI, status
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel
import uvicorn

from dal import ToDoDAL, ListSummary, ToDoList

COLLECTION_NAME = "todo_lists"
MONGODB_URI = os.environ["MONGODB_URI"]
DEBUG = os.environ.get("DEBUG", "").strip().lower() in {"1", "true", "on", "yes"}


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Avvio:
    client = AsyncIOMotorClient(MONGODB_URI)
    database = client.get_default_database()

    # Assicurati che il database sia disponibile:
    pong = await database.command("ping")
    if int(pong["ok"]) != 1:
        raise Exception("Cluster connection is not okay!")

    todo_lists = database.get_collection(COLLECTION_NAME)
    app.todo_dal = ToDoDAL(todo_lists)

    # Restituisci al FastAPI Application:
    yield

    # Arresto:
    client.close()


app = FastAPI(lifespan=lifespan, debug=DEBUG)


@app.get("/api/lists")
async def get_all_lists() -> list[ListSummary]:
    return [i async for i in app.todo_dal.list_todo_lists()]


class NewList(BaseModel):
    name: str


class NewListResponse(BaseModel):
    id: str
    name: str


@app.post("/api/lists", status_code=status.HTTP_201_CREATED)
async def create_todo_list(new_list: NewList) -> NewListResponse:
    return NewListResponse(
        id=await app.todo_dal.create_todo_list(new_list.name),
        name=new_list.name,
    )


@app.get("/api/lists/{list_id}")
async def get_list(list_id: str) -> ToDoList:
    """Get a single to-do list"""
    return await app.todo_dal.get_todo_list(list_id)


@app.delete("/api/lists/{list_id}")
async def delete_list(list_id: str) -> bool:
    return await app.todo_dal.delete_todo_list(list_id)


class NewItem(BaseModel):
    label: str


class NewItemResponse(BaseModel):
    id: str
    label: str


@app.post(
    "/api/lists/{list_id}/items/",
    status_code=status.HTTP_201_CREATED,
)
async def create_item(list_id: str, new_item: NewItem) -> ToDoList:
    return await app.todo_dal.create_item(list_id, new_item.label)


@app.delete("/api/lists/{list_id}/items/{item_id}")
async def delete_item(list_id: str, item_id: str) -> ToDoList:
    return await app.todo_dal.delete_item(list_id, item_id)


class ToDoItemUpdate(BaseModel):
    item_id: str
    checked_state: bool


@app.patch("/api/lists/{list_id}/checked_state")
async def set_checked_state(list_id: str, update: ToDoItemUpdate) -> ToDoList:
    return await app.todo_dal.set_checked_state(
        list_id, update.item_id, update.checked_state
    )


class DummyResponse(BaseModel):
    id: str
    when: datetime


@app.get("/api/dummy")
async def get_dummy() -> DummyResponse:
    return DummyResponse(
        id=str(ObjectId()),
        when=datetime.now(),
    )


def main(argv=sys.argv[1:]):
    try:
        uvicorn.run("server:app", host="0.0.0.0", port=3001, reload=DEBUG)
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()

Questa implementazione imposta il server FastAPI con il middleware CORS, si connette a MongoDB, e definisce gli endpoint API per la nostra applicazione todo.

Step 7: Imposta le variabili d’ambiente

Crea un file .env nella directory radice con il seguente contenuto. Assicurati di aggiungere il nome del database (“todo”) alla fine di “.mongodb.net/”.

MONGODB_URI='mongodb+srv://beau:codecamp@cluster0.ji7hu.mongodb.net/todo?retryWrites=true&w=majority&appName=Cluster0'

Step 8: Crea un file docker-compose

Nella directory radice del tuo progetto (farm-stack-todo), crea un file chiamato compose.yml con il seguente contenuto:

name: todo-app
services:
  nginx:
    image: nginx:1.17
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - 8000:80
    depends_on:
      - backend
      - frontend
  frontend:
    image: "node:22"
    user: "node"
    working_dir: /home/node/app
    environment:
      - NODE_ENV=development
      - WDS_SOCKET_PORT=0
    volumes:
      - ./frontend/:/home/node/app
    expose:
      - "3000"
    ports:
      - "3000:3000"
    command: "npm start"
  backend:
    image: todo-app/backend
    build: ./backend
    volumes:
      - ./backend/:/usr/src/app
    expose:
      - "3001"
    ports:
      - "8001:3001"
    command: "python src/server.py"
    environment:
      - DEBUG=true
    env_file:
      - path: ./.env
        required: true

Step 9: Imposta la configurazione di Nginx

Crea una directory chiamata nginx nella radice del tuo progetto:

mkdir nginx

Crea un file chiamato nginx.conf all’interno della directory nginx con il seguente contenuto:

server {
    listen 80;
    server_name farm_intro;

    location / {
        proxy_pass http://frontend:3000;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /api {
        proxy_pass http://backend:3001/api;
    }
}

Questo conclude la seconda parte del tutorial, in cui abbiamo implementato il server FastAPI, impostato le variabili d’ambiente, creato un file docker-compose e configurato Nginx. Nella prossima parte, ci focalizzeremo sull’impostazione del frontend React per la nostra applicazione todo dello stack FARM.

Impostazione del Frontend React

Passo 10: Crea l’applicazione React

Navighi nella directory frontend:

cd ../frontend

Crea una nuova applicazione React utilizzando Create React App:

npx create-react-app .

Installa dipendenze aggiuntive:

   npm install axios react-icons

Passo 11: Imposta il componente principale App

Sostituisci il contenuto di src/App.js con il seguente:

import { useEffect, useState } from "react";
import axios from "axios";
import "./App.css";
import ListToDoLists from "./ListTodoLists";
import ToDoList from "./ToDoList";

function App() {
  const [listSummaries, setListSummaries] = useState(null);
  const [selectedItem, setSelectedItem] = useState(null);

  useEffect(() => {
    reloadData().catch(console.error);
  }, []);

  async function reloadData() {
    const response = await axios.get("/api/lists");
    const data = await response.data;
    setListSummaries(data);
  }

  function handleNewToDoList(newName) {
    const updateData = async () => {
      const newListData = {
        name: newName,
      };

      await axios.post(`/api/lists`, newListData);
      reloadData().catch(console.error);
    };
    updateData();
  }

  function handleDeleteToDoList(id) {
    const updateData = async () => {
      await axios.delete(`/api/lists/${id}`);
      reloadData().catch(console.error);
    };
    updateData();
  }

  function handleSelectList(id) {
    console.log("Selecting item", id);
    setSelectedItem(id);
  }

  function backToList() {
    setSelectedItem(null);
    reloadData().catch(console.error);
  }

  if (selectedItem === null) {
    return (
      <div className="App">
        <ListToDoLists
          listSummaries={listSummaries}
          handleSelectList={handleSelectList}
          handleNewToDoList={handleNewToDoList}
          handleDeleteToDoList={handleDeleteToDoList}
        />
      </div>
    );
  } else {
    return (
      <div className="App">
        <ToDoList listId={selectedItem} handleBackButton={backToList} />
      </div>
    );
  }
}

export default App;

Passo 12: Crea il componente ListTodoLists

Crea un nuovo file src/ListTodoLists.js con il seguente contenuto:

import "./ListTodoLists.css";
import { useRef } from "react";
import { BiSolidTrash } from "react-icons/bi";

function ListToDoLists({
  listSummaries,
  handleSelectList,
  handleNewToDoList,
  handleDeleteToDoList,
}) {
  const labelRef = useRef();

  if (listSummaries === null) {
    return <div className="ListToDoLists loading">Loading to-do lists ...</div>;
  } else if (listSummaries.length === 0) {
    return (
      <div className="ListToDoLists">
        <div className="box">
        <label>
          New To-Do List:&nbsp;
          <input id={labelRef} type="text" />
        </label>
        <button
          onClick={() =>
            handleNewToDoList(document.getElementById(labelRef).value)
          }
        >
          New
        </button>
        </div>
        <p>There are no to-do lists!</p>
      </div>
    );
  }
  return (
    <div className="ListToDoLists">
      <h1>All To-Do Lists</h1>
      <div className="box">
        <label>
          New To-Do List:&nbsp;
          <input id={labelRef} type="text" />
        </label>
        <button
          onClick={() =>
            handleNewToDoList(document.getElementById(labelRef).value)
          }
        >
          New
        </button>
      </div>
      {listSummaries.map((summary) => {
        return (
          <div
            key={summary.id}
            className="summary"
            onClick={() => handleSelectList(summary.id)}
          >
            <span className="name">{summary.name} </span>
            <span className="count">({summary.item_count} items)</span>
            <span className="flex"></span>
            <span
              className="trash"
              onClick={(evt) => {
                evt.stopPropagation();
                handleDeleteToDoList(summary.id);
              }}
            >
              <BiSolidTrash />
            </span>
          </div>
        );
      })}
    </div>
  );
}

export default ListToDoLists;

Crea un nuovo file src/ListTodoLists.css con il seguente contenuto:

.ListToDoLists .summary {
    border: 1px solid lightgray;
    padding: 1em;
    margin: 1em;
    cursor: pointer;
    display: flex;
}

.ListToDoLists .count {
    padding-left: 1ex;
    color: blueviolet;
    font-size: 92%;
}

Passo 13: Crea il componente ToDoList

Crea un nuovo file src/ToDoList.js con il seguente contenuto:

import "./ToDoList.css";
import { useEffect, useState, useRef } from "react";
import axios from "axios";
import { BiSolidTrash } from "react-icons/bi";

function ToDoList({ listId, handleBackButton }) {
  let labelRef = useRef();
  const [listData, setListData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await axios.get(`/api/lists/${listId}`);
      const newData = await response.data;
      setListData(newData);
    };
    fetchData();
  }, [listId]);

  function handleCreateItem(label) {
    const updateData = async () => {
      const response = await axios.post(`/api/lists/${listData.id}/items/`, {
        label: label,
      });
      setListData(await response.data);
    };
    updateData();
  }

  function handleDeleteItem(id) {
    const updateData = async () => {
      const response = await axios.delete(
        `/api/lists/${listData.id}/items/${id}`
      );
      setListData(await response.data);
    };
    updateData();
  }

  function handleCheckToggle(itemId, newState) {
    const updateData = async () => {
      const response = await axios.patch(
        `/api/lists/${listData.id}/checked_state`,
        {
          item_id: itemId,
          checked_state: newState,
        }
      );
      setListData(await response.data);
    };
    updateData();
  }

  if (listData === null) {
    return (
      <div className="ToDoList loading">
        <button className="back" onClick={handleBackButton}>
          Back
        </button>
        Loading to-do list ...
      </div>
    );
  }
  return (
    <div className="ToDoList">
      <button className="back" onClick={handleBackButton}>
        Back
      </button>
      <h1>List: {listData.name}</h1>
      <div className="box">
        <label>
          New Item:&nbsp;
          <input id={labelRef} type="text" />
        </label>
        <button
          onClick={() =>
            handleCreateItem(document.getElementById(labelRef).value)
          }
        >
          New
        </button>
      </div>
      {listData.items.length > 0 ? (
        listData.items.map((item) => {
          return (
            <div
              key={item.id}
              className={item.checked ? "item checked" : "item"}
              onClick={() => handleCheckToggle(item.id, !item.checked)}
            >
              <span>{item.checked ? "✅" : "⬜️"} </span>
              <span className="label">{item.label} </span>
              <span className="flex"></span>
              <span
                className="trash"
                onClick={(evt) => {
                  evt.stopPropagation();
                  handleDeleteItem(item.id);
                }}
              >
                <BiSolidTrash />
              </span>
            </div>
          );
        })
      ) : (
        <div className="box">There are currently no items.</div>
      )}
    </div>
  );
}

export default ToDoList;

Crea un nuovo file src/ToDoList.css con il seguente contenuto:

.ToDoList .back {
    margin: 0 1em;
    padding: 1em;
    float: left;
}

.ToDoList .item {
    border: 1px solid lightgray;
    padding: 1em;
    margin: 1em;
    cursor: pointer;
    display: flex;
}

.ToDoList .label {
    margin-left: 1ex;
}

.ToDoList .checked .label {
    text-decoration: line-through;
    color: lightgray;
}

Passo 14: Aggiorna il file CSS principale

Sostituisci il contenuto di src/index.css con il seguente:

html, body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 12pt;
}

input, button {
  font-size: 1em;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.box {
    border: 1px solid lightgray;
    padding: 1em;
    margin: 1em;
}

.flex {
  flex: 1;
}

Questo conclude la terza parte del tutorial, in cui abbiamo impostato il frontend React per la nostra applicazione todo dello stack FARM. abbiamo creato il componente principale App, il componente ListTodoLists per la visualizzazione di tutte le liste todo e il componente ToDoList per le liste todo individuali. Nella prossima parte, ci focalizzeremo sull’esecuzione e il testing dell’applicazione.

Esecuzione e test dell’applicazione

Step 18: Eseguire l’applicazione usando Docker Compose

  1. Assicurarsi di avere Docker e Docker Compose installati sul sistema

  2. Aprire un terminale nella directory radice del tuo progetto (farm-stack-todo)

  3. Costruire e avviare i contenitori:

docker-compose up --build
  1. Una volta che i contenitori sono in esecuzione, apri il tuo browser web e vai a http://localhost:8000

Step 19: Arresto dell’applicazione

  1. Se stai facendo girare l’applicazione senza Docker:

    • Arrestare il server di sviluppo React premendo Ctrl+C nel suo terminale

    • Arrestare il server FastAPI premendo Ctrl+C nel suo terminale

    • Arrestare il server MongoDB premendo Ctrl+C nel suo terminale

  2. Se stai eseguendo l’applicazione con Docker Compose:

    • Premi Ctrl+C nel terminale in cui hai eseguito docker-compose up

    • Esegui il seguente comando per arrestare e rimuovere i container:

     docker-compose down

“`

Complimenti! Hai costruito e testato con successo un’applicazione todo per FARM stack. Questa applicazione dimostra l’integrazione di FastAPI, React e MongoDB in un’applicazione web full-stack.

Ecco alcuni passi successivi potenziali per migliorare la tua applicazione:

  1. Aggiungi l’autenticazione e l’autorizzazione degli utenti

  2. Implementa la validazione dei dati e la gestione degli errori

  3. Aggiungi più funzionalità come date di scadenza, priorità o categorie per gli elementi todo

  4. Migliora l’UI/UX con un design più curato

  5. Scrivere test unitari e di integrazione sia per il frontend che per il backend

  6. Impostare integrazione continua e deploy (CI/CD) per la tua applicazione

Ricorda di mantenere aggiornate le tue dipendenze e seguire le migliori pratiche per la sicurezza e le prestazioni mentre continui a sviluppare la tua applicazione.

Conclusione e Passi Successivi

Congratulazioni per aver completato questo completo tutorial sulla pila FARM! Costruendo quest’applicazione di lavoro in corso, hai acquisito esperienza pratica con alcune delle tecnologie più potenti e popolari nella web development moderna. Hai imparato come creare un robusto backend API con FastAPI, costruire un frontend dinamico e reattivo con React, persistere i dati con MongoDB e containerizzare l’intera applicazione usando Docker. questo progetto ha dimostrato come queste tecnologie si integrino in maniera fluida per creare un’applicazione web completa e scalabile.