はじめに~SQLModelとは
FastAPIでよく使われるSQLのライブラリの中でも、比較的簡単に記述できるSQLModelについてご紹介します。
SQLModelは、FastAPIの作者であるtiangolo氏が開発した、SQLAlchemyとPydanticの良いとこ取りをしたPython用ORMライブラリです。
今回使用するライブラリ
- FastAPI
- SQLModel
- その他:
typing
,datetime
(標準ライブラリ)
必要に応じて、uvicorn
やsqlalchemy
もプロジェクトに追加してください。
SQLModelの特徴
SQLAlchemyベース
→ SQLAlchemyのORM機能やDB接続機能をそのまま活用可能。Pydanticと統合
→ 型ヒントとバリデーションがPydanticの書き方で可能。APIとDBモデルの共通化に優れる。シンプルな記述
→ 最小限のコードでテーブル設計ができ、FastAPIとの親和性が高い。
モデル定義例
from sqlmodel import SQLModel, Field class Ingredient(SQLModel, table=True): id: int = Field(default=None, primary_key=True) name: str reading: str type: str
こんな方におすすめ
- FastAPIでAPIとDBモデルを一元管理したい方
- 型安全・バリデーションを重視したい方
- SQLAlchemyの複雑な記法が苦手な方
注意点
- 複雑なDB設計には不向き。あくまで「シンプルな用途向け」
- 既存のSQLAlchemyプロジェクトへの導入は慎重に検討が必要
まとめ
SQLModelは、FastAPIでAPIとDBを型安全に、簡潔に扱いたい方に非常に便利なライブラリです。 新規にFastAPI + SQLiteなどで構築する際には、SQLModelを採用するのがおすすめです。
今回作成するモデル構成
各テーブルの説明
食材テーブル (ingredients
)
id
: 主キーname
: 食材名reading
: 読み方type
: 種類(選択肢形式)
レシピテーブル (recipes
)
id
: 主キーname
: レシピ名url
: YouTubeのURLthumbnail
: サムネイル画像URLnotes
: 備考created_at
: 作成日時updated_at
: 更新日時
中間テーブル(レシピ × 食材)recipe_ingredients
recipe_id
: レシピID(外部キー)ingredient_id
: 食材ID(外部キー)
カテゴリテーブル (categories
)
id
: 主キーname
: カテゴリ名(例: パン・麺類、主菜、副菜 等)
実際に記述していく
ステップ1:まずは関係性はとりあえず無視して個別にテーブルを定義
from sqlmodel import SQLModel, Field from typing import Optional class Ingredient(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str reading: str = Field(max_length=20, description="食材の読み") type: str class Category(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str class Recipe(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str = Field(max_length=30, unique=True) url: str = Field(max_length=255, unique=True) thumbnail: str = Field(max_length=255, unique=True, description="サムネイル画像のURL") notes: str | None = Field(default=None, nullable=True) created_at: str | None = Field(default=None) updated_at: str | None = Field(default=None)
Fieldで使う主な引数
default
: デフォルト値default_factory
: デフォルト値を生成する関数primary_key
: 主キー指定nullable
: NULL許容unique
: 一意制約index
: インデックス作成foreign_key
: 外部キー指定("テーブル名.カラム名")max_length
,min_length
: 文字列長制約description
,title
: 説明・タイトルgt
,ge
,lt
,le
: 数値の制約(不等号)
ステップ2 1対多および多対多の関係性を記述
多対多の関係(レシピ×食材)
・多対多の関係の場合、中間テーブルを記述する ・個別のテーブルにRelationshipでlink_modelを指定
中間テーブル
class RecipeIngredientLink(SQLModel, table=True): recipe_id: int = Field(default=None, foreign_key="recipe.id", primary_key=True) ingredient_id: int = Field(default=None, foreign_key="ingredient.id", primary_key=True)
Relationshipでlink_modelを指定 例:
recipes: List["Recipe"] = Relationship(back_populates="ingredients", link_model=RecipeIngredientLink) # 多対多の関係
リレーション定義
class Ingredient(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str reading: str = Field(max_length=20, description="食材の読み") type: str recipes: List["Recipe"] = Relationship(back_populates="ingredients", link_model=RecipeIngredientLink) class Recipe(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str = Field(max_length=30, unique=True) url: str = Field(max_length=255, unique=True) thumbnail: str = Field(max_length=255, unique=True, description="サムネイル画像のURL") notes: str | None = Field(default=None, nullable=True) created_at: Optional[datetime] = Field(default_factory=datetime.utcnow) updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, onupdate=datetime.utcnow) ingredients: List[Ingredient] = Relationship(back_populates="recipes", link_model=RecipeIngredientLink)
1対多の関係(カテゴリ×レシピ)
1対多の関係はそのまま記述できる
- 子側(多側)に外部キー(category_id)を持たせる
- 親側(1側)にRelationshipでリストを持たせる
class Category(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str recipes: List["Recipe"] = Relationship(back_populates="category") class Recipe(SQLModel, table=True): # (前略) category_id: Optional[int] = Field(default=None, foreign_key="category.id") category: Optional[Category] = Relationship(back_populates="recipes")
最終的な完成形
from sqlmodel import SQLModel, Field, Relationship from typing import List, Optional from datetime import datetime class RecipeIngredientLink(SQLModel, table=True): recipe_id: int = Field(default=None, foreign_key="recipe.id", primary_key=True) ingredient_id: int = Field(default=None, foreign_key="ingredient.id", primary_key=True) class Ingredient(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str reading: str = Field(max_length=20, description="食材の読み") type: str recipes: List["Recipe"] = Relationship(back_populates="ingredients", link_model=RecipeIngredientLink) class Category(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str recipes: List["Recipe"] = Relationship(back_populates="category") class Recipe(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str = Field(max_length=30, unique=True) url: str = Field(max_length=255, unique=True) thumbnail: str = Field(max_length=255, unique=True, description="サムネイル画像のURL") notes: str | None = Field(default=None, nullable=True) created_at: Optional[datetime] = Field(default_factory=datetime.utcnow) updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, onupdate=datetime.utcnow) ingredients: List[Ingredient] = Relationship(back_populates="recipes", link_model=RecipeIngredientLink) category_id: Optional[int] = Field(default=None, foreign_key="category.id") category: Optional[Category] = Relationship(back_populates="recipes")
おわりに
本記事では、FastAPIと親和性の高いSQLModelを用いて、1対多・多対多のテーブル設計をどのように記述するかを実例とともに解説しました。
初学者でも手軽に実装でき、型安全かつ拡張性のあるDB設計が可能です。ぜひ、実際のプロジェクトで活用してみてください。