FARMスタックは、強力な3つの技術、FastAPI、React、MongoDBを組み合わせた現代のWeb開発スタックです。このフルスタックソリューションは、開発者にスケーラブルで効率的、高性能なWebアプリケーションを構築する強力なツールセットを提供します。

この記事では、各主要技術について紹介を行い、それからFARMスタックとDockerを使用してプロジェクトを構築して、すべてがどのように連携して動作するかを見ていきます。

この記事は、私が作成したコースを基にしています。freeCodeCamp.org YouTubeチャンネルで視聴できます。ここを見てください:

FARMスタック入門

FARMスタックのFARMは以下の意味を持ちます:

  • F: FastAPI (バックエンド)

  • R: React (フロントエンド)

  • M: MongoDB (データベース)

FARMスタックは、各コンポーネントの強みを活用するよう設計されており、開発者は開発体験がスムーズな機能豊富なアプリケーションを作成することができます。

FARMスタックのコンポーネント

  1. FastAPI: FastAPIは、APIを構築するための現代的で高性能なPython Webフレームワークです。使いやすく、開発が迅速で、本番環境向けのものであるStarletteをWeb部分に、Pydanticをデータ部分に使って構築されています。これにより、堅牢なバックエンドサービスを構築する強力な選択肢となっています。

  2. React: Reactは、Facebookによって開発・保守されている人気のJavaScriptライブラリで、UIを構築するための librariesです。再利用可能なUIコンポーネントを作成し、データの変更に対応して効率的に更新し表示することができます。コンポーネント基盤のアーキテクチャと虚拟DOMにより、動的で対応性のあるフロントエンドアプリケーションの構築に最適な選択肢となります。

  3. MongoDB: MongoDBは、文書指向のNoSQLデータベースです。データを柔軟なJSON-likeの文書に格納しており、文書ごとのフィールドが異なり、データ構造が時間とともに変更できます。この柔軟性により、MongoDBは、迅速に進化する必要があるアプリケーションや、多様なデータ型を扱う必要があるアプリケーションに理想的な選択肢となります。

FARM Stackの利点

  1. 高パフォーマンス: FastAPIは最も速いPythonフレームワークの一員であり、Reactの仮想DOMは効率的なUI更新を Ensureします。MongoDBの文書モデルにより高速な読み書きができます。

  2. スケール性: FARM スタックのすべてのコンポーネントはスケールするために設計されています。FastAPIは並列なリクエストを効率的に処理でき、React アプリケーションは複雑なUIを管理でき、MongoDBはデータを複数のサーバー間で配布できます。

  3. コミュニティと生態系: この3つの技術すべてには大きく、活動的なコミュニティがあり、豊富なライブラリやツールの生態系があります。

  4. 柔軟性: FARM スタックは、シンプルな CRUD アプリから複雑でデータに強く依存するシステムまでのさまざまな種類の Web アプリケーションに対応できるほど柔軟です。

これらの技術を組み合わせることで、FARM スタックはモダンな Web アプリケーションの構築に全面的な解決策を提供します。開発者は FastAPI を使用して速くスケーラブルなバックエンドを作成すること、React を使用してインターラクティブで応答性のあるフロントエンドを作成すること、MongoDB を使用して柔軟で効率的なデータストORAGEを実現することができます。このスタックは、リアルタイム更新、複雑なデータモデル、高パフォーマンスを必要とするアプリケーションに特に適しています。

プロジェクト概要: Todo アプリケーション

ビデオコースでは、FARM スタックの各技術について详しく解説します。しかし、この記事では、すべてを Collect するプロジェクトに直接飛び込みます。

私たちは todo アプリケーションを作成して FARM スタックを理解することにします。アプリケーションを作成する前に、機能とソフトウェアアーキテクチャについてもっと詳しく話しましょう。

todo アプリケーションの機能

私たちの FARM スタックの todo アプリケーションには以下の機能が含まれます。

  1. 複数の Todo リスト:

    • ユーザーは複数の Todo リストを作成、表示、更新、および削除することができます。

    • 各リストには名前があり、複数の Todo アイテムが含まれます。

  2. 待办アイテム:

    • 各リストの中で、ユーザーは待办アイテムを追加、表示、更新、および削除することができます。

    • 各アイテムにはラベルがあり、チェック/未チェックのステータスがあり、特定のリストに属します。

  3. リアルタイム更新:

    • リストやアイテムに変更が行われると、UIはリアルタイムで更新されます。
  4. 応答式設計:

    • アプリケーションはデスクトップとモバイルデバイスの両方で機能します。

