De FARM-stack is een moderne webontwikkelingsstack die drie krachtige technologieën combineert: FastAPI, React en MongoDB. Deze full-stack oplossing biedt ontwikkelaars een robuuste set tools om schaalbare, efficiente en hoogpresterende webapplicaties te bouwen.

In dit artikel geef ik je een introductie over elk van de belangrijkste technologieën, en daarna zullen we een project bouwen met behulp van de FARM-stack en Docker, zodat je kunt zien hoe alles samenwerkt.

Dit artikel is gebaseerd op een cursus die ik maakte op de freeCodeCamp.org YouTube-kanaal. Kijk hier:

Introductie tot de FARM-stack

De FARM in FARM-stack staat voor:

  • F: FastAPI (Backend)

  • R: React (Frontend)

  • M: MongoDB (Database)

De FARM-stack is ontworpen om de sterktes van elk component te benutten, waardoor ontwikkelaars feature-rijke applicaties kunnen maken met een gladde ontwikkelervaring.

Componenten van de FARM-stack

  1. FastAPI: FastAPI is een moderne, hoogpresterende Python-webframework voor het bouwen van API’s. Het is ontworpen om gemakkelijk te gebruiken, snel te coderen en klaar voor productieomgevingen. FastAPI is gebouwd op Starlette voor de webdelen en Pydantic voor de datadelen, waardoor het een krachtige keuze is voor het bouwen van robuuste backend-diensten.

  2. React: React is een populaire JavaScript-bibliotheek voor het bouwen van gebruikersinterfaces. Ontwikkeld en onderhouden door Facebook, maakt React het voor ontwikkelaars mogelijk om herbruikbare UI-componenten te maken die efficient bijwerken en renderen als de gegevens veranderen. Met zijn op componenten gebaseerde architectuur en virtuele DOM is het een uitstekende keuze voor het bouwen van dynamische en responsieve frontend-toepassingen.

  3. MongoDB: MongoDB is een document-georiënteerde NoSQL-database. Het slaat gegevens op in flexibele, JSON-achtige documenten, wat betekent dat velden van document tot document kunnen verschillen en de gegevensstructuur over tijd kan veranderen. Deze flexibiliteit maakt MongoDB een ideale keuze voor toepassingen die snel moeten evolueren en diverse gegevenstypen moeten kunnen verwerken.

Voordelen van het gebruik van FARM Stack

  1. Hoog Performant: FastAPI is een van de snelste Python-frameworks beschikbaar, terwijl React’s virtuele DOM efficiente UI-updates waarborgt. MongoDB’s documentmodel maakt snelle lees- en schrijfoperaties mogelijk.

  2. Scalabiliteit: Alle componenten van de FARM stack zijn ontworpen om te scalen. FastAPI kan efficiënte gelijktijdige verzoeken afhandelen, React-toepassingen kunnen complexe UIs beheren, en MongoDB kan gegevens over meerdere servers verspreiden.

  3. Gemeenschap en Ecosysteem: Al deze technologieën hebben grote, actieve gemeenschappen en rijke ecosystemen van bibliotheken en hulpmiddelen.

  4. Gevoeligheid: De FARM stack is voldoende flexibel om verschillende typen webapplicaties te kunnen aan, van eenvoudige CRUD-applicaties tot complexe, data-intensieve systemen.

Door deze technologieën combinerend, biedt de FARM stack een omvattende oplossing voor het bouwen van moderne webapplicaties. Het laat ontwikkelaars snelle, schaalbare backends maken met FastAPI, intuitieve en responsieve frontends maken met React, en flexibele, efficiente gegevensopslag maken met MongoDB. Deze stack is bijzonder geschikt voor toepassingen die realtime updates, complexe datamodellen en hoge prestaties vereisen.

Projectoverzicht: To-do Applicatie

In de video cursus ga ik uitgebreid over elke afzonderlijke technologie van de FARM Stack. Maar in dit artikel gaan we direct een project aan om alles samen te voegen.

We zullen een to-do applicatie aanmaken om de FARM stack te begrijpen. voordat we beginnen met het maken van de applicatie, laten we ons nog een keer over de functionaliteiten en software architectuur uitlaten.

To-do applicatie-functionaliteiten

Onze FARM stack to-do applicatie zal de volgende functionaliteiten bevatten:

  1. Meerdere To-do Lijsten:

    • Gebruikers kunnen meerdere to-do lijsten maken, bekijken, bijwerken en verwijderen.

    • Elke lijst heeft een naam en bevat meerdere to-do items.

  2. To-do Items:

    • Binnen elke lijst kunnen gebruikers items toevoegen, bekijken, bijwerken en verwijderen.

    • Elk item heeft een label, een gecontroleerd/ongecontroleerd status en behoort tot een specifieke lijst.

  3. Real-time Updates:

    • De UI wordt in real-time bijgewerkt als er wijzigingen worden aangebracht aan lijsten of items.
  4. Responsive Design:

    • De applicatie zal responsief zijn en goed werken op zowel desktop als mobiele apparaten.

