SpendWise
Personal finance app
Expense tracking and budgeting as a Django REST API with an Expo client. It handles recurring transactions, splits group expenses between people, and settles up the balances that fall out of it.
- JWT auth with rotating, blacklisted refresh tokens
- Budgets, recurring transactions and spending analysis
- Group expenses with flexible splitting and settlements
- Financial reports and data export
A Django REST backend paired with an Expo client, with the auth flow designed so the app can reach its home screen without a second round trip after registering or signing in.
- Django
- DRF
- PostgreSQL
- Expo
- React Native
- TypeScript
How it is built
A Django REST backend paired with an Expo client, with the auth flow designed so the app can reach its home screen without a second round trip after registering or signing in.
Stack
- Django 4.2 backend
- DRF 3.16 backend
- Simple JWT backend
- django-filter backend
- django-environ backend
- Expo 54 client
- React Native 0.81 client
- React Query client
- React Hook Form client
- Zod client
Architecture
-
Expo client
client
- Expo Router
- React Query
- React Hook Form
- Zod
-
Django REST API
service
- DRF
- Simple JWT
- django-filter
Flow
- Expo client Django REST API HTTPS, bearer token
API
REST
- Register and login both return the token pair plus the user, so the client can skip a follow-up profile request
- POST /api/register/ Returns user plus token pair
- POST /api/token/ Login, returns token pair plus full profile
- POST /api/token/refresh/ Rotates the refresh token
- POST /api/token/logout/ Blacklists the refresh token
- GET /api/users/me/ Current user profile
Authentication
JSON Web Tokens
- Bearer tokens via Simple JWT rather than DRF's plain token auth
- Refresh tokens rotate on every use and the spent one is blacklisted
- A stolen refresh token is good for a single refresh before it is dead
Notable decisions
-
Rotate and blacklist refresh tokens on every use
Caps the damage from a stolen refresh token at one refresh
-
Return the user object alongside the token pair
Removes a round trip on app startup and after registration