Lesson Complete!
Update Your Tests
What you built across this course
You replaced a JSON file with a real SQLite database — without changing a single endpoint's external behavior. The API accepts the same requests and returns the same responses. The storage layer is completely different.
models.py—Expense(SQLModel, table=True)maps directly to a SQLite table. One class handles validation, storage, and serialization.ExpenseOutis gone.database.py— the engine,create_db_and_tables(), andget_session(). Each request gets its own database session, opened before the endpoint runs and closed after.main.py— five endpoints rewritten with session calls, three new query capabilities (date range, aggregation, text search), and alifespanfunction that creates the database on startup.test_main.py— 18 tests running against an in-memory SQLite database. Each test starts clean. No fake data in your development database.
What changes when you grow
SQLite works well for personal projects, prototypes, and single-user APIs. When you need multiple users writing simultaneously, the upgrade path is PostgreSQL.
The migration is one line: change the engine URL from sqlite:///expenses.db to a PostgreSQL connection string. Every SQLModel call — session.get(), select(), .where(), func.sum(), .group_by() — works identically. Hosted PostgreSQL options include Supabase, Neon, and Railway.
What comes next
Your API now has real persistence, powerful queries, and a full test suite. But there is one big problem — anyone who knows the URL can read, modify, or delete any expense.
Ready to lock it down? Secure Your API with Authentication picks up right where this course ends — same expense tracker, same codebase. You will add user registration, JWT login, and make every expense belong to the user who created it.