NaN/NaN/NaN

PythonでWebアプリケーションを構築するなら、Django・FastAPI・Flaskのいずれかを選ぶケースがほとんどです。どのフレームワークでも、PDF生成APIを統合すれば請求書・証明書・レポートなどを動的に生成できます。

この記事では、FUNBREW PDFのAPIをDjango・FastAPI・Flaskそれぞれに組み込む方法を、実践的なコード例とともに解説します。同期・非同期処理、テンプレートからのPDF生成、セキュリティ対策、そしてpytestによるテストまでカバーします。

Pythonでの基本的なAPI呼び出しは言語別クイックスタートを、エラーハンドリングの詳細はエラーハンドリングガイドを参照してください。

準備

APIキーの取得

無料アカウントを作成し、ダッシュボードからAPIキーを発行します。

# .envファイルに設定
FUNBREW_PDF_API_KEY="sk-your-api-key"

APIキーの安全な管理方法はセキュリティガイドで詳しく解説しています。

共通ライブラリのインストール

pip install httpx python-dotenv

httpxはasync/syncの両方に対応したHTTPクライアントで、DjangoでもFastAPIでも使えます。

Django編

プロジェクト構成

myproject/
├── myproject/
│   ├── settings.py
│   └── urls.py
├── pdf_app/
│   ├── views.py
│   ├── urls.py
│   ├── services.py
│   └── templates/
│       └── pdf_app/
│           └── invoice.html
└── manage.py

settings.py — API設定

環境変数からAPIキーを読み込みます。ハードコーディングは絶対に避けてください。

# myproject/settings.py
import os
from dotenv import load_dotenv

load_dotenv()

FUNBREW_PDF_API_KEY = os.getenv("FUNBREW_PDF_API_KEY")
FUNBREW_PDF_API_URL = "https://api.pdf.funbrew.cloud/v1/pdf/from-html"

services.py — PDF生成サービス

ビジネスロジックをviewから分離し、再利用性を高めます。

# pdf_app/services.py
import httpx
from django.conf import settings


class PDFGenerationError(Exception):
    """PDF生成に失敗した場合の例外"""
    pass


def generate_pdf(html: str, options: dict | None = None) -> bytes:
    """HTMLからPDFを生成して、バイナリを返す"""
    payload = {
        "html": html,
        "format": "A4",
        "engine": "quality",
    }
    if options:
        payload.update(options)

    response = httpx.post(
        settings.FUNBREW_PDF_API_URL,
        json=payload,
        headers={
            "Authorization": f"Bearer {settings.FUNBREW_PDF_API_KEY}",
            "Content-Type": "application/json",
        },
        timeout=60.0,
    )

    if response.status_code != 200:
        raise PDFGenerationError(
            f"PDF generation failed: {response.status_code} - {response.text}"
        )

    return response.content

views.py — PDFエンドポイント

# pdf_app/views.py
from django.http import HttpResponse, JsonResponse
from django.template.loader import render_to_string
from django.views.decorators.http import require_POST

from .services import generate_pdf, PDFGenerationError


@require_POST
def generate_invoice_pdf(request, invoice_id):
    """請求書PDFを生成してHTTPレスポンスとして返す"""
    # 実際にはDBから請求書データを取得
    invoice_data = {
        "invoice_id": invoice_id,
        "company": "サンプル株式会社",
        "items": [
            {"name": "PDF API Proプラン", "quantity": 1, "price": 4980},
            {"name": "追加API呼び出し 500件", "quantity": 1, "price": 2000},
        ],
        "total": 6980,
    }

    # Djangoテンプレートを使ってHTMLを生成
    html = render_to_string("pdf_app/invoice.html", invoice_data)

    try:
        pdf_bytes = generate_pdf(html)
    except PDFGenerationError as e:
        return JsonResponse({"error": str(e)}, status=502)

    response = HttpResponse(pdf_bytes, content_type="application/pdf")
    response["Content-Disposition"] = f'attachment; filename="invoice-{invoice_id}.pdf"'
    return response

urls.py — ルーティング

# pdf_app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path(
        "invoices/<int:invoice_id>/pdf/",
        views.generate_invoice_pdf,
        name="invoice-pdf",
    ),
]
# myproject/urls.py
from django.urls import path, include

