Panzita web app
Overview:
Pupose: Panzita is an eCommerce website for ephemeral products.
The website admins, or artists, will be able to upload products that expire after a certain amount of time. Visitors to the website will be able to purchase the product(s) until the products expire.
Scope: MVP. The goal right now is to get something up and running. This means we will ignore certain features, like allowing filtering and ordering of the product, or even allowing more than one
product, in favor of focusing on the ephemeral aspect.
Tech stack:
- Backend: Python (Flask)
- Frontend: SSR, using Jinja Templates and vanilla JavaScript, HTML, and CSS
- Database: PostgreSQL DB
- Hosting: Self hosted VPS using Nginx and gUnicorn.
- Ordering and fullfilment: Printful API
- Payment processing: Stripe
Rationale
The chosen stack is minimal and proven. It will allow quick iteration and production scale. Flask, while not the most performant web framework, is good enough. The goal of the shop isn't to have a vast catalog of products, but a single product at a time, or a few at most, that expire when our time runs out.
Flask is also a pragmatic choice. While other Python web frameworks might be older and more established, like Django, or more performant, like FastAPI, Flask falls right in the middle. It isn't a large opinionated framework that might be more appropriate for a larger app. Instead it's light and simple, allowing us to get running quickly without having all the extra overhead that Django brings with it. Although it isn't as fast as FastAPI, performance optimizations aren't a big concern right now (and might not ever be), and Flask more than makes up for it with it's vast ecosystem. With Flask, we get Flask-Login, WTForms and FlaskWTF, the Jinja templating engine, and other extensions that will make our task easier. Python itself will allow us to use tools like APScheduler to handle the expiration logic. All in all, our Python backend gives us plenty of options for quickly building out our website.
The choice of going with Jinja templates and using vanilla JavaScript, HTML, and CSS is also made out of the desire to keep the front end simple. Because we will only have a very limited number of products, things typically associated with eCommerce, like complex frontends, won't apply here. In fact, our MVP will be built with the assumption that only 1 product will be displayed at a time.
Our choice of database follows the theme we've set - PostgreSQL is a classic choice for a database. Performant, with a proven track record and vast community. It will get the job done for our simple app, and will also scale wonderfully.
This web app will also be deployed on a self hosted VPS. Again, our scope is small and so is our scale. If, by some miracle, there is some sort viral hit that makes our website blow up, crashing the server is more desirable than suprise cloud charges. Furthermore, Ngnix is a powereful server that can also serve as a load balancer.
Finally, all ordering and fullfilment will be handled by the Printful API. Mainly because Printful offers products we're interested in -- Embroidered shirts. Payment processing will be handled by Stripe. It is a popular payment processor with a reputation for being developer friendly.
graph TD subgraph Panzita Web App A[Flask app] B[(PostgreSQL DB)] C[APScheduler] end
A --> B
A --> Stripe[Stripe API]
Stripe --> Printful[Printful API]
C -.-> B
Implementation
Flask:
Implementing a Flask backend is relatively simple and easy. A basic Flask app typically uses an
Application Factory pattern, and looks likes so:
import os
from flask import Flask
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# a simple page that says hello
@app.route('/hello')
def hello():
return 'Hello, World!'
return app
That example is taken straight from the official Flask tutorial.