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.pyExpense(SQLModel, table=True) maps directly to a SQLite table. One class handles validation, storage, and serialization. ExpenseOut is gone.
  • database.py — the engine, create_db_and_tables(), and get_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 a lifespan function 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.