I recently needed to develop a small application for personal use. Since I had a specific need to use Python, I decided to build the backend with Flask. For the frontend, I only needed (literally) a single page to visualise some data, so I resisted the urge to go with something like Angular or React, and instead decided to try this minimal Alpine.js thing I had heard about a few days earlier.
In this article, I’m going to show you how you can really quickly create a minimal web application (REST API, static files and web UI) like I did. This can be handy for putting a simple tool together in very little time, but please consider it as a quick and dirty thing rather than any kind of best practice. I’m new to both Flask and Alpine.js, so I’m certainly not going to be giving advice on how best to use them.
Getting Started with Flask
After creating a new folder for your project, create a file called main.py (or whatever you want, really). We can kick off our API development by using the Flask Quickstart example:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
The same page also explains how to run the application so that it can listen for connections and serve requests:
“To run the application, use the
— Flask Quickstartflask
command orpython -m flask
. You need to tell the Flask where your application is with the--app
option.”
This is nice and all, but I like to be able to debug my code from Visual Studio Code, and for that it’s necessary to create a launch.json file with the right settings. This can be done as illustrated by the following images and their captions. If you need to do anything more advanced, see “Working with VS Code Launch Configurations“.
Once that is done, it generates a launch.json file with the following contents:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "main.py",
"FLASK_DEBUG": "1"
},
"args": [
"run",
"--no-debugger",
"--no-reload"
],
"jinja": true,
"justMyCode": true
}
]
}
You can now press F5 to start the application in debug mode. The integrated terminal informs you that the application is running at http://localhost:5000/, so you can open a browser window to see that it’s working. By running it in this way, you can also add breakpoints and debug your Python code as needed.
A Simple REST API
To build a reasonable example, we need to return some data from our API. We can do this quite easily by returning a list of dictionary objects from a function annotated with the route “/products”:
@app.route("/products")
def get_products():
return [
{
"name": "Rope",
"description": "Used to climb heights or swing across chasms.",
"price": 15
},
{
"name": "Whip",
"description": "An old and trusty friend.",
"price": 20
},
{
"name": "Notebook",
"description": "Contains lots of valuable information!",
"price": 80
}
]
After restarting the application and visiting http://localhost:5000/products, we can see that the list gets translated quite nicely to JSON:
Serving a Static Webpage
It is now time to build a simple user interface. Before we do that though, we need to figure out how to serve static files so that we can return an HTML file and then use Alpine.js to dynamically populate it using the API.
It turns out that this is quite simple: Flask automatically serves any files you put under the static folder. So let’s create a static folder and add an index.html file with some minimal HTML in it:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My Products</title>
<body>
<h1>My Products</h1>
</body>
</html>
We can now see this page if we visit http://localhost:5000/static/index.html in a browser:
It works, but it’s not great. I’d like this to come up by default when I visit http://localhost:5000/, instead of having such a long URL. Fortunately, this is easy to achieve. All we need to do is replace our “Hello world” code in the default endpoint with the following:
from flask import Flask, send_from_directory
app = Flask(__name__)
@app.route('/')
def serve_index():
return send_from_directory('static', 'index.html')
Displaying the Products
Now that we have an API and an easily accessible webpage, we can display the data after retrieving it from the API. We can now add Alpine.js to our website by adding a <script>
tag, and then build out a table that retrieves data from the API:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My Products</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
body {
font-family: sans-serif;
}
table, th, td {
border: solid 1px #555;
border-collapse: collapse;
}
</style>
<body>
<h1>My Products</h1>
<div
x-data="{ products: [] }"
x-init="products = await (await fetch('/products')).json()"
>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Price</th>
</tr>
<template x-for="product in products">
<tr>
<td x-text="product.name"></td>
<td x-text="product.description"></td>
<td x-text="product.price"></td>
</tr>
</template>
</table>
</div>
</body>
</html>
In the above HTML, the main thing to look at is the stuff inside the <div>
. Outside of that, the new things are just the <script>
tag that imports Alpine.js, and a little CSS to make the webpage look almost bearable.
The <div>
tag’s attributes set up the data model that the table is based on. Using this x-data attribute, we’re saying that we have an array called “products”. The x-init attribute is where we use the Fetch API to retrieve data from the /products endpoint and assign the result to the “products” array.
Once this data is retrieved, all that’s left to do is display it. In the <table>
, we use an x-for attribute in a <template>
to loop over the “products” array. Then, we bind the content of each cell (<td>
element) to the properties of each product using the x-text attribute, effectively displaying them.
The result is simple but effective:
Conclusion
As you can see, it’s pretty quick and easy to put something simple together and then build whatever you need on top of that. For instance, you might want to filter the data you display, apply conditional highlighting, or add other functionality.
Honestly, I’m not quite sure how far you can take Alpine.js. For instance, it doesn’t seem possible to create reusable components, which somewhat limits its usefulness. And I don’t yet know how to avoid having heaps of JavaScript within HTML attributes. I’ve only started using this a few days ago, after all.
But it’s great to be able to use something like this to create simple tools without having to incur the learning curves of things like Angular, React or Vue.
One thought on “Minimal Web Application with Flask and AlpineJS”