システムアーキテクチャ

私たちの待办アプリケーションは典型的なFARMスタックアーキテクチャを追随します:

  1. Frontend (React):

    • Todoリストやアイテムに対する操作のためのUIを提供します。

    • RESTful API调用を通じてバックエンドと通信します。

  2. Backend (FastAPI):

    • フロントエンドからのAPI要求を処理します。

    • Todoリストとアイテムの管理のためのビジネスロジックを実装します。

    • MongoDBデータベースとデータ持久化のためのやり取りを行います。

  3. Database (MongoDB):

    • Todoリストとアイテムを储存します。

    • Todoデータの効率的なクエリングと更新を提供します。

  4. Docker:

    • 各コンポーネント(フロントエンド、バックエンド、データベース)をコンテナ化し、開発とデプロイメントを簡単にします。

データモデル設計

私たちのMongoDBデータモデルは2つの主要な構造を composition of:

  1. Todo List:
   {
     "_id": ObjectId,
     "name": String,
     "items": [
       {
         "id": String,
         "label": String,
         "checked": Boolean
       }
     ]
   }
  1. 一覧の概要 (全Todo一覧に表示する):
   {
     "_id": ObjectId,
     "name": String,
     "item_count": Integer
   }

APIエンドポイント設計

私たちのFastAPIバックエンドは以下のRESTfulエンドポイントを露出します:

  1. Todo Lists:

    • GET /api/lists: 全Todo一覧 (概要ビュー)を取得

    • POST /api/lists: 新しいTodo一覧を作成

    • GET /api/lists/{list_id}: 特定のTodo一覧を全てのアイテムとともに取得

    • DELETE /api/lists/{list_id}: 特定のTodo一覧を削除

  2. Todoアイテム:

    • POST /api/lists/{list_id}/items: 特定のリストに新しいアイテムを追加

    • PATCH /api/lists/{list_id}/checked_state: アイテムの完了状態を更新

    • DELETE /api/lists/{list_id}/items/{item_id}: 特定のリストから特定のアイテムを削除

このプロジェクトはFARMスタック開発とDockerコンテナ化に基づいた堅固な基盤を提供し、将来はより複雑なアプリケーションに拡張できるようにする。

では、プロジェクトを始めましょう。

プロジェクトトレーニング

プロジェクトのセットアップとバックエンド開発

ステップ1: プロジェクト構造を設定

プロジェクト用の新しいディレクトリを作成:

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

バックエンドとフロントエンド用のサブディレクトリを作成:

   mkdir backend frontend

ステップ2: バックエンド環境を設定

バックエンドディレクトリに移動:

   cd backend

仮想環境を作成して有効化:

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

以下のファイルをbackendディレクトリーに作成してください:

    • Dockerfile

      • pyproject.toml

ターミナルで以下の必要なパッケージをインストールしてください:

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

requirements.txtファイルを生成します:

pip freeze > requirements.txt

requirements.txtファイルを作成した後(pip-compileを使用するか手動で作成)、以下のように依存関係をインストールします:

   pip install -r requirements.txt

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" ]

pyproject.tomlに以下の内容を追加してください:

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

ステップ4: バックエンドの構造を設定

backendディレクトリー内にsrcディレクトリーを作成します:

   mkdir src

srcディレクトリー内に以下のファイルを作成します:

ステップ5: データアクセスレイヤー(DAL)を実装してください。

Open src/dal.py and add the following content:

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)

これはチュートリアルの第1部分の終わりです。ここで、私たちはプロジェクト構造を設定し、FARMスタックのtodoアプリケーションのデータアクセス層を実装しました。次の部分で、FastAPIサーバーを実装し、APIエンドポイントを作成します。

FastAPIサーバーの実装

ステップ6: FastAPIサーバーの実装

src/server.pyを開き、以下の内容を追加してください:

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

    # データベースが利用可能であることを確認:
    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)

    # FastAPIアプリケーションに返却:
    yield

    # シャットダウン:
    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()

この実装は、CORSミドルウェアをFastAPIサーバーに設定し、MongoDBに接続し、todoアプリケーションのAPIエンドポイントを定義します。

ステップ7: 環境変数の設定

ルートディレクトリに以下の内容の.envファイルを作成してください。”.mongodb.net/”の末尾にデータベース名(“todo”)を追加してください。

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

