を構築し、ファームスタックアプリケーションを展開しましょう


あなたは、反応、角度、またはVueと完全なスタックアプリケーションを書いたことがありますか?このチュートリアルでは、ファームスタックをカバーします.FastAPIを構築するためのPythonフレームワークは、よく、高速、APIです.このプロジェクトは、do - doリストです.このチュートリアルは、一般的にVueやAngleのような他のフレームワークにも適用できますが、私は反応を使用します.

プロジェクト設定


プロジェクトを始めるのはとても簡単です.私は、私のCLIの作成ファームアプリケーションを使用して1つ、手動で2つの方法が表示されます.どちらも非常に簡単ですが、すべての自分自身を設定しない場合は、CLIを使用することができます.私はあなたの最初のプロジェクトのために手動でアプリを設定することをお勧めします.

マニュアル設定


手動設定から始めましょう.
$ mkdir farm-stack-tut
$ cd farm-stack-tut
$ mkdir backend
$ code .
$ git init
$ yarn create react-app frontend --template typescript
$ cd backend
$ git init
$ touch requirements.txt main.py model.py database.py
さあ、必要条件を開きましょう.以下の依存関係を指定します.
fastapi == 0.65.1

uvicorn == 0.14.0

motor == 2.4.0

gunicorn == 20.1.0

pymongo[srv] == 3.12.0
我々は、我々が我々のMongoDBアトラスデータベースとGunicornに接続するためにASGIサーバー、モーターとPirmongo[SRV]を走らせることのためにUvicornを必要とします我々がアプリケーションを配備するとき.
私たちが2つのgit - repos(craによって自動的に初期化されるもの)を初期化している理由は、サブモジュールを利用することです.私はこのセットアップを1つの大きなリポジトリにするのが好きです.このチュートリアルではサブモジュールで展開する方法を紹介しますが、あなたがそれを調べるなら、それらを使用せずに展開する方法を見つけることができます.

依存関係のインストール


Pipenvを使っているなら、PIP依存性をインストールするのはとても簡単です.単にバックエンドフォルダに移動して入力します.
$ pipenv install -r requirements.txt

テンプレートの設定


これはとても簡単です.CLIを通してほとんどのものを設定しているので、まだgitサブモジュールを設定する必要があります.
$ yarn create farm-app --name=farm-stack-tut
あなたはとにかく名前のポップアップを見るかもしれません、私はそれを修理することに取り組んでいます、しかし、同じ名前を入力するならば、それはうまくいくはずです.

Gitセットアップ


では、これらのサブモジュールを設定しましょう.
つの新しいリモートrepos、フロントエンドのための1つ、バックエンドのための1つ、および完全なアプリケーションの1つを確認します.
フロントエンドとバックエンドローカルreposで、コマンドを実行します.
$ git remote add origin <url>
$ git add *
$ git commit -m "first commit"
$ git branch -M main
$ git push -u origin main
メインのrepoでは、これらのコマンドを一度押してください.
$ git submodule add <frontend-url> frontend
$ git submodule add <backend-url> backend
その後、変更して、主なリモートrepoに変更をプッシュします.

バックエンドAPIの作成


我々は2012年に開始されますmain.py , このコードを始めるには
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

origins = ["*"] # This will eventually be changed to only the origins you will use once it's deployed, to secure the app a bit more.

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

@app.get('/')
def get_root():
    return {"Ping": "Pong"}
これは、最も基本的な可能なAPIであり、我々は適切にすべてを設定していることを確認するためのテストとして機能します.
ここでuvicornコマンドを実行します.
$ uvicorn main:app --reload
あなたがhttp://localhost:8000 , 返されるメッセージを{ ping ": "pong "とします.あなたがそうしたならば、我々はバックエンドの残りを構築し始められることができます.

モンゴドル


