はじめに
こんにちは!サイバーセキュリティやゼロトラストアーキテクチャを学んでいる情報系の大学生です。
現在、7日間で「セキュア社内掲示板」をゼロから開発するプロジェクトに挑戦しています。
プロジェクト2日目である今回は、オンメモリで動かしていた初期のAPIから脱却し、データベースを導入して「データを安全かつ確実に永続化する仕組み」を構築しました。
この記事では、Dockerを使った隔離されたPostgreSQL環境の構築から、SQLAlchemy(ORM)を用いてFastAPIとセキュアに連携させるまでの手順を、わかりやすく解説します。
1. 全体像(アーキテクチャ)の理解
コードを書き始める前に、「誰が何の役割をしているのか」を整理しましょう。現実世界の「倉庫システム」に例えると非常にイメージしやすくなります。
-
PostgreSQL + Docker(隔離された安全な倉庫)
データを実際に保存・管理する場所です。ホストOS(自分のPC)に直接データベースをインストールせず、Dockerを使って隔離されたコンテナ(専用の箱)の中で稼働させます。これにより、環境をクリーンに保ちつつセキュリティを高めることができます。 -
FastAPI + Pydantic(厳しい受付と検品所)
外部からのリクエスト(投稿データなど)を受け取る最初の窓口です。Pydanticが「タイトルの文字数は適切か?」「必須項目は揃っているか?」を入り口で厳しくチェック(バリデーション)し、不正なデータはここで弾き返します。 -
SQLAlchemy(倉庫専用の優秀な通訳)
Pythonのコードを、データベースが理解できる言語(SQL)に翻訳して命令を出してくれるORM(Object-Relational Mapping)です。生のSQLを直接書かないことで、SQLインジェクションなどの脆弱性を防ぐ重要な役割も担います。
2. DockerでPostgreSQL環境を構築
ローカル環境を汚さず、再現性の高いデータベース環境を作るために docker-compose.yml を作成します。
services:
db:
image: postgres:16-alpine
container_name: secure-board-db
restart: always
env_file:
- .env
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
セキュリティ・設計のポイント
-
軽量イメージの採用:
alpine版(軽量Linux)を採用することで、不要なパッケージを削ぎ落とし、攻撃対象領域(アタックサーフェス)を最小化しています。 -
機密情報の分離: データベースのパスワード等は
.envファイルに切り出し、コードに直接書き込まない(ハードコードしない)セキュアな設計にしています。
3. SQLAlchemyを用いたDB接続とモデル定義
データベースとやり取りするための土台を作ります。
データベース接続設定 (database.py)
PythonからPostgreSQLへアクセスするためのセッション(通信の窓口)を作成します。
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
from dotenv import load_dotenv
load_dotenv()
# 環境変数から接続情報を取得
DB_USER = os.getenv("POSTGRES_USER", "secure_user")
DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "secure_password")
DB_NAME = os.getenv("POSTGRES_DB", "secure_board_db")
SQLALCHEMY_DATABASE_URL = f"postgresql://{DB_USER}:{DB_PASSWORD}@localhost:5432/{DB_NAME}"
# エンジンとセッションの作成
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
テーブル構造の定義 (models.py)
SQLAlchemyを使って、実際のデータベースのテーブル構造をPythonのクラスとして定義します。
from sqlalchemy import Column, Integer, String
from database import Base
class Article(Base):
__tablename__ = "articles"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False)
content = Column(String, nullable=False)
4. FastAPIでCRUDエンドポイントを実装
いよいよ、API経由でデータベースにデータを読み書きする処理(Create, Read, Delete)を main.py に実装します。
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
import models
from database import SessionLocal, engine
from pydantic import BaseModel
app = FastAPI()
# リクエストごとにDBセッションを安全に管理
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# --- Pydanticモデル(受付用スキーマ) ---
class ArticleCreate(BaseModel):
title: str
content: str
class Article(ArticleCreate):
id: int
model_config = {"from_attributes": True}
# --- エンドポイント ---
# 新規データの保存(Create)
@app.post("/articles", response_model=Article)
def create_article(article: ArticleCreate, db: Session = Depends(get_db)):
# PydanticモデルからSQLAlchemyモデルへ安全に詰め替え
db_article = models.Article(title=article.title, content=article.content)
db.add(db_article)
db.commit() # ここで初めてDBに保存される
db.refresh(db_article)
return db_article
セキュリティ・設計のポイント
-
安全なセッション管理:
Dependsとyieldを使用することで、エラーが起きても確実にデータベース接続を閉じます。これによりコネクションプールの枯渇(DoS状態)を防ぎます。 - PydanticとSQLAlchemyの分離: クライアントから受け取ったデータをそのままDBに突っ込むのではなく、Pydanticで型チェックを通った安全なデータのみを抽出してDBモデルに詰め替えています。これにより、意図しないデータの改ざん(Mass Assignmentなど)を防ぐことができます。
おわりに
今回はFastAPIとデータベースを繋ぎ、「データを安全に永続化する」というバックエンドの非常に重要な土台を構築しました。
「受付・検品(Pydantic) → 翻訳(SQLAlchemy) → 保管(PostgreSQL)」というデータのバケツリレーを意識することで、複雑なシステムもスッキリと理解できるようになります。
次回は、Webアプリケーションの要となる「セッション管理とセキュアな認証基盤(JWTやパスワードハッシュ化)」の実装に挑戦します!