🔰

ハンズオン形式で学ぶバックエンド (FastAPI) 入門

2025/03/01に公開

注意点

この記事はバックエンド初心者向けに書いています。
理解しやすいよう、一部説明を簡略化している点をご了承ください。

はじめに

この記事では、そもそもバックエンドとはどんなようなものなのかというところから始まり、基本的な機能を持ったバックエンドをハンズオン形式で作りながら学ぶことができるようになっています。

使う言語はPythonなので、プログラミング初心者も臆せず、ぜひ挑戦してみてください!

バックエンドとは

おそらく皆さんが一度は触れたことがあるであろう HTML、CSS、JavaScript などは、フロントエンドと呼ばれる領域になります。
フロントエンドとは、WebサービスやWebアプリの画面部分など、ユーザーが見る部分のことを指します。
一方、バックエンドはログイン処理やデータの管理・処理を行うなど、ユーザーからは見えない部分のことを指します。

本格的なWebアプリを作成する場合は、フロントエンドとバックエンドを組み合わせて開発するのが一般的です。

どんな場面でバックエンドが必要なのか

主に、データを保存する必要がある場合に求められます。データベースというものにデータを保存していきます。

例えば、Todoアプリを作ったとします。バックエンドがない状態だと、作成したタスクが保存されていないので、ページをリロードすると消えてしまいます。
もちろん、バックエンドがなくても保存する方法はありますが、データを保存するとなるとデータベースというものを使うのが一般的です。

データベースとは

説明すると長くなるのでざっくりとしか説明しませんが、データベースとは、大量のデータを効率よく保存・管理するためのシステムです。

実際に作ってみる

今回は、FastAPIというPythonのライブラリを使って、Todoアプリのデータを管理するバックエンドを作成していきます。

もし、詰まった場合は
https://github.com/thirdlf03/fastapi-handson/tree/main/samples

サンプルコードを参照してください。

環境の準備

GitHubのCodespacesを使っていくので、もしGitHubに登録していない場合は登録してください。
https://github.com/

ここにアクセスしてください。
https://github.com/thirdlf03/fastapi-handson

アクセスすると、Codeという緑のボタンがあるので

真ん中のCodespacesを選択し、Create codespace on mainをクリックしてください。

こんな画面に飛んだらOKです!

必要なファイルの作成

必要なファイルの作成をしていきます。

ターミナルで以下のコマンドを実行してください。

touch main.py todo.db db.py init.py models.py schemas.py

するとエクスプローラーのところがこのようになるはずです。
こうなってたらコマンド実行できてます!

Step1 とりあえずFastAPIを触ってみる

仮想環境の用意

まず、pythonの仮想環境を用意していきます。
今回は、uvを使って仮想環境を作成します。

ターミナルで以下のコマンドを実行していきます。

仮想環境作成

uv venv --python=3.12

仮想環境有効化

source .venv/bin/activate

activateしたときに、右下にこのような表示が出るのではいを押してください

もし、設定し忘れた場合は

画面の下部にインタープリターの選択というボタンがあるのでクリックしてください。

すると、画面上部にインタープリターの選択というポップアップが出て、一番したにおすすめというのが出るのでこれをクリックしてください。

FastAPIのインストール

uv pip install fastapi

コードを書いていく

実際にmain.pyにコードを書いていきましょう。

main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def hello():
	return {"message": "Hello World"}

Hello Worldの準備ができたので、動かしてみたいんですがこのままだと実行できないのでuvicornというものを入れていきます

Uvicorn インストール

uvicornの説明は詳しくしません。FastAPIを動かすのに必要なものと思ってもらってokです

ターミナルで以下のコマンドを実行してください。

uvicornインストール

uv pip install uvicorn

uvicornで起動してみる

uvicorn "main:app"

uvicornで起動すると、右下の方にブラウザーで開くというのが出るのでクリックしてください。

すると、ブラウザーが開き以下のように表示されてるはずです。


動作が確認できたら、Codespacesに戻りましょう。
uvicron "main:app"と入力したらところをクリックした後、

Windowsの方は
CTRL + C

Macの方は、
control + C

で動作を中断してください。

そうすると、またコマンドが入力できるようになっているはずのでそれを確認してください。

Step2 データベースとFastAPIを繋げてみる

前述した通り、バックエンドはデータベースと繋げてデータを保存することが多いです。
なので、実際にデータベースとFastAPIを繋げてみましょう。

今回は、Sqliteをデータベースとして選びました。

sqlalchemyのインストール

sqlalchemyとは、FastAPIとデータベースを繋げるためによく使われるライブラリです。
ORMと呼ばれるものです。

インストールする

uv pip install sqlalchemy

データベースを使えるようにする

