惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Vercel News
Vercel News
C
Check Point Blog
G
Google Developers Blog
博客园 - 司徒正美
量子位
Engineering at Meta
Engineering at Meta
S
SegmentFault 最新的问题
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
A
About on SuperTechFans
美团技术团队
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Cloudflare Blog
U
Unit 42

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Day 85 of #100DaysOfCode — Building a Mini Flask App: Exp...
M Saad Ahmad · 2026-04-28 · via DEV Community

Flask learning is done. Today I built a complete mini Flask application from scratch: an expense tracker where users can register, log in, add expenses, filter them by category, and see a running total. Everything from days 78 to 84 came together in one day of building. Here's how it's architected and how it works.


Why an Expense Tracker

I wanted something small enough to finish in a day but real enough to actually use. An expense tracker hits that balance, it solves a genuine problem, it uses every Flask concept I've covered, and it's different enough from DevBoard and the other things I've built that it shows range rather than repetition.

Structurally, it's similar to a to-do app: a list of items that belong to a user. What pushes it beyond tutorial territory is the fields on each expense, the category filter, and the running total. Those three things make it feel like a real tool rather than a practice exercise.


What the App Does

The app has two sides: authentication and expense management.

On the authentication side, users can register with a username, email, and password. They can log in, stay logged in with a remember me option, and log out. Unauthenticated users are redirected to the login page.

On the expense side: logged-in users can add an expense with an amount, category, optional description, and date. They see all their expenses in a list, newest first, with the total at the top. They can filter the list by category, and the total updates will reflect only the filtered expenses. They can delete any expense. That's the entire feature set. Nothing more was needed.


Project Structure

The app lives in five Python files and a templates folder.
app.py is the entry point. It creates the Flask app, configures the database connection and secret key from environment variables, initializes SQLAlchemy, Flask-Migrate, and Flask-Login, registers the user loader function, and imports routes at the very bottom.

models.py defines the two database tables: User and Expense. User has the standard authentication fields. Expense has an amount, category, description, date, and a foreign key linking it to the user who created it. The User model inherits from UserMixin for Flask-Login compatibility and has methods for setting and checking the password hash.

forms.py defines the three forms: RegisterForm, LoginForm, and ExpenseForm. RegisterForm validates the uniqueness of username and email by querying the database during validation. LoginForm is straightforward. ExpenseForm has a SelectField for category with hardcoded choices, no separate Category model needed at this scale.

routes.py contains all seven route functions: three for auth, four for expenses. It imports from all other files and is itself imported by app.py.

templates/ contains base.html and subdirectories for auth and expense templates.


The Data Models

Two models, one relationship.

The User model stores credentials and links to expenses through a one-to-many relationship. One user has many expenses. Password is never stored in plain text, only the hash generated by werkzeug's generate_password_hash function.

The Expense model stores the actual expense data. The category field is a plain string; the choices are enforced at the form level, not the database level. This keeps the schema simple. The amount is a float. The date is a proper date field, not a string, so ordering and filtering by date works correctly.

The relationship between User and Expense is a standard foreign key, author_id on Expense pointing to the User's primary key. SQLAlchemy's db.relationship on the User side creates a Python-level accessor so you can do current_user.expenses to get all expenses for the logged-in user.


The Authentication Flow

Registration creates a new User, hashes the password, saves to the database, logs the user in immediately, and redirects to the expense list. No email verification, that would be scope creep for a one-day build.

Login queries the user by email, checks the password hash, and calls login_user() with the remember flag. After login, the user is redirected to wherever they were trying to go; the next query parameter from Flask-Login handles this automatically.

Logout calls logout_user() and redirects to login. Three lines.

Every expense route is decorated with @login_required. Unauthenticated requests are redirected to the login page with the original URL stored in ?next= so they return there after logging in.


The Expense List — The Most Interesting Part

The list route does more than just fetch expenses. Here's the full logic:

First, it reads the category filter from the query string; this is optional and defaults to empty. Then it builds a query for all expenses belonging to current_user. If a category filter is set, it adds a .filter_by(category=...) clause. The results are ordered by date descending, so the newest expenses appear first.

Then it calculates the total. Instead of fetching all expenses and summing in Python, it uses SQLAlchemy's db.func.sum() to do the calculation in the database; one query, one number returned. If no expenses match, the sum returns None, so it defaults to zero.

Both the expense list and the total respect the category filter simultaneously, if you filter by Food, you see only Food expenses, and the total reflects only Food expenses. This felt like the most important UX detail to get right.

The template receives the filtered expenses, the calculated total, the current filter value to keep the dropdown showing the right selection, and the list of available categories to populate the dropdown.


The Delete Flow

Delete is intentionally simple. No confirmation page, that would add another route and another template for something that doesn't need it at this scale. Instead, each expense row in the list has a small form with a hidden submit button. Clicking delete submits that form as a POST request.

The delete route fetches the expense by ID, checks that it belongs to current_user, an ownership check that prevents one user from deleting another's expenses by manipulating the URL, then deletes it and redirects back to the list. The category filter is preserved in the redirect, so you don't lose your current filter position after deleting.


The Import Chain and Circular Import Problem

This was the one architectural challenge worth mentioning. Flask apps with multiple files have a specific import order problem.

models.py needs to import db from app.py. routes.py needs to import db and models from app.py and models.py. app.py needs to import routes so Flask registers them. If app.py imports routes at the top and routes imports from app, Python hits a circular import error.

The solution is simple once you know it: import routes.py at the very bottom of app.py, after everything else is defined. By the time Python reaches that line, db, login_manager, and everything else in app.py is already defined, so when routes.py imports from app.py it finds what it needs. Bottom import, problem solved.


What the App Looks Like at the End

Six pages total:

Login and Register — clean centered forms with flash message support and cross-links between the two pages.

Expense List — a table of all expenses with amount, category, description, and date columns. A total at the top. A category dropdown filter above the table. A delete button on each row. An "Add Expense" button in the header.

Add Expense — a form with amount, category dropdown, optional description, and date. Submits and redirects back to the list.

Every page extends base.html, which has the navigation bar showing the current user's name and a logout link.


What I Learned from Building It

Flask rewards keeping things simple. The entire app: models, forms, routes, templates, is readable in under an hour. There's no magic, no framework doing things behind the scenes you don't understand. Every piece is where you put it.

The contrast with Django is real. Django would have had this done faster with ModelForms, class-based views, and the built-in auth system. But Flask forced me to understand every piece: password hashing, session management, query building, the circular import problem, in a way that Django abstracts away. Both are valuable. They teach different things.


What's Next

Flask learning is wrapped up. DevBoard is still being finished in parallel, on the candidate side, API polish, Tailwind, and deployment. A separate post will go up when it's live.

Thanks for reading. Feel free to share your thoughts!