ステップ8: docker-composeファイルの作成

あなたのプロジェクトのルートディレクトリ(farm-stack-todo)に、以下の内容のcompose.ymlという名前のファイルを作成してください。

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

ステップ9: Nginx構成の設定

プロジェクトのルートに名前されているdirectory named nginxを作成します。

mkdir nginx

nginxディレクトリに以下の内容のnginx.confという名前のファイルを作成してください。

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

これはチュートリアルの第2部分で終わります。ここではFastAPIサーバーを実装し、環境変数を設定し、docker-composeファイルを作成し、Nginxを設定しました。次の部分では、FARMスタックのtodoアプリケーションのReactフロントエンドの設定を行います。

Reactフロントエンドの設定

手順10: Reactアプリケーションを作成

frontendディレクトリに移動します。

cd ../frontend

Create React Appを使用して新しいReactアプリケーションを作成します。

npx create-react-app .

追加の依存関係をインストールします。

   npm install axios react-icons

手順11: main Appコンポーネントを設定

src/App.jsの内容を以下の内容に置き換えます。

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;

手順12: ListTodoListsコンポーネントを作成

新しいファイルsrc/ListTodoLists.jsを作成し、以下の内容にします。

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;

新しいファイルsrc/ListTodoLists.cssを作成し、以下の内容にします。

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

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

手順13: ToDoListコンポーネントを作成

新しいファイルsrc/ToDoList.jsを作成し、以下の内容にします。

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;

新しいファイルsrc/ToDoList.cssを作成し、以下の内容にします。

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

手順14: 主要なCSSファイルを更新

src/index.cssの内容を以下の内容に置き換えます。

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

これはチュートリアルの第3部分で終わります。私たちはFARMスタックのtodoアプリケーションのReactフロントエンドを設定しました。私たちはmain Appコンポーネント、すべてのtodoリストを表示するListTodoListsコンポーネント、個別のtodoリストを表示するToDoListコンポーネントを作成しました。次の部分では、アプリケーションを実行してテストを行います。

アプリケーションの実行とテスト

ステップ18: Docker Composeを使用してアプリケーションを実行

  1. システムにDockerとDocker Composeがインストールされていることを確認してください

  2. プロジェクトのルートディレクトリ(farm-stack-todo)でターミナルを開いてください

  3. コンテナーをビルドして起動します:

docker-compose up --build
  1. コンテナーが起動したら、Webブラウザを開いてhttp://localhost:8000にアクセスします

ステップ19: アプリケーションの停止

  1. Dockerなしでアプリケーションを実行している場合:

    • React開発サーバーを終了するには、そのターミナルでCtrl+Cを押します

    • FastAPIサーバーを終了するには、そのターミナルでCtrl+Cを押します

    • MongoDBサーバーを終了するには、そのターミナルでCtrl+Cを押します

  2. Docker Composeでアプリケーションを実行している場合:

    • docker-compose upを実行した端末でCtrl+Cを押します

    • 以下のコマンドを実行してコンテナーを停止および削除します:

     docker-compose down

“`

おめでとうございます!FARMスタックのtodoアプリケーションを構築・テストしました。このアプリケーションは、FastAPI、React、MongoDBを完全なWebアプリケーションに統合した例です。

アプリケーションを強化するための次のステップを以下に示します:

  1. ユーザー認証と権限付与を追加

  2. データ検証とエラーハンドリングを実装

  3. todoアイテムに期日、優先度、カテゴリーなどの機能を追加

  4. UI/UXをより洗練されたデザインで改善

  5. フロントエンドとバックエンドのためのユニットとインテグレーションテストを書く

  6. アプリケーションに対する連続的なインテグレーションとデプロイメント(CI/CD)を設定する

依存関係を更新し、安全性とパフォーマンスのための最善の慣習に従っていくようにしてください。

結論と次の手順

このFARMスタックの完全なチュートリアルを完了しました!このtodoアプリケーションの構築を通じて、现代のウェブ開発の最も強力で人気のある技術のいくつかに手を染める実践的な経験を得ました。FastAPIを使用して堅固なバックエンドAPIを作成し、Reactを使用して動的で対応性のあるフロントエンドを構築し、MongoDBを使用してデータを永続化し、Dockerを使用してアプリケーション全体をコンテナ化しました。このプロジェクトは、これらの技術が无缝に一緒に機能して完全な機能を持ったスケーラブルなウェブアプリケーションを作成する方法を示しています。