Systeemarchitectuur

Onze to-do applicatie zal een typische FARM stack architectuur volgen:

  1. Frontend (React):

    • Biedt de gebruikersinterface voor interactie met takenlijsten en taken.

    • Communiceert met de backend via RESTful API- aanroepen.

  2. Backend (FastAPI):

    • Behandelt API-verzoeken van de frontend.

    • Implementeert bedrijfslogica voor het beheren van takenlijsten en taken.

    • Interageert met de MongoDB-database voor gegevensbehoud.

  3. Database (MongoDB):

    • Bevat takenlijsten en taken.

    • Biedt efficiënte query’s en updates van takengegevens.

  4. Docker:

    • Containeriseert elk component (frontend, backend, database) voor gemakkelijke ontwikkeling en implementatie.

Ontwerp van het gegevensmodel

Ons MongoDB gegevensmodel zal bestaan uit twee hoofdstructuren:

  1. Todo Lijst:
   {
     "_id": ObjectId,
     "name": String,
     "items": [
       {
         "id": String,
         "label": String,
         "checked": Boolean
       }
     ]
   }
  1. Lijstoverzicht (voor weergave in de lijst van alle todo-lijsten):
   {
     "_id": ObjectId,
     "name": String,
     "item_count": Integer
   }

Ontwerp van API-eindpunten

Onze FastAPI backend zal de volgende RESTful eindpunten blootstellen:

  1. Todo Lijsten:

    • GET /api/lists: Ophalen van alle todo-lijsten (overzichtsweergave)

    • POST /api/lists: Een nieuwe todo-lijst aanmaken

    • GET /api/lists/{list_id}: Een specifieke todo-lijst met al zijn items ophalen

    • DELETE /api/lists/{list_id}: Een specifieke todo-lijst verwijderen

  2. Todo Items:

    • POST /api/lists/{list_id}/items: Voeg een nieuw item toe aan een specifieke lijst

    • PATCH /api/lists/{list_id}/checked_state: Update de gekleurde status van een item

    • DELETE /api/lists/{list_id}/items/{item_id}: Verwijder een specifiek item uit een lijst

Dit project zal u een solide basis in de ontwikkeling van FARM stack en containerisatie van Docker bieden, die u vervolgens kunt aanvullen voor meer complexe toepassingen in de toekomst.

Dus laat’s beginnen met het project.

Projecthandleiding

Projectsetup en backend-ontwikkeling

Stap 1: Stel het projectstructuur in

Maak een nieuw directory voor uw project:

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

Maak subdirectory’s voor de backend en frontend:

   mkdir backend frontend

Stap 2: Stel de backend omgeving in

Ga naar de backend directory:

   cd backend

Maak een virtuele omgeving en activeer deze:.

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

Maak de volgende bestanden in de backend-map:

    • Dockerfile

      • pyproject.toml

Gebruik uw terminal om de vereiste pakketten te installeren:

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

Genereer het requirements.txt-bestand:

pip freeze > requirements.txt

Nadat u het requirements.txt-bestand hebt gemaakt (ofwel door pip-compile of handmatig), kunt u de afhankelijkheden installeren met behulp van:

   pip install -r requirements.txt

Voeg de volgende inhoud toe aan 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" ]

Voeg de volgende inhoud toe aan pyproject.toml:

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

Stap 4: Stel de backend-structuur op

Maak een src-map binnen de backend-map:

   mkdir src

Maak de volgende bestanden binnen de src-map:

Stap 5: Implementeer de Data Access Layer (DAL)

Open src/dal.py en voeg het volgende inhoud toe:

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)

Dit maakt deel 1 van de handleiding af, waarin we de projectstructuur opzetten en de Data Access Layer voor onze FARM stack todo-toepassing implementeerden. In de volgende deel zullen we de FastAPI server implementeren en de API endpoints aanmaken.

Implementeren van de FastAPI Server

Stap 6: Implementeer de FastAPI server

Open src/server.py en voeg het volgende inhoud toe:

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):
    # Startup:
    client = AsyncIOMotorClient(MONGODB_URI)
    database = client.get_default_database()

    # Zorg ervoor dat de database beschikbaar is:
    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)

    # Geef terug aan de FastAPI applicatie:
    yield

    # Afsluiten:
    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()

Deze implementatie stelt de FastAPI server op met CORS middleware, verbindt met MongoDB en definieert de API endpoints voor onze todo-toepassing.

Stap 7: Stel omgevingsvariabelen in

Maak een .env bestand in de root map met het volgende inhoud. Zorg ervoor dat je de database naam (“todo”) aan het einde van “.mongodb.net/” toevoegt.

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

