DEV Community

Cover image for Building a Full-Stack iOS Stock App on $4/Month: UIKit, Go, Supabase, and AI
nooraja
nooraja

Posted on

Building a Full-Stack iOS Stock App on $4/Month: UIKit, Go, Supabase, and AI

Building a Full-Stack iOS Stock App on IDR 60K/Month

TradingFlow iOS and Go architecture

TradingFlow began as an iOS app for US stock search, watchlists, details, charts, and news. Making it usable beyond a development machine eventually required a complete path from UIKit to the database.

Architecture

UIKit app
  ├── Supabase Auth → user session in Keychain
  └── Bearer token over HTTPS
          ↓
       Caddy
          ↓
   Go REST API (net/http)
     ├── Finnhub
     ├── Yahoo / yfinance-go
     └── Supabase Postgres + RLS
Enter fullscreen mode Exit fullscreen mode

Provider secrets stay on the server. The app contains publishable configuration and the user session only. The example deliberately excludes credentials, UUIDs, deployment addresses, and personal email.

The iOS layer

The main UI uses UIKit, Auto Layout, Dynamic Type, SF Symbols, and a native tab bar. Networking uses URLSession and async/await. Search is debounced by 350 ms and cancels the previous task so an older response cannot overwrite a newer query.

Tokens are stored in Keychain. After a 401, the client refreshes the session once and retries. If that still fails, it removes the session and returns to sign-in. UserDefaults is used only for an email address the user explicitly asks the app to remember.

Swift Charts is the only SwiftUI surface:

let host = UIHostingController(rootView: PriceChart(chart: response.data))
addChild(host)
container.addSubview(host.view)
host.didMove(toParent: self)
Enter fullscreen mode Exit fullscreen mode

This keeps the screen architecture in UIKit while using Apple’s native charting framework. The trade-off is managing the child-controller lifecycle and the state boundary.

The Go layer

The API uses the standard net/http package. I kept dependencies small and made important behavior explicit:

  • request timeouts and cancellation;
  • symbol and query validation;
  • a stable response envelope;
  • size-bounded in-memory caching;
  • local provider rate limiting and a concurrency gate;
  • error mapping that does not leak upstream responses;
  • authentication middleware and request IDs.

The backend exposes market overview, search, watchlist, detail, chart, statistics, analyst consensus, and news endpoints. GET /health is public, while product endpoints require a valid access token.

Go provides a small binary and straightforward deployment. The cost of that simplicity is visible plumbing: no framework silently chooses the behavior for us.

Market-data providers are imperfect

Finnhub handles quotes, profiles, search, market state, analyst data, and news. Yahoo through yfinance-go fills historical charts, reference quotes, indices, and selected statistics.

I learned to design responses around upstream reality: fields may be null; requests may receive 429; units and timestamps differ; one provider can succeed while another fails; charts can be rejected; and data may be delayed.

Responses therefore include source, mode, fetch time, and cache metadata. The UI supports loading, empty, partial, rate-limit, and retry states. It never silently falls back to fabricated numbers.

Auth, Postgres, and RLS

Supabase Auth issues the user access token. Go verifies it, derives identity from the verified session rather than request input, and accesses the watchlist table with the same user token.

The watchlist primary key is (user_id, symbol). RLS filters SELECT/INSERT/DELETE by the active user. A transaction lock keeps the 100-item cap atomic under concurrent inserts.

Supabase accelerates development, but RLS is not a security checkbox. Policies need tests with at least two accounts to prove isolation.

Deployment and budget

A multi-stage Docker build produces a non-root, read-only container. Docker Compose runs the API and Caddy; only Caddy exposes public ports and handles HTTPS. The VPS has 2 vCPU, 2 GB RAM, 40 GB storage, and runs in Singapore.

The budget I count is:

Item Incremental cost
VPS IDR 60,000/month
Supabase IDR 0 while within the current free access/tier
Market-data providers IDR 0 for the current experiment
Stitch + Figma IDR 0 incremental in this workflow
Experimental hostname + TLS IDR 0

The baseline is IDR 60,000 per month. It excludes an existing ChatGPT/Codex subscription, Apple Developer Program membership, a custom domain, taxes, and overages. A free tier is a limit, not a guarantee of permanent zero cost.

Where ChatGPT/Codex helped

I used AI as a pair programmer to map designs to API contracts, scaffold code, create tests, audit security, and maintain deployment documentation. My loop is:

  1. provide scope, constraints, and source files;
  2. request small, inspectable changes;
  3. read the diff and compare it with the real contract;
  4. run Swift type checks/API contract checks plus go test -race ./... and go vet ./...;
  5. correct unsupported assumptions or labels.

AI has suggested technologies that the project does not use. That is a useful reminder: convincing output is not necessarily correct. Never place secrets in prompts, never paste production configuration into an article, and never skip review because the code compiles.

Trade-offs at a glance

Technology Advantage Cost
UIKit Precise and mature Verbose
Swift Charts bridge Native and dependency-free UIKit–SwiftUI boundary
Go net/http Small and explicit Manual plumbing
Supabase Fast Auth + Postgres + RLS Policy complexity and vendor coupling
Finnhub + Yahoo Useful coverage Limits, partial data, licensing
VPS + Caddy Low cost and high control Self-managed operations
ChatGPT/Codex Faster iteration Hallucinations and risk without review

Mobile full-stack is not a new title for me. It is the ability to follow one user action—such as tapping Save—through UI, token, network, handler, database policy, response, and failure state, then prove that the entire path is safe and understandable.

Top comments (0)