urlpatterns = [
    path("api/", include("pdf_app.urls")),
]

テンプレートからのPDF生成

Djangoテンプレートエンジンを使えば、HTMLテンプレートにデータを差し込んでPDFを生成できます。テンプレート設計のコツはテンプレートエンジンガイドで解説しています。

<!-- pdf_app/templates/pdf_app/invoice.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    body {
      font-family: 'Noto Sans JP', sans-serif;
      padding: 40px;
      color: #1a1a1a;
    }
    .header {
      display: flex;
      justify-content: space-between;
      border-bottom: 3px solid #1a56db;
      padding-bottom: 16px;
      margin-bottom: 32px;
    }
    h1 { font-size: 24px; margin: 0; }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 24px;
    }
    th, td {
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #e5e7eb;
    }
    th { background: #f9fafb; font-weight: 600; }
    .total {
      text-align: right;
      font-size: 20px;
      font-weight: 700;
      margin-top: 24px;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>請求書</h1>
    <div>
      <p>請求書番号: #{{ invoice_id }}</p>
      <p>{{ company }}</p>
    </div>
  </div>

  <table>
    <thead>
      <tr>
        <th>項目</th>
        <th>数量</th>
        <th>金額</th>
      </tr>
    </thead>
    <tbody>
      {% for item in items %}
      <tr>
        <td>{{ item.name }}</td>
        <td>{{ item.quantity }}</td>
        <td>&yen;{{ item.price|stringformat:",.0f" }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>

  <div class="total">合計: &yen;{{ total|stringformat:",.0f" }}</div>
</body>
</html>

Celeryで非同期PDF生成(Django)

大量のPDFを生成する場合や、PDF生成に時間がかかる場合は、Celeryでバックグラウンド処理にするのが定番です。バッチ処理の詳細はPDF一括生成ガイドを参照してください。

# pdf_app/tasks.py
from celery import shared_task
from django.core.files.storage import default_storage

from .services import generate_pdf, PDFGenerationError


@shared_task(
    bind=True,
    max_retries=3,
    default_retry_delay=10,
)
def generate_pdf_async(self, html: str, filename: str, options: dict | None = None):
    """非同期でPDFを生成し、ストレージに保存する"""
    try:
        pdf_bytes = generate_pdf(html, options)
        path = f"generated_pdfs/{filename}"
        default_storage.save(path, pdf_bytes)
        return {"status": "success", "path": path}
    except PDFGenerationError as exc:
        # 指数バックオフでリトライ
        raise self.retry(exc=exc, countdown=2 ** self.request.retries * 10)
# pdf_app/views.py(非同期版エンドポイント追加)
from django.http import JsonResponse
from django.template.loader import render_to_string
from django.views.decorators.http import require_POST

from .tasks import generate_pdf_async


@require_POST
def generate_invoice_pdf_async(request, invoice_id):
    """PDFの非同期生成をキューに投入する"""
    invoice_data = get_invoice_data(invoice_id)  # DB取得関数
    html = render_to_string("pdf_app/invoice.html", invoice_data)

    task = generate_pdf_async.delay(
        html=html,
        filename=f"invoice-{invoice_id}.pdf",
    )

    return JsonResponse({
        "task_id": task.id,
        "status": "processing",
        "poll_url": f"/api/tasks/{task.id}/status/",
    })

FastAPI編

プロジェクト構成

myproject/
├── app/
│   ├── main.py
│   ├── config.py
│   ├── services/
│   │   └── pdf_service.py
│   ├── routers/
│   │   └── pdf.py
│   └── templates/
│       └── report.html
├── tests/
│   └── test_pdf.py
└── requirements.txt

config.py — 設定管理

Pydantic Settingsで型安全に環境変数を管理します。

# app/config.py
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    funbrew_pdf_api_key: str
    funbrew_pdf_api_url: str = "https://api.pdf.funbrew.cloud/v1/pdf/from-html"

    class Config:
        env_file = ".env"


settings = Settings()

pdf_service.py — 非同期PDF生成サービス

FastAPIのasync/awaitを最大限活用して、ノンブロッキングでPDFを生成します。

# app/services/pdf_service.py
import httpx

from app.config import settings


class PDFGenerationError(Exception):
    def __init__(self, status_code: int, detail: str):
        self.status_code = status_code
        self.detail = detail


async def generate_pdf(html: str, options: dict | None = None) -> bytes:
    """非同期でHTMLからPDFを生成する"""
    payload = {
        "html": html,
        "format": "A4",
        "engine": "quality",
    }
    if options:
        payload.update(options)

    async with httpx.AsyncClient(timeout=60.0) as client:
        response = await client.post(
            settings.funbrew_pdf_api_url,
            json=payload,
            headers={
                "Authorization": f"Bearer {settings.funbrew_pdf_api_key}",
                "Content-Type": "application/json",
            },
        )

    if response.status_code != 200:
        raise PDFGenerationError(
            status_code=response.status_code,
            detail=response.text,
        )

    return response.content

routers/pdf.py — エンドポイント定義

# app/routers/pdf.py
from fastapi import APIRouter, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel

from app.services.pdf_service import generate_pdf, PDFGenerationError

router = APIRouter(prefix="/pdf", tags=["PDF"])


class PDFRequest(BaseModel):
    html: str
    filename: str = "document.pdf"
    format: str = "A4"
    engine: str = "quality"


@router.post("/generate")
async def generate_pdf_endpoint(req: PDFRequest):
    """HTMLからPDFを生成してバイナリで返す"""
    try:
        pdf_bytes = await generate_pdf(
            html=req.html,
            options={"format": req.format, "engine": req.engine},
        )
    except PDFGenerationError as e:
        raise HTTPException(status_code=e.status_code, detail=e.detail)

    return Response(
        content=pdf_bytes,
        media_type="application/pdf",
        headers={
            "Content-Disposition": f'attachment; filename="{req.filename}"',
        },
    )

テンプレートとJinja2によるPDF生成

FastAPIはJinja2をネイティブサポートしています。テンプレートにデータを差し込んでPDFを生成するパターンです。

# app/routers/pdf.py(テンプレートベースのエンドポイント追加)
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import Response
from fastapi.templating import Jinja2Templates

from app.services.pdf_service import generate_pdf, PDFGenerationError

router = APIRouter(prefix="/pdf", tags=["PDF"])
templates = Jinja2Templates(directory="app/templates")


@router.post("/reports/{report_id}")
async def generate_report_pdf(report_id: int, request: Request):
    """レポートPDFをテンプレートから生成する"""
    # 実際にはDBからデータを取得
    report_data = {
        "request": request,
        "report_id": report_id,
        "title": "月次レポート",
        "metrics": [
            {"name": "PDF生成件数", "value": "1,234"},
            {"name": "平均レスポンス時間", "value": "0.8秒"},
            {"name": "成功率", "value": "99.7%"},
        ],
    }

    # Jinja2テンプレートでHTMLを生成
    html = templates.get_template("report.html").render(report_data)

    try:
        pdf_bytes = await generate_pdf(html)
    except PDFGenerationError as e:
        raise HTTPException(status_code=502, detail="PDF generation failed")

    return Response(
        content=pdf_bytes,
        media_type="application/pdf",
        headers={
            "Content-Disposition": f'attachment; filename="report-{report_id}.pdf"',
        },
    )

main.py — アプリケーション初期化

# app/main.py
from fastapi import FastAPI

from app.routers import pdf

app = FastAPI(
    title="PDF Generation Service",
    version="1.0.0",
)

app.include_router(pdf.router, prefix="/api/v1")

BackgroundTasksで非同期PDF生成(FastAPI)

FastAPIのBackgroundTasksを使えば、Celeryなしで軽量な非同期処理を実現できます。

# app/routers/pdf.py(BackgroundTasks版)
import uuid
from pathlib import Path

from fastapi import APIRouter, BackgroundTasks, HTTPException
from pydantic import BaseModel

from app.services.pdf_service import generate_pdf

router = APIRouter(prefix="/pdf", tags=["PDF"])

# メモリ内のタスクストア(本番ではRedis等を使用)
task_store: dict[str, dict] = {}


class AsyncPDFRequest(BaseModel):
    html: str
    filename: str = "document.pdf"


async def _generate_and_save(task_id: str, html: str, filename: str):
    """バックグラウンドでPDFを生成し、ファイルに保存する"""
    try:
        pdf_bytes = await generate_pdf(html)
        output_dir = Path("storage/generated_pdfs")
        output_dir.mkdir(parents=True, exist_ok=True)
        output_path = output_dir / filename
        output_path.write_bytes(pdf_bytes)
        task_store[task_id] = {"status": "completed", "path": str(output_path)}
    except Exception as e:
        task_store[task_id] = {"status": "failed", "error": str(e)}


@router.post("/generate/async")
async def generate_pdf_async(req: AsyncPDFRequest, background_tasks: BackgroundTasks):
    """PDFの非同期生成を開始する"""
    task_id = str(uuid.uuid4())
    task_store[task_id] = {"status": "processing"}

    background_tasks.add_task(_generate_and_save, task_id, req.html, req.filename)

    return {"task_id": task_id, "status": "processing"}


@router.get("/tasks/{task_id}")
async def get_task_status(task_id: str):
    """非同期タスクの状態を確認する"""
    task = task_store.get(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return task

Webhookで生成完了を通知する方法はWebhook連携ガイドを参照してください。

Flask編

プロジェクト構成

myproject/
├── app/
│   ├── __init__.py
│   ├── config.py
│   ├── services/
│   │   └── pdf_service.py
│   └── pdf/
│       ├── __init__.py
│       ├── routes.py
│       └── templates/
│           └── invoice.html
├── tests/
│   └── test_pdf.py
└── requirements.txt

config.py — 設定管理

# app/config.py
import os
from dotenv import load_dotenv

load_dotenv()


class Config:
    FUNBREW_PDF_API_KEY = os.getenv("FUNBREW_PDF_API_KEY")
    FUNBREW_PDF_API_URL = "https://api.pdf.funbrew.cloud/v1/pdf/from-html"

services/pdf_service.py — PDF生成サービス

Flaskではhttpxの同期クライアントをそのまま使えます。

# app/services/pdf_service.py
import httpx
from flask import current_app


class PDFGenerationError(Exception):
    """PDF生成に失敗した場合の例外"""
    pass


def generate_pdf(html: str, options: dict | None = None) -> bytes:
    """HTMLからPDFを生成して、バイナリを返す"""
    payload = {
        "html": html,
        "format": "A4",
        "engine": "quality",
    }
    if options:
        payload.update(options)

    response = httpx.post(
        current_app.config["FUNBREW_PDF_API_URL"],
        json=payload,
        headers={
            "Authorization": f"Bearer {current_app.config['FUNBREW_PDF_API_KEY']}",
            "Content-Type": "application/json",
        },
        timeout=60.0,
    )

    if response.status_code != 200:
        raise PDFGenerationError(
            f"PDF generation failed: {response.status_code} - {response.text}"
        )

    return response.content

基本的なFlaskルートでのPDF生成

# app/pdf/routes.py
from flask import Blueprint, render_template, make_response, jsonify

from app.services.pdf_service import generate_pdf, PDFGenerationError

pdf_bp = Blueprint("pdf", __name__, template_folder="templates")


@pdf_bp.route("/invoices/<int:invoice_id>/pdf", methods=["POST"])
def generate_invoice_pdf(invoice_id):
    """請求書PDFを生成してHTTPレスポンスとして返す"""
    # 実際にはDBから請求書データを取得
    invoice_data = {
        "invoice_id": invoice_id,
        "company": "サンプル株式会社",
        "items": [
            {"name": "PDF API Proプラン", "quantity": 1, "price": 4980},
            {"name": "追加API呼び出し 500件", "quantity": 1, "price": 2000},
        ],
        "total": 6980,
    }

    # Jinja2テンプレートでHTMLを生成
    html = render_template("invoice.html", **invoice_data)

    try:
        pdf_bytes = generate_pdf(html)
    except PDFGenerationError as e:
        return jsonify({"error": str(e)}), 502

    response = make_response(pdf_bytes)
    response.headers["Content-Type"] = "application/pdf"
    response.headers["Content-Disposition"] = (
        f'attachment; filename="invoice-{invoice_id}.pdf"'
    )
    return response

Flask-RESTfulでのAPI実装

Flask-RESTfulを使うと、よりREST APIらしい構成にできます。

# app/pdf/resources.py
from flask import request
from flask_restful import Resource

from app.services.pdf_service import generate_pdf, PDFGenerationError


class PDFResource(Resource):
    def post(self):
        """HTMLからPDFを生成してバイナリを返す"""
        data = request.get_json()
        if not data or "html" not in data:
            return {"error": "html field is required"}, 400

        html = data["html"]
        filename = data.get("filename", "document.pdf")
        options = {
            "format": data.get("format", "A4"),
            "engine": data.get("engine", "quality"),
        }

        try:
            pdf_bytes = generate_pdf(html, options)
        except PDFGenerationError as e:
            return {"error": str(e)}, 502

        from flask import make_response
        response = make_response(pdf_bytes)
        response.headers["Content-Type"] = "application/pdf"
        response.headers["Content-Disposition"] = (
            f'attachment; filename="{filename}"'
        )
        return response
# app/__init__.py(Flask-RESTful版)
from flask import Flask
from flask_restful import Api

from app.config import Config
from app.pdf.resources import PDFResource


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    api = Api(app)
    api.add_resource(PDFResource, "/api/v1/pdf/generate")

    return app

Blueprintを使った構成例

大規模アプリでは、Blueprintでモジュールを分割するのが定番パターンです。

# app/pdf/__init__.py
from flask import Blueprint

pdf_bp = Blueprint(
    "pdf",
    __name__,
    url_prefix="/api/v1/pdf",
    template_folder="templates",
)

from app.pdf import routes  # noqa: E402, F401
# app/__init__.py(Blueprint版)
from flask import Flask

from app.config import Config
from app.pdf import pdf_bp


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    app.register_blueprint(pdf_bp)

    return app

これでPDFルートは /api/v1/pdf/invoices/<id>/pdf としてアクセスできます。Blueprintを活用すれば、請求書・証明書・レポートなど用途ごとにモジュールを分けて管理できます。テンプレート設計のコツはテンプレートエンジンガイドを参照してください。

セキュリティ考慮事項

APIキーの管理はPDF API統合における最も重要なセキュリティポイントです。詳細はセキュリティガイドに譲りますが、最低限以下を守ってください。

APIキーをソースコードに書かない

# NG: ハードコーディング
API_KEY = "sk-abc123..."

# OK: 環境変数から取得
API_KEY = os.getenv("FUNBREW_PDF_API_KEY")

.envをバージョン管理に含めない

# .gitignore
.env
.env.local
.env.production

サーバーサイドのみでAPI呼び出し

PDF APIの呼び出しは必ずサーバーサイドで行います。フロントエンドのJavaScriptからAPIキーを送信してはいけません。DjangoもFastAPIもサーバーサイドフレームワークなので、この点は自然に守られます。

pytestによるテスト

テストではAPIの実呼び出しを避け、respx(httpxのモックライブラリ)を使います。

Djangoのテスト

# tests/test_pdf_service.py
import pytest
import respx
from httpx import Response

from pdf_app.services import generate_pdf, PDFGenerationError


@respx.mock
def test_generate_pdf_success():
    """PDF生成が成功した場合、PDFバイナリが返る"""
    fake_pdf = b"%PDF-1.4 fake content"
    respx.post("https://api.pdf.funbrew.cloud/v1/pdf/from-html").mock(
        return_value=Response(200, content=fake_pdf)
    )

    result = generate_pdf("<h1>Test</h1>")
    assert result == fake_pdf


@respx.mock
def test_generate_pdf_api_error():
    """APIがエラーを返した場合、PDFGenerationErrorが発生する"""
    respx.post("https://api.pdf.funbrew.cloud/v1/pdf/from-html").mock(
        return_value=Response(500, text="Internal Server Error")
    )

    with pytest.raises(PDFGenerationError):
        generate_pdf("<h1>Test</h1>")


@respx.mock
def test_generate_invoice_pdf_view(client):
    """請求書PDFエンドポイントがPDFを返す"""
    fake_pdf = b"%PDF-1.4 fake content"
    respx.post("https://api.pdf.funbrew.cloud/v1/pdf/from-html").mock(
        return_value=Response(200, content=fake_pdf)
    )

    response = client.post("/api/invoices/1/pdf/")
    assert response.status_code == 200
    assert response["Content-Type"] == "application/pdf"

FastAPIのテスト

# tests/test_pdf.py
import pytest
import respx
from httpx import AsyncClient, Response

from app.main import app


@pytest.mark.anyio
@respx.mock
async def test_generate_pdf_endpoint():
    """PDF生成エンドポイントが正常に動作する"""
    fake_pdf = b"%PDF-1.4 fake content"
    respx.post("https://api.pdf.funbrew.cloud/v1/pdf/from-html").mock(
        return_value=Response(200, content=fake_pdf)
    )

    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.post(
            "/api/v1/pdf/generate",
            json={"html": "<h1>Test</h1>", "filename": "test.pdf"},
        )

    assert response.status_code == 200
    assert response.headers["content-type"] == "application/pdf"
    assert response.content == fake_pdf


@pytest.mark.anyio
@respx.mock
async def test_generate_pdf_api_failure():
    """外部APIが失敗した場合、502を返す"""
    respx.post("https://api.pdf.funbrew.cloud/v1/pdf/from-html").mock(
        return_value=Response(500, text="Server Error")
    )

    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.post(
            "/api/v1/pdf/generate",
            json={"html": "<h1>Test</h1>"},
        )

    assert response.status_code == 500


@pytest.mark.anyio
@respx.mock
async def test_async_pdf_generation():
    """非同期PDF生成がタスクIDを返す"""
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.post(
            "/api/v1/pdf/generate/async",
            json={"html": "<h1>Test</h1>", "filename": "async-test.pdf"},
        )

    assert response.status_code == 200
    data = response.json()
    assert "task_id" in data
    assert data["status"] == "processing"

Django・FastAPI・Flaskの使い分け

観点 Django FastAPI Flask
向いているケース 管理画面付きの業務アプリ、既存Djangoプロジェクトへの統合 マイクロサービス、高スループットAPI、リアルタイム処理 軽量API、プロトタイプ、シンプルなWebアプリ
PDF生成パターン テンプレートエンジン + Celery非同期 async/await + BackgroundTasks Blueprint + 同期処理(Celeryも利用可)
パフォーマンス 同期処理がデフォルト(非同期はDjango 4.1+で対応) async/awaitがネイティブ、I/Oバウンド処理に強い 同期処理がデフォルト、シンプルで予測しやすい
エコシステム Django REST Framework、管理画面、ORM Pydantic自動バリデーション、OpenAPI自動生成 Flask-RESTful、Blueprint、豊富な拡張ライブラリ
非同期処理 Celery + Redis/RabbitMQ BackgroundTasks(軽量)、またはCelery(大規模) Celery + Redis/RabbitMQ
学習コスト フルスタックの学習が必要 APIに特化、軽量 最小限の概念で始められる、柔軟性が高い

選び方の目安:

  • 既存のDjangoアプリがある → Djangoのviewに統合するのが自然
  • 新規のAPIサービスを作る → FastAPIでasync/awaitを活かす
  • 軽量なサービスやプロトタイプ → FlaskのBlueprintで素早く構築
  • 大量のPDFを一括生成する → どのフレームワークでもCeleryを導入すべき
  • 管理画面が必要 → Djangoの管理画面が強力

請求書や証明書などのPDF生成ユースケースは請求書自動化ガイドユースケース一覧も参考にしてください。本番環境での運用ノウハウはプロダクション運用ガイドにまとめています。

まとめ

Django・FastAPI・Flaskのどれを使っても、FUNBREW PDF APIの統合はシンプルです。HTTPリクエストでHTMLを送り、PDFバイナリを受け取る――この基本パターンを押さえれば、あとはフレームワークの作法に沿って組み込むだけです。

  1. サービス層を分離する — viewやrouterからPDF生成ロジックを切り離す
  2. 非同期処理を活用する — 重いPDF生成はバックグラウンドに回す
  3. テストを書く — respxでAPIモックし、エッジケースもカバーする
  4. セキュリティを守る — APIキーは環境変数で管理し、サーバーサイドのみで使う

まずはPlaygroundで自分のHTMLがどんなPDFになるか試してみてください。準備ができたらドキュメントでAPIの全機能を確認できます。

Powered by FUNBREW PDF