簡単に休憩してデータベースを作るために移動しましょう.あなたはこのアプリを展開する予定はない場合は、単にローカルMongoDBのデータベースを使用することができますが、私は私のアプリを展開するので、私は彼らのクラウドホスティングサービスを使用している.移動するMongoDB Atlas , そして、あなたが以前にこれを使用したならば、新しい口座をセットするか、新しいプロジェクトをつくってください.プロジェクトが作成されたら、“データベースを追加”ボタンを使用して、無料でクラスタを追加することができます.クラスタに名前を付け、それを作成します.それが行われると、“ブラウズコレクション”ボタンを押すと、“todoDatabase”と“todos”という名前の新しいデータベースとコレクションを挿入します.それが今のために必要なことです.

当社のモデルを作成し、当社のデータベースに接続


私たちはデータをデータベースにプッシュするために2つのことをする必要がありますmodel.py . 私たちは3文字列、ナノイド、タイトル、および記述を含みます.そして、それが終わるかどうかチェックするブール値に加えます.モデルは次のようになります.
from pydantic import BaseModel

class Todo(BaseModel):
    nanoid: str
    title: str
    desc: str
    checked: bool
私たちがしなければならない次のことは、実際に私たちのデータベースに接続しています.これはモーターとピモゴで十分に簡単ですが、アプリケーションを安全にするためには、データベースURIの環境変数を使います.
$ pipenv install python-dotenv
バックエンドのルートで作成します.ENVファイルは、データベースURI(これはMongoDBアトラスのConnectをクリックすることで見つけることができます)でこれを置きます.
DATABASE_URI = "<URI>" 
技術的には、Herokuが我々が展開するとき、環境変数を挿入するのを許すので、我々のローカル・マシンで我々のアプリケーションを働かせ続けることを目的とするだけです、しかし、それはあなたの機密データを隠しておく良い行いです.あなたがすでにいないならば、Aを作ります.gitignore ファイルを置く.env インサイド.
さあ、データベースに接続しましょう.
そのためには、最初にdotenvを使ってURIをファイルから取得します.
from model import *
import motor.motor_asyncio
from dotenv import dotenv_values
import os

config = dotenv_values(".env")
DATABASE_URI = config.get("DATABASE_URI")
if os.getenv("DATABASE_URI"): DATABASE_URI = os.getenv("DATABASE_URI") #ensures that if we have a system environment variable, it uses that instead

client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URI)
今、我々はデータベースとコレクションの変数を作成し、コレクションのデータを変更する機能の束を作ることができます.
database = client.TodoDatabase
collection = database.todos

async def fetch_all_todos():
    todos = []
    cursor = collection.find()
    async for doc in cursor:
        todos.append(Todo(**doc))
    return todos

async def fetch_one_todo(nanoid):
    doc = await collection.find_one({"nanoid": nanoid}, {"_id": 0})
    return doc

async def create_todo(todo):
    doc = todo.dict()
    await collection.insert_one(doc)
    result = await fetch_one_todo(todo.nanoid)
    return result

async def change_todo(nanoid, title, desc, checked):
    await collection.update_one({"nanoid": nanoid}, {"$set": {"title": title, "desc": desc, "checked": checked}})
    result = await fetch_one_todo(nanoid)
    return result

async def remove_todo(nanoid):
    await collection.delete_one({"nanoid": nanoid})
    return True
これらは我々が必要とするすべての機能ですが、自分自身を追加する自由を感じる.いくつかのHTTP操作を取得しましょうmain.py :
@app.get("/api/get-todo/{nanoid}", response_model=Todo)
async def get_one_todo(nanoid):
    todo = await fetch_one_todo(nanoid)
    if not todo: raise HTTPException(404)
    return todo

@app.get("/api/get-todo")
async def get_todos():
    todos = await fetch_all_todos()
    if not todos: raise HTTPException(404)
    return todos

@app.post("/api/add-todo", response_model=Todo)
async def add_todo(todo: Todo):
    result = await create_todo(todo)
    if not result: raise HTTPException(400)
    return result

@app.put("/api/update-todo/{nanoid}", response_model=Todo)
async def update_todo(todo: Todo):
    result = await change_todo(nanoid, title, desc, checked)
    if not result: raise HTTPException(400)
    return result