db.pyとinit.pyに以下のようにコードを書いてください。

db.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine("sqlite:///todo.db")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

def get_db():
	db = SessionLocal()
	try:
		yield db
	finally:
		db.close()

init.py

import sqlite3

sql_statements = [
	"""CREATE TABLE IF NOT EXISTS todos (
		id INTEGER PRIMARY KEY,
		title text NOT NULL,
		content text NOT NULL
	);""",

	"""INSERT INTO todos (title, content) 
			VALUES ("First Task", "Hello World")
			"""
]

try:
	with sqlite3.connect('todo.db') as conn:
		cursor = conn.cursor()
		
	for statement in sql_statements:
		cursor.execute(statement)
		
	conn.commit()
	
	print("Table Create")
except sqlite3.OperationalError as e:
	print("Failed to create tables:", e)

db.pyはFastAPIでデータベースを呼び出すために必要な関数 get_db()を定義しています。
init.pyでは、データベースを使うのにテーブルというものが必要なので、テーブルを作成するコードを書いています。

コードを書き終わったら下記のコマンドを実行してください。

python init.py

モデルを作成

sqlalchemyを使っていくのに、必要なものがあるのでmodels.pyに用意していきます。

models.py

from sqlalchemy import Column, Text, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Todo(Base):
	__tablename__ = 'todos'
	
	id = Column(Integer, primary_key=True, autoincrement=True)
	title = Column(Text, nullable=False)
	content = Column(Text, nullable=False)

実際にデータベースを使ってみる

main.pyに以下のようにコードを書いてみましょう。

main.py

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db
from models import Todo

app = FastAPI()

@app.get("/")
def hello():
	return {"message": "Hello World"}
	
@app.get("/todos")
def get_todos(db: Session = Depends(get_db)):
	return db.query(Todo).all()

コード入力後、再度FastAPIを起動する必要があるのですが

uvicorn "main:app"

毎回このコマンド打つのは面倒ですよね
そこで便利なコマンドがあります。
それが、

uvicorn "main:app" --reload

--reloadというものです。
hotreloadといい、ファイルの変更を監視してくれて更新があった場合勝手にuvicornが再起動してくれます。

これを使うことで更新のたびに、CTRL + Cで中断してuvicornを起動するということをしなくて済むようになります。

話を戻して、入力したコードが動いてるかブラウザーで確認していきましょう。

URLの後ろに/todosとつけてみましょう。

すると、このように表示されるはずです

ただ、これ毎回/todos入力するのは面倒臭いですよね。
そこで、FastAPIには便利なものが用意されてます。

Swagger

試しにURLの後ろに/docsとつけてみてください。

すると、このような画面が現れます。

試しに / のところをクリックして、Try it outを押すと


このように結果が返ってきます。

今後、これを使って動作確認をしていきます。
もちろん、--reloadをつけていたらこのページをリロードするだけで簡単に動作確認できます。

Step3に入る前に、API、CRUDについて説明していきます。

APIとは

APIとは、フロントエンドとバックエンドを通信するときに使うものです。
通信する時に、HTTPプロトコルというものを使うんですが、それにはざっくり4のメゾットがあいます。

  • GET
  • POST
  • PUT
  • DELETE
    の4つです。

それぞれ
GET データの取得
POST データの保存
PUT データの更新
DELETE データの削除

で使われています。

FastAPIでは、
@app.
の後ろにgetやpost
()
の中にそれを使う時の名前を定義しています。

例 )
@app.get("/todos")

これはGETメゾットを使って、/todosという名前に接続できるようにするという意味です。

CRUD

データベースを操作するときに、大まかに4つ操作があります。

それが、

  • CREATE
  • READ
  • UPDATE
  • DELETE

です。

それぞれ、

  • CREATE データを保存
  • READ データを読み取る
  • UPDATE データを更新
  • DELETE データを削除

この基本的な操作のことを頭文字を取ってCRUDと呼ばれています。

ここで、感がいい方は気づいているかもしれませんが

  • GET = READ
  • POST = CREATE
  • PUT = UPDATE
  • DELETE = DELETE

と紐づいています。

この後の実装で意識してみてください!

Step3 Postを実装する

実際にデータを保存できるようにしていきます。

その前に、この後使うのでschemaというものを定義しておきます

schemas.py

from pydantic import BaseModel

class TodoCreate(BaseModel):
	title: str
	content: str

実際にpostを定義してみましょう。

前のStepで実装したものとは違い、@app.のあとがpostになっていますね

main.py

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db
from models import Todo
from schemas import TodoCreate

app = FastAPI()

@app.get("/")
def hello():
	return {"message": "Hello World"}
	
@app.get("/todos")
def get_todos(db: Session = Depends(get_db)):
	return db.query(Todo).all()

