Building Fortify: Why I Picked This Stack

Cover Image for Building Fortify: Why I Picked This Stack

Choosing a tech stack for your average web app is a solved problem. You have your favorites, the trade-offs are well-known, and the path is clear. But fintech is a different beast. Before you even look at a line of code, you have to navigate a landscape that is far narrower and more restrictive than most developers expect. In this world, your options aren't just about preference—they're about what's actually possible.

I wanted to walk through the major decisions behind Fortify. While the dry, technical details are in the repo's Architecture Decision Records (I'll link those later), this is the narrative version of why I chose what I did and what I wish I'd known on day one.

The Framework: Next.js

Next.js with the App Router is the obvious heavy hitter for React right now, but for fintech, it’s specifically about that line between the server and the client.

In a normal app, SSR is a nice performance boost. In banking, it's a security essential. Sensitive work—like fetching balances or talking to financial APIs—needs to happen on the server. Keeping credentials out of the browser isn't just good practice; it's the job. Next.js makes this the default path, not something you have to hack together.

I considered Remix, but given the ecosystem and the specific needs of this project, Next.js was the more pragmatic choice.

The Backend: Appwrite

I needed a reliable way to handle auth, sessions, and data without spending months building a custom backend. The goal was to focus on the interesting fintech problems—Plaid, Dwolla, and money movement logic—not to get bogged down in session management.

A Backend-as-a-Service (BaaS) kept the overhead low. I looked at the big three: Appwrite, Supabase, and Firebase.

Firebase was an easy "no." It's solid, but NoSQL is a headache for relational financial data, and I didn't want to be locked into Google's infrastructure.

Supabase was the toughest to pass up. Its Postgres foundation is incredible for complex reporting and transaction history. If I were starting the project today, I might actually lean this way.

Appwrite won because it’s self-hostable. In fintech, the philosophy of owning your data matters. Even at the sandbox stage, I liked the idea of using a tool that respects that control. Plus, having auth, database, and storage under one SDK made the integration incredibly smooth.

The trade-off? Appwrite's document model might get tight as the data grows complex, but for now, it’s a risk I’m willing to manage.

The Fintech Layer: Reality Check

This is where things get real. When you think of moving money, names like Zelle or Venmo come to mind. But here's the catch: they aren't developer platforms.

Zelle is a network owned by the big banks. To touch it, you basically need a banking license. Venmo and Cash App are closed loops with no public APIs. For a developer building bank-to-bank ACH transfers, your real options are Dwolla, Stripe, or a few newer players like Moov. Understanding these limits early saves you from chasing ghosts.

Bank Linking: Plaid

The industry made this choice for me. Plaid is the gold standard for bank linking. The documentation is great, and the sandbox actually behaves like the real world. While there are alternatives, none offered a compelling reason to stray from the path Plaid has cleared.

One thing to keep in mind: Plaid handles the "handshake" and fetches data, but it doesn't actually move a cent. That separation is a massive part of how this stack functions.

Payment Processing: Dwolla over Stripe

Stripe is the default for almost everything else, so why not here? Stripe is brilliant at cards, but ACH feels like a secondary feature there. Dwolla, on the other hand, is built specifically for bank-to-bank transfers.

Its data model—customers, funding sources, and transfers—mapped perfectly to a banking app. The "cost" is a smaller community and more time spent reading official docs rather than Stack Overflow, but for this specific use case, it was the right tool for the job.

The Plaid + Dwolla Pattern

The magic isn't in the individual services; it's in the handoff. Plaid verifies the account, and Dwolla moves the money. They never cross paths, which means if Plaid goes down, your existing transfers aren't affected. This is exactly how the "big players" build, and mimicking that in Fortify was a top priority.

The handoff between them is the key integration point in the entire application:

User initiates bank link

→ Plaid OAuth flow (user authenticates directly with their bank)

→ Plaid returns verified account credentials

→ Credentials passed to Dwolla as a verified funding source

→ Dwolla processes ACH transfers using that funding source

Plaid never touches money movement. Dwolla never handles bank authentication. Each service operates within its own domain, and failures stay isolated — a Plaid outage affects account linking, not in-flight transfers. This is the pattern real fintech products use, and building it hands-on is what makes this project different from one that mocks the financial layer.

Testing: A Consequence of the Service Layer

Testing a stack with three external services is a challenge. You need realism without hitting live APIs. I chose Mock Service Worker (MSW) because it intercepts requests at the network level. Instead of just replacing a module, you're testing the actual fetch call. For fintech, where the shape of the response is everything, that extra layer of realism is worth it. The full testing strategy will be covered later in this series.

The Full Stack at a Glance

Layer Technology Why
Framework Next.js (App Router) Server-first by default; security and performance
Language TypeScript End-to-end type safety across financial logic
Auth & Database Appwrite Self-hostable BaaS; clean SDK; single integration surface
Bank Linking Plaid Industry standard; largest institution coverage
Payments Dwolla Purpose-built for ACH; natural fit for bank-to-bank transfers
Validation Zod Runtime schema validation at every data boundary
Testing Jest + RTL + MSW Realistic API mocking without live credentials
Monitoring Sentry Error tracking across client, server, and edge runtimes

The deeper reasoning behind each of these decisions lives in the Architecture Decision Records in the repository:

Coming up next: I'll dive into the auth implementation with Appwrite—the good, the bad, and the parts I had to fix.

Let’s Build the Future Together

I’m currently seeking a full-time engineering role where I can contribute to secure, logic-driven systems. If you're looking for a disciplined developer to join your team, I’d love to hear from you.