Chapter 10: Dynamic Website Hosting (PHP, Python, Node.js with MySQL)
10.4 Python (Flask) with MySQL
It's really simple – the pattern is the same as PHP, only the app server changes. Here Gunicorn takes the place of PHP-FPM. Type these commands with me.
एकदम सिंपल आहे – पॅटर्न PHP सारखाच, फक्त ॲप सर्व्हर बदलतो. इथे PHP-FPM च्या जागी Gunicorn येतो. या कमांड्स माझ्यासोबत टाइप करा.
एकदम सिंपल है – पैटर्न PHP जैसा ही है, सिर्फ़ ऐप सर्वर बदलता है. यहाँ PHP-FPM की जगह Gunicorn आता है. ये कमांड्स मेरे साथ टाइप करो.
Install Python tools
# Amazon Linux 2023 / CentOS Stream 9
sudo yum install -y python3 python3-pip git
# Ubuntu
sudo apt install -y python3 python3-venv python3-pip git
Create the app in /opt/flaskapp
We keep apps in /opt/<app> owned by the login user. (On CentOS, SELinux may block systemd from running programs stored inside home directories, so /opt avoids that problem on every OS.)
sudo mkdir -p /opt/flaskapp && sudo chown $USER:$USER /opt/flaskapp
cd /opt/flaskapp
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install flask pymysql cryptography gunicorn
pip freeze > requirements.txt
cat > /opt/flaskapp/.env <<'EOF'
DB_HOST=127.0.0.1
DB_USER=appuser
DB_PASS=App@Pass123
DB_NAME=appdb
EOF
chmod 600 /opt/flaskapp/.env
cat > /opt/flaskapp/app.py <<'EOF'
import os
import pymysql
from flask import Flask, jsonify, redirect, render_template_string, request
app = Flask(__name__)
def get_db():
return pymysql.connect(
host=os.getenv("DB_HOST", "127.0.0.1"),
user=os.getenv("DB_USER", "appuser"),
password=os.getenv("DB_PASS", ""),
database=os.getenv("DB_NAME", "appdb"),
cursorclass=pymysql.cursors.DictCursor,
)
PAGE = """
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Flask + MySQL</title></head>
<body style="font-family:Arial;max-width:700px;margin:30px auto">
<h1>Students (Flask + MySQL)</h1>
<table border="1" cellpadding="6"><tr><th>ID</th><th>Name</th><th>City</th></tr>
{% for s in rows %}<tr><td>{{ s.id }}</td><td>{{ s.name }}</td><td>{{ s.city }}</td></tr>{% endfor %}
</table>
<h3>Add student</h3>
<form method="post" action="/add">
<input name="name" placeholder="Name" required>
<input name="city" placeholder="City" required>
<button>Add</button>
</form>
</body></html>
"""
@app.route("/")
def index():
conn = get_db()
with conn, conn.cursor() as cur:
cur.execute("SELECT id, name, city FROM students ORDER BY id")
rows = cur.fetchall()
return render_template_string(PAGE, rows=rows)
@app.route("/add", methods=["POST"])
def add():
conn = get_db()
with conn, conn.cursor() as cur:
cur.execute("INSERT INTO students (name, city) VALUES (%s, %s)",
(request.form["name"], request.form["city"]))
conn.commit()
return redirect("/")
@app.route("/api/students")
def api_students():
conn = get_db()
with conn, conn.cursor() as cur:
cur.execute("SELECT id, name, city FROM students")
return jsonify(cur.fetchall())
@app.route("/health")
def health():
return {"status": "ok"}
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=False)
EOF
Test it manually
cd /opt/flaskapp && source venv/bin/activate
set -a && source .env && set +a # load variables from .env into this shell
gunicorn --bind 127.0.0.1:5000 app:app & # start in background for a quick test
sleep 2
curl -s http://127.0.0.1:5000/api/students
kill %1 # stop the test server
deactivate
Run with Gunicorn as a systemd service
sudo tee /etc/systemd/system/flaskapp.service > /dev/null <<EOF
[Unit]
Description=Flask app served by Gunicorn
After=network.target mariadb.service mysql.service
[Service]
User=$USER
Group=$USER
WorkingDirectory=/opt/flaskapp
EnvironmentFile=/opt/flaskapp/.env
ExecStart=/opt/flaskapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:5000 app:app
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo service flaskapp start
sudo systemctl enable flaskapp
sudo service flaskapp status
curl -s http://127.0.0.1:5000/health
(Here the here-doc delimiter EOF is not quoted, so $USER is replaced by your user name — ec2-user or ubuntu.)
Ravindra Bagale's Tip
Many students create the .env file but forget chmod 600, or push it to GitHub. A file with passwords should be readable only by its owner, and be sure to add .env to .gitignore.
Ravindra Bagale's Tip – मराठी
खूप students .env file बनवतात पण chmod 600 विसरतात, किंवा ती GitHub वर push करतात. Password असलेली file फक्त owner ला वाचता यावी, आणि .gitignore मध्ये .env नक्की टाका.
Ravindra Bagale's Tip – हिंदी
बहुत students .env file बनाते हैं पर chmod 600 भूल जाते हैं, या उसे GitHub पर push कर देते हैं. Password वाली file सिर्फ़ owner पढ़ सके, और .gitignore में .env ज़रूर डालो.
Lab
Chala, Flask app systemd service mhanun chalu kara aani curl -s http://127.0.0.1:5000/health ne ok yetoy ka te bagha. Mag sudo reboot kara, parat login karun tech curl chalva – app aapoaap start zala ka te confirm kara.