@app.post("/todos")
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
	new_todo = Todo(title = todo.title, content = todo.content)
	db.add(new_todo)
	db.commit()
	return db.query(Todo).all()

コードを書き終わったら、動作確認してみましょう!

POST /todosをクリックした後、 Try it outを押してみましょう。
すると、白いところが入力できるようになっているので好きな文字を入力してみましょう


Executeを押すと

データが追加されてますね!

Step4 Putを実装する

Putを実装していきます。

まず、schemaを定義します。

schemas.py

from pydantic import BaseModel
from typing import Optional

class TodoCreate(BaseModel):
	title: str
	content: str

class TodoUpdate(BaseModel):
	title: Optional[str] = None
	content: Optional[str] = None

その後、putを実装していきます。

main.py

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db
from models import Todo
from schemas import TodoCreate, TodoUpdate

app = FastAPI()

@app.get("/")
def hello():
	return {"message": "Hello World"}
	
@app.get("/todos")
def get_todos(db: Session = Depends(get_db)):
	return db.query(Todo).all()

@app.post("/todos")
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
	new_todo = Todo(title = todo.title, content = todo.content)
	db.add(new_todo)
	db.commit()
	return db.query(Todo).all()
	
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)):
	target_todo = db.query(Todo).filter(Todo.id == todo_id).first()
	target_todo.title = todo.title
	target_todo.content = todo.content
	return db.query(Todo).all()

コードを書き終わったら、確認しにいきましょう。
todo_idに更新したいidをした後、更新内容を書きましょう。

すると、このように内容が変わっているのが確認できると思います。

しかし、今のままだと欠陥があります。
このようにtitleかcontentどっちかだけ更新しようとすると

contentにnullが入ってしまいましたね。

これを直していきましょう!

main.py

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db
from models import Todo
from schemas import TodoCreate, TodoUpdate

app = FastAPI()

@app.get("/")
def hello():
	return {"message": "Hello World"}
	
@app.get("/todos")
def get_todos(db: Session = Depends(get_db)):
	return db.query(Todo).all()

@app.post("/todos")
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
	new_todo = Todo(title = todo.title, content = todo.content)
	db.add(new_todo)
	db.commit()
	return db.query(Todo).all()
	
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)):
	target_todo = db.query(Todo).filter(Todo.id == todo_id).first()
	if todo.title != None:
		target_todo.title = todo.title

	if todo.content != None:
		target_todo.content = todo.content
	
	db.commit()
	return db.query(Todo).all()

やってることは簡単で、入力されたデータが空じゃない時に更新するようにしました。

試してみましょう
contentを消した状態で、excuteしてみると

片方だけ更新されるようになりましたね

Step5に入る前に、queryとbodyについて説明します。

まず、PostとPutの違いをみてください

Post は /todos
Put は /todos/1
となっていますね

この1の部分はクエリと呼ばれるものになります。

query

今回のように、何かを指定するみたいな時に使います

実装例)
@app.put("/todos/{todo_id}")

body

queryは/todos/の中に含まれてますが、bodyは含まれてませんよね?
このように、公に公開したくないデータをバックエンドに送る時はbodyを使います。

実装例)
schemaで定義したもの

from pydantic import BaseModel

class TodoCreate(BaseModel):
	title: str
	content: str

これを関数の引数に指定することでbody扱いになります。

@app.post("/todos")
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):

Step5 Deleteを実装してみる

main.py


from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db
from models import Todo
from schemas import TodoCreate, TodoUpdate

app = FastAPI()

@app.get("/")
def hello():
	return {"message": "Hello World"}
	
@app.get("/todos")
def get_todos(db: Session = Depends(get_db)):
	return db.query(Todo).all()

@app.post("/todos")
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
	new_todo = Todo(title = todo.title, content = todo.content)
	db.add(new_todo)
	db.commit()
	return db.query(Todo).all()
	
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)):
	target_todo = db.query(Todo).filter(Todo.id == todo_id).first()
	if todo.title != None:
		target_todo.title = todo.title

	if todo.content != None:
		target_todo.content = todo.content
	
	db.commit()
	return db.query(Todo).all()
	
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
	target_todo = db.query(Todo).filter(Todo.id == todo_id).first()
	db.delete(target_todo)
	db.commit()
	return db.query(Todo).all()

実際に使ってみましょう


削除されてますね

最後に

お疲れ様です

ここまで実装してきたものを改良していくだけで、基本的なバックエンドを作成できるようになっています。

ただ、あくまで基本的なものなのでさらに発展したものを勉強したい方は以下のものがおすすめです

質問や要望あれば気軽にコメントしてください!

GitHubで編集を提案

Discussion