ProgateでPythonを学んで超・簡単なTodoアプリを作ってみた。

# 初めに
こんにちは！Progateの学生アンバサダーのnakaです🦜
学生アンバサダーらしいことがしたいと思い、今回のアドベンドカレンダーに参加させていただきました。

# 私のこと
・普段はフロントエンド（React，Nextで開発中）
・バックエンドは未経験
・Pythonは授業で触った程度

普段は↑の通りフロントエンドを触っているのですが、今回FastAPI使ってバックエンドに挑戦したいと思いました。
## FastAPIとは
FastAPIはPythonでRESTful APIを構築するためのWebフレームワークです
つまり・・・FastAPIを動かすにはPythonを学ぶ必要があります🧐
しかし、私は実験でソースコードを扱った程度であるためあまりコード自体の理解度は低いです

どうやってPythonを勉強しようかなぁとおもっていたところ、ProgateのPythonタスクを発見しました🌟

https://prog-8.com/courses/python

こちらのタスクでは基礎構文から詳しい応用部分まで幅広く学べます。
私はクラス部分の理解があまりできていなかったので、とても参考になりました。
またProgateのタスクはレクチャー部分と実践部分が分かれており、コードを実際に書いて学ぶ構成になっていたので技術書を買ってついつい満足しちゃう私にとっては手が動かしやすくとてもありがたい構成でした🙏

こちらのレクチャーでPythonの基礎構文を一通り学んだので早速実践チャレンジしていきます🔥

## 作るもの
React，Python，Dockerを利用した超簡単なtodoアプリを作ります。
もしReactをまだ勉強したことがなければ合わせてReactのProgateコースも勉強すると実装できると思います👍

今回作ったリポジトリは以下の部分に置いています。

・バックエンド
https://github.com/naka520/Progate-advend-back
・フロントエンド
https://github.com/naka520/my-todo-app

## フロントエンド
フロントエンドはReactを利用しています。シンプルなページ一枚なのでApp.tsx上でPOSTする部分などをサラッと記載しています。ここでAPI連携のURLが正しいかチェックしておくことが大事です！URLを間違っていたり形式が正しくないと永遠にエラーが出続けるときがしばしばあります（笑）


```
import React, { useState, useEffect } from "react";
import axios from "axios";
import TodoList from "./components/TodoList";
import AddTodoForm from "./components/AddTodoForm";

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

const App: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  // ToDoアイテムを取得する関数
  const fetchTodos = async () => {
    try {
      const response = await axios.get<Todo[]>("http://localhost:5000/todos");
      setTodos(response.data);
    } catch (error) {
      console.error("Error fetching todos", error);
    }
  };

  // コンポーネントのマウント時にToDoアイテムを取得
  useEffect(() => {
    fetchTodos();
  }, []);

  // ToDoアイテムの追加
  const addTodo = async (text: string) => {
    try {
      // 新しいToDoアイテムのIDを生成する
      const newId = new Date().getTime(); // 現在のタイムスタンプをIDとして使用

      const newTodo = {
        id: newId,
        text,
        completed: false,
      };

      const response = await axios.post<Todo>(
        "http://localhost:5000/todos",
        newTodo
      );
      setTodos([...todos, response.data]);
    } catch (error) {
      console.error("Error adding todo", error);
    }
  };
  // ToDoアイテムの更新
  const toggleTodo = async (id: number) => {
    try {
      const todo = todos.find(t => t.id === id);
      if (!todo) return;
      const updatedTodo = { ...todo, completed: !todo.completed };
      await axios.put(`http://localhost:5000/todos/${id}`, updatedTodo);
      setTodos(todos.map(t => (t.id === id ? updatedTodo : t)));
    } catch (error) {
      console.error("Error toggling todo", error);
    }
  };

  return (
    <div className="container mx-auto mt-10">
      <AddTodoForm addTodo={addTodo} />
      <TodoList todos={todos} toggleTodo={toggleTodo} />
    </div>
  );
};

export default App;
```
## バックエンド
バックエンドはDockerを利用してFastAPIを動かしています。
ポートのマッピングを忘れないようにしましょう。
今回はmain.pyだけ書いています。
FastAPIはPythonのフレームワークですので、前に勉強した構文などがふんだんに使われています🔍

```
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
from fastapi.middleware.cors import CORSMiddleware


app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # すべてのオリジンを許可
    allow_credentials=True,
    allow_methods=["*"],  # すべてのHTTPメソッドを許可
    allow_headers=["*"],  # すべてのHTTPヘッダーを許可
)

class TodoItem(BaseModel):
    id: int
    text: str
    completed: bool

todos: List[TodoItem] = []

@app.get("/todos", response_model=List[TodoItem])
async def read_todos():
    return todos

@app.post("/todos", response_model=TodoItem)
async def create_todo(todo_item: TodoItem):
    todos.append(todo_item)
    return todo_item

@app.put("/todos/{todo_id}", response_model=TodoItem)
async def update_todo(todo_id: int, todo_item: TodoItem):
    for index, item in enumerate(todos):
        if item.id == todo_id:
            todos[index] = todo_item
            return todo_item
    raise HTTPException(status_code=404, detail="Todo not found")

@app.delete("/todos/{todo_id}")
async def delete_todo(todo_id: int):
    global todos
    todos = [item for item in todos if item.id != todo_id]
    return {"message": "Todo deleted successfully"}

```

Dockerの立ちあげ方も以下に記載しておきます。
```
docker build -t [イメージ名] .
```

```
docker run -p  5000:80 -d --name [コンテナ名] [イメージ名]
```
ポートの部分やコンテナの部分は各自で設定してください！

## 実際に動かした様子

todoに「寝る」を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983442/50b26479-4b11-614d-a628-14e5453f136a.png)

追加した結果、「寝る」が新しく登録されました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983442/d2ba7cdf-53cb-0e43-0807-3bdcba94596d.png)

バックエンドの方でも確認してみましょう。FastAPIではdocsでバックエンドのAPIについて確認を行えます。url/docsでdocs画面は確認できます👍

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983442/751f59a2-3a70-294a-e011-83d31b085841.png)

GETを実行すると先ほど追加したタスクが確認できます🌟

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983442/7f66b163-e12d-b3c5-187b-08f89368bc0e.png)

## さらに豪華に実装するには

今はローカルで簡素なtodoとなっています。更に見た目や機能を豪華にするには

#### フロント
・MUIやTailWindなどを利用した実装を行う
・Figmaを利用してデザイン案を考える
・Azure Static Web Appsを利用してデプロイ

#### バックエンド
・DBを利用する
・App　Serviceなどを利用してデプロイ

など色々手法があります。デプロイ方法については別の記事に記載しているのでぜひそちらもご覧ください👍

## 終わりに
最近卒研が忙しくなりなかなか新規のコースに挑戦できていなかったのですが、冬休みに挑戦してまたアウトプットに記事を書いていこうかなと思います🔥

皆さんもProgateのサービス是非利用して新しい言語に挑戦したり、サービスを作ってみてください📒