Stap 8: Maak een docker-compose bestand

In de root map van uw project (farm-stack-todo), maak een bestand genaamd compose.yml met het volgende inhoud.

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

Stap 9: Stel de Nginx configuratie in

Maak een map genaamd nginx in de root van uw project:

mkdir nginx

Maak een bestand aan met de naam nginx.conf binnen de directory nginx met het volgende inhoud:

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;
    }
}

Dit sluit deel 2 van de handleiding af, waarin we de FastAPI server implementeerden, omgevingsvariabelen ingesteld maakten, een docker-compose bestand aangemaakt en de Nginx configuratie uitgevoerd werd. In de volgende deel zullen we ons concentreren op het opzetten van de React frontend voor onze FARM stack taken applicatie.

Opzetten van de React Frontend

Stap 10: Maak de React applicatie

Navigeer naar de frontend directory:

cd ../frontend

Maak een nieuwe React applicatie aan met behulp van Create React App:

npx create-react-app .

Installeer extra afhankelijkheden:

   npm install axios react-icons

Stap 11: Opzetten van het hoofd App component

Vervang de inhoud van src/App.js door het volgende:

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;

Stap 12: Maak het ListTodoLists component

Maak een nieuw bestand src/ListTodoLists.js met het volgende inhoud:

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;

Maak een nieuw bestand src/ListTodoLists.css met het volgende inhoud:

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

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

Stap 13: Maak het ToDoList component

Maak een nieuw bestand src/ToDoList.js met het volgende inhoud:

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;

Maak een nieuw bestand src/ToDoList.css met het volgende inhoud:

.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;
}

Stap 14: Bijwerken van het hoofd CSS bestand

Vervang de inhoud van src/index.css door het volgende:

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;
}

Dit sluit deel 3 van de handleiding af, waarin we de React frontend voor onze FARM stack taken applicatie opzetten. We hebben het hoofd App component, het ListTodoLists component voor het weergeven van alle takenlijsten en het ToDoList component voor individuele takenlijsten gemaakt. In de volgende deel zullen we ons focus leggen op het draaien en testen van de applicatie.

Running and Testing de applicatie

Stap 18: Voer de applicatie uit met Docker Compose

  1. Zorg ervoor dat Docker en Docker Compose op uw systeem zijn geïnstalleerd

  2. Open een terminal in de hoofdmap van uw project (farm-stack-todo)

  3. Build en start de containers:

docker-compose up --build
  1. Wanneer de containers actief zijn, open uw webbrowser en ga naar http://localhost:8000

Stap 19: Stoppen van de applicatie

  1. Als u de applicatie zonder Docker uitvoert:

    • Stop het React ontwikkelingsserver door op Ctrl+C te drukken in zijn terminal

    • Stop de FastAPI server door op Ctrl+C te drukken in zijn terminal

    • Stop de MongoDB server door op Ctrl+C te drukken in zijn terminal

  2. Als u de toepassing met Docker Compose uitvoert:

    • Druk Ctrl+C in de terminal waar u docker-compose up heeft uitgevoerd

    • Voer het volgende commando uit om de containers te stoppen en te verwijderen:

     docker-compose down

“`

Gefeliciteerd! U heeft een FARM stack todo-toepassing succesvol gebouwd en getest. Deze toepassing demonstreert de integratie van FastAPI, React en MongoDB in een volledig webgebaseerde applicatie.

Hier zijn enkele potentiële volgende stappen om uw toepassing te verbeteren:

  1. Voeg gebruikersauthenticatie en autorisatie toe

  2. Implementeer gegevensvalidatie en foutafhandeling

  3. Voeg meer functionaliteit toe zoals vervaldatums, prioriteiten of categorieën voor taken

  4. Verbeter de UI/UX met een meer gepolijst ontwerp

  5. Schrijf eenheidstests en integratietests voor zowel de frontend als de backend

  6. Stel continue integratie en deployoment (CI/CD) in voor uw applicatie

Vergeet niet uw dependencies bij te werken en de beste practices voor veiligheid en prestaties te volgen terwijl u doorgaat met ontwikkelen van uw applicatie.

Conclusie en volgende stappen

Gefeliciteerd met het afronden van deze uitgebreide FARM-stack handleiding! Door deze todo-applicatie te bouwen, hebt u praktijkervaring opgedaan met enkele van de krachtigste en populairste technologieën in moderne webontwikkeling. U hebt geleerd hoe u een robuuste backend API met FastAPI kunt maken, een dynamische en responsieve frontend met React kunt bouwen, gegevens te laten persisteren met MongoDB, en uw hele applicatie te containerizen met Docker. Dit project heeft laten zien hoe deze technologieën samen naadloos samenwerken om een volledig-gefunctioneerde, schaalbare webapplicatie te maken.