Next.js / Python Flask / SQLite3
前回、PyCharmを使ったスタンドアローンGUIのPythonプログラミンングを試しましたが、今回はこの環境でWebプログラミングをしてみます。
UIをPythonでやることもできますが、やはりJavaScriptの利便性には敵いませんので、今一番よく使われているReactのWebフレームであるNext.jsを使用します。
環境) Next.js v14.0.2 / node v18.12.1, npm(npx) v8.19.2 / Python v3.9.6 / PyCharm 2023.2.4 CE, sqlite3 v3.39.5 / Mac(arm64)
Reactについて過去記事
データベースはSQLite3を使用します。これでWebアプリの最小限の機能を実現できます。
参考) https://zenn.dev/ovrsa/articles/6e66c50f3b3429
今回一番やりたかったことは、FrontEndをnode.jsで動かすのではなく、PythonのサーバにNext.jsのコードを静的ファイルとしてデプロイして動作させることです。そして一歩進んで、実行ファイル化して実行するテストもしました。(コンソール起動、ダブルクリック起動) この時、SQLite3のDBファイルのパスについていろいろと考慮する必要がありました。
app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import os import sys from flask import Flask, jsonify, request, render_template, send_from_directory from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() dpath = os.path.dirname(sys.argv[0]) #app = Flask(__name__, static_folder=dpath + '/static') app = Flask(__name__) CORS(app) #app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + dpath + '/demo.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app) class Item(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), nullable=False) def __init__(self, name): self.name = name def __repr__(self): return f'<Item {self.name}>' with app.app_context(): db.create_all() #@app.route('/') #def index(): # return render_template('index.html') ''' @app.route('/templates/<path:path>') def send_file(path): return send_from_directory('templates', path) ''' @app.route('/hello') def hello(): return "<h3>Hello from Flask!</h3>" @app.route('/items', methods=['GET']) def get_items(): items = Item.query.all() return jsonify([str(item) for item in items]) @app.route('/items', methods=['POST']) def add_item(): name = request.json['name'] item = Item(name=name) db.session.add(item) db.session.commit() return jsonify(str(item)), 201 if __name__ == '__main__': app.run(debug=True) |
staticフォルダに下記ビルド結果のoutフォルダの中身をコピーして持ってきます。
実行ファイル化したときの、staticファイルをコマンドで指定するか、コードで明記するかや、dbファイルの場所も、カレントかユーザルートか、を変更するため、コメント部分はあえて残しました。
next.js環境構築
npx create-next-app frontend
cd frontend
npm install
–
pages/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import { useState, useEffect } from 'react'; export default function Home() { const [items, setItems] = useState([]); const [newItem, setNewItem] = useState(''); useEffect(() => { fetchItems(); }, []); const fetchItems = async () => { const res = await fetch('http://localhost:5000/items'); const data = await res.json(); setItems(data); }; const addItem = async () => { if (!newItem) return; const res = await fetch('http://localhost:5000/items', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: newItem }), }); const data = await res.json(); setItems([...items, data]); setNewItem(''); }; return ( <div> <h2>Next.js / Python Flask / SQLite3</h2> <ul> {items.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> <input type="text" value={newItem} onChange={(e) => setNewItem(e.target.value)} /> <button onClick={addItem}>Add Item</button> </div> ); } |
ステートフックと副作用フックのおかげでとてもシンプルにコーディングできることがわかります。
データのあるべき姿を記述すれば、変化があったときに関連する部分が自動的に更新されます。(リアクティブの特徴)
“npm run dev”の実行時、コンフリクトのエラーが出るため、app/page.js を無効にするか、リネームします。
ビルド設定
next.config.js
1 2 3 4 5 6 7 |
/** @type {import('next').NextConfig} */ const nextConfig = { basePath: "/static", output: 'export' } module.exports = nextConfig |
ビルド実行
npx next build
–
outフォルダに生成されるファイルをapp.pyと同階層にあるstaticフォルダにコピーします。
(参考までにindex.htmlを確認)
% tidy -raw -i out/index.html
…(略)…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!DOCTYPE html> <html> <head> <meta name="generator" content= "HTML Tidy for Mac OS X (vers 31 October 2006 - Apple Inc. build 10443), see www.w3.org"> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <meta name="next-head-count" content="2"> <title></title> </head> <body> <noscript data-n-css=""></noscript><script defer crossorigin="" nomodule="" src= "/static/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" type= "text/javascript"> </script><script src= "/static/_next/static/chunks/webpack-68f8edf7417ca8de.js" defer crossorigin="" type="text/javascript"> </script> ...(略)... <div id="__next"> <div> <h2>Next.js / Python Flask / SQLite3</h2><input type="text" value=""><button>Add Item</button> </div> </div><script id="__NEXT_DATA__" type="application/json" crossorigin=""> {"props":{"pageProps":{}},"page":"/","query":{},"buildId":"txAydx5jrtdK0bl1a8nCx","assetPrefix":"/static","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]} </script> </body> </html> |
…(略)…
Static Site Generation(SSG)、またブラウザにファイルをロード後JavaScriptでのレンダリングを意図しているので、Client Side Rendering(CSR) ということを確認できます。レンダリングするので広い意味で動的か、と誤解しそうになりますが、HTMLファイルを静的に生成した、という部分がメインということでしょう。(JSX、トランスパイル、nodejsのおかげで、Web界隈はかなり複雑になってきています。)
nodejsで実行(一応確認のため)
npm run dev
–
デフォルトでは、staticは不要ですが、ここではexport時にPythonが静的ファイルとしてアクセスするためのPathと合わせるためにこのようになっています。
Flaskサーバ実行
python app.py
–
app.pyと同階層のinstanceフォルダに、demo.dbが作成されます。
誤解のないように補足いたします。
上記二つとも同じ内容を表示していますが、複製していますので、ソースは違います。(データベースは同じもの)またFlaskサーバのみで実行できます。Nodejsサーバで内容を確認するときはFlaskサーバの起動が必要です。
AddItemボタンでデータを追加した結果のDB
% sqlite3 demo.db
sqlite> .mode column
sqlite> .table
item
sqlite> .schema
CREATE TABLE item (
id INTEGER NOT NULL,
name VARCHAR(80) NOT NULL,
PRIMARY KEY (id)
);
sqlite> select * from item;
id name
— —-
1 aaaa
2 bbbb
3 cccc
4 1111
sqlite>
Pythonファイルの実行形式化
pyinstaller -F –add-data “static:static” –onefile –clean app.py
–
distフォルダに 実行ファイルapp が出力されます。–noconsole オプションをつけると起動時コンソールが表示されなくなり、出力ファイルのMacのパッケージになリ、このときDBファイルはパッケージの中に書かれます。Finderからダブルクリック起動の場合は、カレントでなくユーザルートに書かれます。
(最後にWeb画面に表示されたデータが思ったものと違ったので確認しました。PythonのQueryで取得したまま表示されています。
今回はここまでとします。)
Category: 未分類