@app.delete("/api/delete-todo/{nanoid}")
async def delete_todo(nanoid):
    result = await remove_todo(nanoid)
    if not result: raise HTTPException(400)
    return result
では、次のように動作しますhttp:localhost:8000/docs そして、それらを試してみる.
すべての操作で画面が表示されます.

それらのいずれかで“それを試してみる”が、おそらく追加ToDoのいずれかを開始し、その後、操作を実行することができます.現在の応答を無視し、MonitDBデータベースをビューコレクションセクションで確認します.新しい項目を見る必要がありますが、応答に戻ることができない場合は、ページを開いている場合は、データベースを更新する必要があります.他の操作も試してみるべきですが、もしうまくいけば、フロントエンドで作業を始めることができます.

フロントエンド


あなたがどのように反応するかを知っているならば、そして、あなたがAxiosを通してHTTPリクエストを送る方法を知っているならば、私はこのセクションをスキップすることを勧めます.

ライブラリ


使っている[email protected]

  • [email protected] (ノードのバージョンに応じて別のバージョンのノードSASSとSASS LOADを使うことができます.
  • [email protected]
  • ナノイド
  • アクシオス
  • それは私が実際に使用しているライブラリの基本的には、私のテンプレートは、同様に反応ルーターを追加します
  • アプリ


    すてきなフォルダ構造を設定することから始めましょう.

    今我々は我々のアプリを開始することができます.
    インデックスを残しましょう.TSXだけで、アプリのためにまっすぐ行く.これは次のようになります.
    import React from "react";
    import TodoList from "./components/TodoList";
    
    function App() {
        return (
            <div className="app-container">
                <header className="app-header">
                    <h1>To-Do List</h1>
                </header>
                <div className="content">
                    <TodoList />
                </div>
            </div>
        );
    }
    
    export default App;
    
    スタイリングをする前に、必要な3つのコンポーネントを設定しましょうTodoList.tsx , Todo.tsx , and AddTodo.tsx . 今のところ、基本的に同じように見えるはずですが、これはこのクラスのように、どうやっているのかによってクラス名のdivだけです.
    import React from "react";
    
    function Todo() {
        return(
            <div className="todo-container">
    
            </div>
        );
    }
    
    export default Todo;
    
    私たちは、これらのコンポーネントを我々のアプリのいくつかのスタイルを定義してみましょう、私はSASSの代わりにSCSSを使用するが、これは簡単にSASS(またはCSSにいくつかの余分な仕事をしたい場合)に適応する必要があります.
    これは私が行ったスタイルシートですindex.scss :
    $primary: #146286;
    $secondary: #641486;
    $accent: #3066b8;
    
    .app-header {
        background-color: $primary;
        color: white;
        padding: 5px;
        border-radius: 10px;
        margin-bottom: 5px;
    }
    
    .content {
        .todo-list-container {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            grid-template-rows: repeat(5, 1fr);
            grid-gap: 10px;
    
            .todo-container {
                display: flex;
                flex-direction: column;
                justify-content: space-evenly;
    
                border-radius: 6px;
                padding: 10px 6px;
                background-color: $secondary;
                color: white;
    
                h1 {
                    font-size: 20px;
                }
    
                span {
                    font-size: 14px;
                }
    
                footer {
                    display: flex;
                    flex-direction: row-reverse;
                }
            }
        }
    }
    
    これは我々がする必要がある唯一のスタイリングでなければなりません、しかし、あなたが望むならば、あなたは若干の余分をすることができます.
    それでは、コンポーネントを操作しましょう.
    完成したアプリは次のようになります.
    import { nanoid } from "nanoid";
    import React, { useState } from "react";
    import { TodoType } from "./components/Todo";
    import TodoList from "./components/TodoList";
    
    function App() {
        const [todoList, setTodoList] =  useState<TodoType[]>([]);
    
        const [title, setTitle] = useState<string>("");
        const [desc, setDesc] = useState<string>("");
    
        const changeTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
            setTitle(event.currentTarget.value);
        };
    
        const changeDesc = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
            setDesc(event.currentTarget.value);
        }
    
        const changeChecked = (event: React.MouseEvent<HTMLInputElement>, id: string) => {
            let temp = [...todoList];
            temp.forEach((item) => {
                if (item.nanoid === id) {
                    item.checked = !item.checked;
                }
            });
            setTodoList(temp);
        };
    
        const addTodo = (event: React.MouseEvent<HTMLButtonElement>) => {
            let newTodo: TodoType = {
                nanoid: nanoid(),
                title: title,
                desc: desc,
                checked: false
            };
            setTodoList([...todoList, newTodo]);
        }
    
        return (
            <div className="app-container">
                <header className="app-header">
                    <h1>To-Do List</h1>
                </header>
                <div className="content">
                    <TodoList submit={addTodo} changeDesc={changeDesc} changeTitle={changeTitle} list={todoList} changeChecked={changeChecked} />
                </div>
            </div>
        );
    }
    
    export default App;
    
    これはいくつかの非常に基本的な機能を反応フックを介して木の小道具を渡すために実行されます.
    Toolistは次のようになります.
    import React from "react";
    import AddTodo from "./AddTodo";
    import Todo, { TodoType } from "./Todo";
    
    interface TodoListProps {
        list: TodoType[]
        changeChecked: (event: React.MouseEvent<HTMLInputElement>, nanoid: string) => void;
        changeTitle: (event: React.ChangeEvent<HTMLInputElement>) => void;
        changeDesc: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
        submit: (event: React.MouseEvent<HTMLButtonElement>) => void;
    }
    
    function TodoList(props: TodoListProps) {
        return(
            <div className="todo-list-container">
                {props.list.map((item) => {
                    return(
                        <Todo nanoid={item.nanoid} title={item.title} desc={item.desc} checked={item.checked} changeChecked={props.changeChecked} /> 
                    );
                })}
                <AddTodo changeTitle={props.changeTitle} changeDesc={props.changeDesc} submit={props.submit} />
            </div>
        );
    }
    
    export default TodoList;
    
    todoは次のようになります.
    import React from "react";
    
    export type TodoType = {
        nanoid: string;
        title: string;
        desc: string;
        checked: boolean;
    }
    
    interface TodoProps extends TodoType {
        changeChecked: (event: React.MouseEvent<HTMLInputElement>, nanoid: string) => void;
    }
    
    function Todo(props: TodoProps) {
        return(
            <div className="todo-container">
                <h1>{props.title}</h1>
                <span>{props.desc}</span>
                <footer>
                    <input type="checkbox" checked={props.checked} onClick={(e) => props.changeChecked(e, props.nanoid)} />
                </footer>
            </div>
        );
    }
    
    export default Todo;
    
    最後に、addtodoは次のようになります.
    import React from "react";
    
    interface AddTodoProps {
        submit: (event: React.MouseEvent<HTMLButtonElement>) => void;
        changeTitle: (event: React.ChangeEvent<HTMLInputElement>) => void;
        changeDesc: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
    }
    
    function AddTodo(props: AddTodoProps) {
        return(
            <div className="todo-container add-todo-container">
                <input type="text" className="title" placeholder="Title..." onChange={props.changeTitle} />
                <textarea className="desc" placeholder="Description..." onChange={props.changeDesc}>
                </textarea>
                <button className="submit" onClick={props.submit}>Add Todo</button>
            </div>
        );
    }
    
    export default AddTodo;
    
    今すぐ使用する時間ですuseEffect() そして、Axiosはデータベース内のすべてのデータを保存します.
    これが最後ですApp.tsx :
    import axios from "axios";
    import { nanoid } from "nanoid";
    import React, { useEffect, useState } from "react";
    import { TodoType } from "./components/Todo";
    import TodoList from "./components/TodoList";
    
    function App() {
        const [todoList, setTodoList] = useState<TodoType[]>([]);
    
        const [title, setTitle] = useState<string>("");
        const [desc, setDesc] = useState<string>("");
    
        useEffect(() => {
            axios
                .get(process.env.REACT_APP_BACKEND_URL + "/api/get-todo")
                .then((res) => {
                    setTodoList(res.data);
                });
        }, []);
    
        const changeTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
            setTitle(event.currentTarget.value);
        };
    
        const changeDesc = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
            setDesc(event.currentTarget.value);
        };
    
        const changeChecked = (
            event: React.MouseEvent<HTMLInputElement>,
            id: string
        ) => {
            let temp = [...todoList];
            let tempIndex = 0;
            temp.forEach((item, i) => {
                if (item.nanoid === id) {
                    item.checked = !item.checked;
                    tempIndex = i;
                }
            });
            setTodoList(temp);
            let item = todoList[tempIndex];
            axios.put(
                process.env.REACT_APP_BACKEND_URL +
                    `/api/update-todo/${item.nanoid}`,
                    { nanoid: item.nanoid, title: item.title, desc: item.desc, checked: item.checked}
            );
        };
    
        const addTodo = (event: React.MouseEvent<HTMLButtonElement>) => {
            let newTodo: TodoType = {
                nanoid: nanoid(),
                title: title,
                desc: desc,
                checked: false,
            };
            setTodoList([...todoList, newTodo]);
            axios.post(
                process.env.REACT_APP_BACKEND_URL + "/api/add-todo",
                JSON.stringify(newTodo)
            );
        };
    
        return (
            <div className="app-container">
                <header className="app-header">
                    <h1>To-Do List</h1>
                </header>
                <div className="content">
                    <TodoList
                        submit={addTodo}
                        changeDesc={changeDesc}
                        changeTitle={changeTitle}
                        list={todoList}
                        changeChecked={changeChecked}
                    />
                </div>
            </div>
        );
    }
    
    export default App;
    
    それが完了したら、我々はアプリケーションを展開する準備ができます.

    展開


    Herokuを使用してバックエンドを展開し、Githubページをフロントエンドに配置します.私はHerokuと遭遇した唯一の本当の欠点は、それがアイドル状態である場合、バックエンドはもはやアイドル状態になっているときに再起動する必要がありますので、アプリケーションの使用の間に休憩後に長いロード時間を経験することがあります.Githubページは私が問題を抱えたことがない何かです.

    バックエンド展開


    あなたはすでに1つを持っていない場合、Herokuに新しいアカウントを作成し、新しいアプリを作成します.Githubを使って展開するのが最も簡単ですが、Heroku Cliを使うなら、より多くのコントロールを得ます.関係なく、これらはあなたが従う必要がある基本的な手順です.
    単に新しいファイルを作成するProcfile バックエンドのルートでこれを入れます.
    web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
    
    また、必ずpython-dotenv == 0.19.0 あなたにrequirements.txt ファイルを再インストールし、すべてのブートを確実にする依存関係を再インストールします.
    その後に戻るmain.py , を置換し、"*" を指定します"https://<username>.github.io" .
    Githubにプッシュし、展開し、それを行ってみましょう.それが働くならば、あなたは以前に見た同じルートページを見ることができなければなりません.
    アプリの設定に移動し、設定のVarsを明らかにし、DATABASE_URI で設定します.

    フロントエンド展開


    依存関係をインストールしなければならないので、これは少し複雑ですpackage.json , しかし、それはまだかなりまっすぐ前方です.
    エディット.env 'sバックエンドURLはHerokuアプリURLで、コミットしてプッシュします.
    $ yarn add --dev gh-pages
    
    その後、開くことができますpackage.json , そして、"scripts" :
    "predeploy": "yarn build",
    "deploy": "REACT_APP_BACKEND_URL=<backend-url> gh-pages -d build"
    
    また、
    "homepage": "https://<username>.github.io/<project-name>-frontend/"
    
    Githubでは、バックエンドURLと同じ環境変数として機能する秘密を追加し、同じ名前にしてください.
    $ yarn start
    ^C
    $ yarn deploy
    
    すべてうまくいけば、100 %の作業アプリを持っている必要があります.
    このソースコードはここでgithubにあります.
    https://github.com/jackmaster110/farm-stack-tut