The following details my experience writing and deploying my first web app. I’m just going to free write this one, with no planned direction or otherwise.
The Project
So me and a few guys from town have been talking about starting a band. We are all exceptional players, share similar musical interests, and all have experience in playing shows and the local scene. So this is an exciting thing - and I really hope it works out. Now some of the guys have already written either entire songs or riffs. So I was asked to learn a few things. The audio was sent over facebook - yet facebook will not let me download it in its raw form. That means I can’t slow it down to get a better listen on the faster riffs, I can’t loop a drumtrack in a DAW to just work overtop of it. I find myself with a 30 second drum beat I have to manually repeat each time it finishes playing, which means I can’t even start right on the first beat as it takes time to press the button, get my hand back in place to start playing.
I love a good excuse for a project - Especially if it can solve a problem and/or help me gain more experience in something - So naturally I decided to brainstorm some excuses to validate doing something to solve this file sharing situation amongst me and the guys. I know there are a billion cloud sharing solutions out there, I’ve even written in detail about one of them (rsync + pixeldrain api), but I’ve been meaning to take python and apply it to a web app project. Plus, it would be pretty cool to have a platform unique to us, which can be altered and tweaked in any way we decide would be beneficial. And it’s always nice to be able to call something your own.
While I don’t always fallow my own advice, it is good practice to have a plan. Some set of goals, and an outline of how you can reach them, especially with respect to any sort of software development. There is nothing worse than getting to a point ain developing something just to realize it is not the best way to be handling it. With a little forethought, a lot of headaches can be saved. When I find myself in those scenarios, I try to stay positive and tell myself ‘there is progress in the struggle’. Navigating those scenarios can be a test of patience and resilience, and almost every time you are faced with hardship you will get through it gaining some kind of insight - even if its that familiar voice in your head reminding you you jumped the gun again!
The goal here is quite straight forward and clear. I wanted to develop a platform from scratch either using go or python. I went with python and flask. The objectives include:
- Easily upload and download files, accessible via my webserver
- Authentication such anything we dont want leaked is secure
- Ways to sort the files.
Now if were to have left it at that, I am still convinced I would be severly overlooking the amount of work this would be. It is so incredibly easy to overlook just how much thought needs to be put into the simplest of things. Now if you are like me, you are likely thinking the entire time how one might be able to exploit a web app like this as well - and we will be getting into that in a bit.
Ultimately, the reason I’ve titled this post as a swift reality check is because, while no specific aspect of this was insanely complicated, it felt at times while debugging issues and facing problems like there were a ton of really easy things making it borderline exhausting.
READ MORE
Lesson #1 - Directory Structure is Mandatory ✅
I mentioned planning a project can make a world of difference. The issue is, if you arent familiar with the platform, or general concepts, it makes it really difficult to plan. Now Ive never written a Flask app before, but ive written plenty of python and shell. And specifically with respect to python, its very important to keep your code modular as well as structured logically.
In a CLI app, I’d just throw functions into modules, structure things neatly, and call it a day. With Flask, I quickly realized that directory structure matters. The framework expects static files, templates, and backend logic to be arranged in a way that plays nicely with its routing system. When deploying with a production server, all this becomes apparent very quickly (more on this later).
✅ Lesson Learned: A Basic Flask Application Structure
1 |
|
To summarize - app.py is the brain of the operation, templates are the structure, the skeleton or core of the application, and the static folder is responsible for technique and style. It may not be a perfect analogy, but
Lesson #2 - The TailwindCSS Priority Nightmare
Using Tailwind sounded like a dream—utility-first, no writing custom CSS, clean and structured. What I didn’t expect was how aggressively it overrides styles.
Why Won’t My Background Change?
At one point, I was trying to change the background color of my file cards - The blocks which files are displayed on in mobile view - or otherwise put, the mobile alternative to tables, but no matter what I did in my style.css file, it wouldn’t apply. Turns out, Tailwind’s specificity and inline classes had higher priority.
✅ Fixes I Had to Learn the Hard Way:
Use !important in CSS
css
Copy
Edit
.file-card {
background-color: #2d2d2d !important;
}
Override Tailwind with Tailwind
html
Copy
Edit
Tailwind is powerful but stubborn. Sometimes, it’s easier to use Tailwind’s classes to override Tailwind instead of fighting it with custom CSS.
Everything is an HTML class—Tailwind discourages writing custom styles, so if I needed a change, I had to do it in the template instead of CSS.
🛡️ Challenge 3: Security is More Than Just if file:
File uploads are a huge attack vector, and coming from pure Python scripting, I wasn’t used to thinking about:
File extension spoofing (file.txt.exe)
Overwriting files on the server
Arbitrary code execution (Remote Code Execution - RCE)
Handling weird edge cases (files with spaces, no extensions, special characters)
✅ Fixes & Security Measures I Implemented:
Sanitizing Filenames (Preventing malicious uploads)
python
Copy
Edit
from werkzeug.utils import secure_filename
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
Allowing Only Specific File Types
python
Copy
Edit
ALLOWED_EXTENSIONS = {‘jpg’, ‘png’, ‘mp4’, ‘mp3’, ‘pdf’}
def allowed_file(filename):
return ‘.’ in filename and filename.rsplit(‘.’, 1)[1].lower() in ALLOWED_EXTENSIONS
Handling Files Without Extensions or Spaces
python
Copy
Edit
if not file.filename or ‘.’ not in file.filename:
flash(“Invalid file. Files must have an extension.”, “error”)
filename = filename.replace(“ “, “_”) # Convert spaces to underscores
✅ Lesson Learned:
Don’t trust user input. Even filenames can be malicious.
Spaces, missing extensions, and weird filenames will break things if not handled properly.
Flask’s secure_filename() is a must-have.
⚙️ Challenge 4: The Overlooked Details of User Experience
Writing backend logic was easy. But making the UI intuitive? That was another story.
⚡ Fixes for a Better UX:
✔ Sorting Files in the Table
js
Copy
Edit
function sortTable(columnIndex) {
const table = document.getElementById(“filesTable”);
const rows = Array.from(table.getElementsByTagName(“tbody”)[0].getElementsByTagName(“tr”));
let ascending = table.getAttribute(“data-sort-asc”) === “true”;
rows.sort((rowA, rowB) => {
const cellA = rowA.getElementsByTagName("td")[columnIndex].textContent.trim().toLowerCase();
const cellB = rowB.getElementsByTagName("td")[columnIndex].textContent.trim().toLowerCase();
return ascending ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);
});
table.setAttribute("data-sort-asc", ascending ? "false" : "true");
rows.forEach(row => table.getElementsByTagName("tbody")[0].appendChild(row));
}
✔ Dropdown Action Menu for Mobile (Expands the Card!)
js
Copy
Edit
function toggleDropdown(button) {
const card = button.closest(“.file-card”);
const dropdown = button.nextElementSibling;
document.querySelectorAll(“.file-card .action-menu”).forEach(menu => {
if (menu !== dropdown) {
menu.classList.add(“hidden”);
menu.closest(“.file-card”).classList.remove(“expanded-card”);
}
});
dropdown.classList.toggle(“hidden”);
card.classList.toggle(“expanded-card”);
}
✅ Lesson Learned:
Users expect sorting, filters, and error handling to “just work.”
Making the UI smooth takes just as much effort as backend logic.
🚀 Final Thoughts: What I’d Do Differently Next Time
Building a Flask app isn’t just writing Python—it’s a mix of frontend, backend, UX, and security considerations.
🎯 Things I’d Do Differently:
Plan frontend layouts first (so I don’t have to refactor UI components later).
Use Flask Blueprints to better organize routes instead of cramming everything into app.py.
Spend more time on security from the start (instead of patching exploits as I find them).
Would I do it again?
✅ Yes. It was frustrating, eye-opening, and honestly rewarding. The deeper I got into Flask, the more I appreciated how much work goes into even the simplest web applications.
👨💻 If you’re starting your first Flask app, just remember: It’s going to take longer than you think—but the learning curve is worth it. 🚀
You said:
Add file management as I started just reading files from a directory, then moved to SQLite, and now in process of moving to postgreaql for scalability
Another one is displaying frontend for both mobile and desktop devices
ChatGPT said:
From Basic File Handling to Scalable Storage: The Evolution of My Flask App
🛠️ Flask, File Management, and the Pain of Scaling Up
When I started working on this Flask app, I had one goal in mind—a simple file-sharing application where users could upload and manage files.
What I didn’t realize was how much infrastructure goes into managing files efficiently—from a basic directory-based system to a SQLite database, and now transitioning to PostgreSQL for scalability.
On top of that, frontend rendering for mobile and desktop users added another layer of complexity. The way an application is presented massively affects usability.
This is a deep dive into what I learned about file management, databases, and responsive frontend rendering while developing my first Flask application.
📂 File Management: From Simple to Scalable
⚡ Version 1: Direct File System Reads (Basic, But Limited)
Initially, my approach was brute force:
✅ Files were stored in a directory (uploads/)
✅ Flask just read files directly
✅ No metadata, no tracking, just a raw list of filenames
python
Copy
Edit
import os
UPLOAD_FOLDER = “uploads”
@app.route(“/files”)
def list_files():
files = os.listdir(UPLOAD_FOLDER) # Just read the directory
return render_template(“files.html”, files=files)
🚨 Problems with this approach:
No user tracking: Who uploaded what? No way to tell.
No metadata: No timestamps, file sizes, or extensions stored in a structured way.
Inefficient: Opening os.listdir() for every request doesn’t scale.
⚡ Version 2: Moving to SQLite (Better, But Still Limited)
To add tracking & metadata, I introduced SQLite and a basic database model:
python
Copy
Edit
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class UploadedFile(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False)
user = db.Column(db.String(100), nullable=False)
upload_time = db.Column(db.DateTime, default=datetime.utcnow)
file_size = db.Column(db.String(50))
file_path = db.Column(db.String(255))
Now, instead of just dumping files into a folder, uploads were tracked in a database:
python
Copy
Edit
@app.route(“/upload”, methods=[“POST”])
def upload():
file = request.files[“file”]
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
new_file = UploadedFile(
filename=filename,
user=session.get("username"),
file_size=format_size(os.path.getsize(filepath)),
file_path=filepath
)
db.session.add(new_file)
db.session.commit()
return redirect(url_for("files"))
🚀 Benefits:
✔ Files were now tied to users
✔ Faster lookups using SQL
✔ Metadata was stored properly
🚨 Problems:
SQLite is not built for heavy concurrent writes.
Scaling becomes an issue with multiple users uploading simultaneously.
⚡ Version 3: Transitioning to PostgreSQL (Scalable & Production-Ready)
Now, I’m moving fully to PostgreSQL. SQLite was fine for small-scale testing, but for a real application with multiple users, PostgreSQL offers better performance and concurrency handling.
✅ Why PostgreSQL?
Handles multiple users better
Supports JSON fields for flexible metadata
Works across multiple servers
To switch, I had to update my Flask app configuration:
python
Copy
Edit
app.config[‘SQLALCHEMY_DATABASE_URI’] = “postgresql://username:password@localhost/mydatabase”
db.init_app(app)
🚀 Improvements:
✔ Scalable for production
✔ Better for large datasets
✔ More advanced features like full-text search
📌 Still in progress: Migrating existing SQLite data to PostgreSQL without breaking things.
📱 Frontend: Making It Work on Mobile & Desktop
⚡ Challenge: Tables vs. Mobile Cards
Initially, I just threw a big table on the screen. It worked great on desktop, but was terrible on mobile:
❌ Tables don’t scale well on small screens
❌ Too much horizontal scrolling
❌ Not intuitive for mobile users
⚡ Solution: Separate Desktop & Mobile Layouts
I realized I needed two separate designs:
✔ Desktop: A standard table layout
✔ Mobile: Individual file cards
html
Copy
Edit