Flask-Restlessは「SQLAlchemyを使用して定義したデータベースモデルのREST API化」をお手軽に実現してくれる拡張機能だ。前回と今回はFlask-SQLAlchemyを使用して、データベースモデルを作成しているので、これはまさにWeb API化にはうってつけの拡張機能といえる。
なお、REST APIを作成するための拡張機能として、他にもFlask-Rest-JSONAPIやFlask-RESTfulなどがある。GitHubのリポジトリに付いているスターの数で判断すると、Flask-RESTfulが一番著名だと思われるが、本稿では「お手軽さ」を重視して、Flask-Restlessを採用した。
なお、ここでは最後に作成するフロントエンドアプリからFlask-Restlessを利用したWeb APIへのアクセス時に、CORS(Cross-Origin Resource Sharing)によるアクセス拒否を避けるためにFlask-CORS拡張機能も利用している。
Web APIプロジェクト自体は「pip install flask flask-sqlalchemy flask-restless flask-cors」コマンドでこれらをインストールした仮想環境で作成していく。作成するといっても、コードは極めて単純だ。今回はapp.pyファイルに以下のコードを記述するだけだ。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restless import APIManager
from flask_cors import CORS
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db/todoitems.db"
db = SQLAlchemy(app)
manager = APIManager(app, flask_sqlalchemy_db=db)
CORS(app)
class ToDoItem(db.Model):
__tablename__ = "todoitems"
item_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
done = db.Column(db.Boolean, nullable=False, default=False)
manager.create_api(ToDoItem,
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
簡単にいえば、Flask-Restless拡張機能からAPIManagerクラスをインポートし、そこにFlask-SQLAlchemyからインポートしたデータベースオブジェクトを渡すことで、FlaskアプリとSQLAlchemyとFlask-Restlessを関連付けている。このAPIManagerクラスには、REST APIを作成するためのメソッドがあり、それをコードの最後で呼び出している(create_apiメソッド)。ToDoItemクラスはこれまでに見てきたままのクラスとなっている。
また、Flask-CORS拡張機能からインポートしたCORSクラスのコンストラクタにappオブジェクトを渡すことで、異なるドメインからのXHRリクエストを処理できるようにしている(ただし、上の「CORS(app)」呼び出しは「全ドメインからの全ルートのリクエストを許可」しているので、非常に危険だ。今回はあくまでもサンプルの動作を確認するための取りあえずの処置だと思ってほしい。詳細な設定を行うのであれば、Flask-CORSのドキュメントを参考にしてほしい。あるいは、Flask-RestlessはJSONP形式のデータ送信にもデフォルトでサポートしているので、そちらを利用する方法もあるだろう)。
実際のWeb APIの作成は「manager.create_api」メソッド呼び出しで行っている。ここでは、Web API化するモデルクラス(ここではToDoItemクラス)と、オプションとして「REST APIで利用可能なHTTPメソッド」を渡している。書いたコードは本当にこれだけだ。
いつもの手順でデータベースを作成しアプリを起動したら、やはりcurlコマンドで動作を確認してみよう。ここでは次のコマンドを実行したものとする。
curl -H "Content-Type: application/json" -X POST -d "{\"title\": \"buy milk\"}" http://localhost:5000/api/todoitems
curl -H "Content-Type: application/json" -X POST -d "{\"title\": \"play game\"}" http://localhost:5000/api/todoitems
curl -H "Content-Type: application/json" -X POST -d "{\"title\": \"write article\"}" http://localhost:5000/api/todoitems
curl -H "Content-type: application/json" -X GET http://localhost:5000/api/todoitems
実際にこれを実行した結果を以下に示す。
ターミナル右側のGETリクエストの出力結果を見ると分かるが、Flask-Restlessでは、全データを取得すると、objectsプロパティに項目が配列として保存される(この辺りの構造が最初に作成したWeb APIとは異なっている)。また、項目数(num_resultsプロパティ)、ページ数(total_pagesプロパティ)などのプロパティも存在する。デフォルトでは、出力は10件ごとにまとめられて返送されるので、多数のデータを取得するときにはそれなりの追加処理も必要になる点には注意されたい(例えば、ページ当たりの項目数を増やすにはcreate_apiメソッド呼び出しにキーワード引数「results_per_page=100」のように指定できる)。
動作の確認ができたところで、最後にこのWeb APIを利用するフロントエンドを作成する。
ここでは、Flask-Restlessプロジェクトとは別のフォルダにHTMLファイルとJavaScriptファイルのみで構成されるWebアプリプロジェクトを作成する。説明をシンプルにするために、ここではpackage.jsonファイルなどは作成しないことにした。使用するフレームワークはVue.jsで、これをHTMLファイルでCDNから読み込んで、JavaScriptコードからこれを利用する(npmで配布されているstatic-serverパッケージを利用して実行)。
VS Codeには、親子関係のない複数のフォルダをひとまとめにする「マルチルートワークスペース」という機能があるので、ここではこれを利用している。
index.htmlファイルの内容は以下の通り。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>ToDo List</title>
</head>
<body>
<div id="app">
<show-todo-items></show-todo-items>
<register-todo-item></register-todo-item>
</div>
<script src="index.js"></script>
</body>
</html>
見ての通り、CDNからVue.jsを読み込んで、index.jsファイルで定義されている2つのVue.jsコンポーネント(show-todo-itemsとregister-todo-item)を配置しているだけだ。
実際の処理を行うindex.jsファイルの内容は次のようになっている。
const request_url = "http://localhost:5000/api/todoitems";
const bus = new Vue();
Vue.component("show-todo-items", {
data: function () { return { items: null }; },
methods: {
showtodo: function () {
fetch(request_url)
.then(response => response.json())
.then(jsondata => this.items = jsondata.objects);
}
},
mounted: function () {
this.showtodo();
bus.$on("on-item-registered", this.showtodo);
},
template: '<ul><li v-for="item in items">{{ item.title }}</li></ul>'
});
Vue.component("register-todo-item", {
data: function () { return { todotitle: "" }; },
template:
`<div>
<input v-model="todotitle">
<button v-on:click="register_todoitem(todotitle)">Register todo item</button>
</div>`,
methods: {
register_todoitem: function(todotitle) {
console.log(todotitle);
fetch(request_url, {
method: "POST",
mode: "cors",
body: JSON.stringify({ title: todotitle }),
headers: {
"content-type": "application/json"
}
})
.then(response => response.json())
.then(jsondata => {
console.log(jsondata);
bus.$emit("on-item-registered", jsondata);
})
.catch(err => console.log(`err: ${err}`));
}
}
});
var app = new Vue({
el: '#app'
});
Vue.jsプログラミングの詳細な解説は省略するが、既に述べた通り、ここではshow-todo-itemsとregister-todo-itemという2つのVue.jsコンポーネントを作成している。注目したいのは、各コンポーネントのmethodsプロパティで作成している2つのメソッドだ。ここではshow-todo-itemsコンポーネントで定義しているshowtodoメソッドについて見ておこう。
showtodo: function () {
fetch(request_url)
.then(response => response.json())
.then(jsondata => this.items = jsondata.objects);
showtodoメソッドでは、JavaScriptのFetch APIを使用して、request_urlにGETリクエストを送信している(request_urlはここでは「flask run」で起動したWeb APIのURLで「http://localhost:5000/api/todoitems」となっている)。Web APIが返した結果をthenで受け取り、それをjsonメソッドで取り出して、一覧表示をしている。
また、createdプロパティでは、このメソッドを呼び出した後に、Vue.jsが提供するイベント機構を利用して、項目追加時に一覧表示を更新するようにもしている。
register-todo-itemコンポーネントのregister_todoitemメソッドも同様だ。こちらについてはリクエストボディーを組み立てて、POSTリクエストを送信するために若干処理が煩雑になっている。また、項目の作成後には、イベント機構を利用して項目の一覧表示を更新するようにもしている。
実行している画面を以下に示す。
今回は、データベースモデルをWeb API化する方法を2つ見た。まず、前回のコードをベースとして、全ての処理を自分で書き起こした後に、Flask-Restlessを使ったお手軽なWeb API化を試してみた。Flask-RestlessとFlask-CORSの詳細については、それぞれのドキュメントを参照されたい。次回は、Flaskアプリの構成やデプロイといった話をする予定だ。
Copyright© Digital Advantage Corp. All Rights Reserved.