A secure open-banking app for bank linking, transaction sync, ACH transfers, and P2P payments.
Interlock is a full-stack fintech monorepo that connects Plaid banking data and Dwolla ACH payments through a Next.js frontend and a Go backend. Local runtime, CI, deployment, and database migrations are owned by server-go/.
- Frontend: Next.js 16, React 19, TypeScript, Tailwind CSS 4, Zustand, Radix UI, Chart.js
- Backend: Go, Chi, pgx, PostgreSQL
- Integrations: Plaid, Dwolla, SendGrid, Sentry
- Testing: Vitest, React Testing Library, Go tests
- Deployment: Docker Compose, GHCR images, EC2-oriented deploy script
| Area | Status | Notes |
|---|---|---|
| Authentication | Complete | Sessions, refresh tokens, email verification, password reset, account lockout |
| Bank linking | Complete | Plaid Link, token exchange, update mode, balance sync |
| Transactions | Complete | Sync, filters, categories, dashboard summaries |
| ACH transfers | Complete | Dwolla funding sources, transfer creation, status tracking, cancellation |
| P2P transfers | Complete | Recipient search, send/receive flows, notifications |
| Notifications | Complete | In-app notifications and preferences |
| Security | Complete | Rate limits, secure headers, PII encryption, webhook verification |
| Observability | Complete | Sentry, structured logs, health checks, metrics endpoint |
| Go backend migration | Complete | Native runtime, CI, deployment, and migrations use server-go/ |
client/ Next.js app and browser-facing API client
server-go/ Go backend, native API handlers, migrations, and integration code
docs/ Deployment and observation notes
performance/ k6 performance scripts
The local Docker topology runs PostgreSQL and the Go backend on :8080.
- Bun v1.3+
- Go 1.26+ for
server-go - PostgreSQL 16+
- Docker and Docker Compose, optional but recommended for local services
Note
Use Bun for JavaScript dependencies and scripts in this repo. Prefer bun over pnpm or npm, and bunx over npx.
bun install
cp .env.example .envUpdate the root .env with database, JWT, encryption, Plaid, Dwolla, SendGrid, and Sentry values as needed. This is the single env file for the monorepo. For local development, the most important values are:
DATABASE_URL=postgresql://postgres:password@localhost:5432/interlock
JWT_SECRET=replace-with-at-least-32-characters
ENCRYPTION_KEY=replace-with-32-characters
CLIENT_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:8080The Go backend loads .env when started from the repo root and ../.env when started from server-go/, without overriding existing shell variables. Run migrations through the root script so the root .env is loaded before the migration subcommand.
Start local infrastructure:
docker compose up -d postgresApply Go-owned database migrations:
bun run migrateRun the app:
bun run dev- Client:
http://localhost:3000 - API:
http://localhost:8080
# Install all workspace dependencies
bun install
# Run client and Go backend
bun run dev
# Backward-compatible alias for the Go backend dev script
bun run dev:go-edge
# Client only
bun run --cwd client dev
# Go backend only
go run ./server-go/cmd/interlock-go
# Apply Go database migrations
bun run migrate
# Go admin/debug CLI
bun run admin -- usersgo run ./server-go/cmd/interlock-go loads the root .env for normal server startup. The migration subcommand reads the process environment directly, so prefer bun run migrate locally or Docker Compose/deploy commands in container workflows.
Legacy admin/debug workflows now run through bun run admin -- <command>. See docs/ADMIN_RUNBOOK.md.
# Client tests
bun run --cwd client test
# Client lint
bun run --cwd client lint
# Go formatting check
cd server-go && test -z "$(gofmt -l .)"
# Go tests
cd server-go && go test ./...Performance smoke tests live in performance/k6/backend-hotpaths.js and expect a running backend.
server-go is the backend runtime. Native handlers cover health, metrics, auth, Plaid, banks/accounts, transactions, transfers, P2P, notifications, and webhooks. Database migrations are embedded in the Go binary and run through bun run migrate locally or server-go migrate up in deployment containers.
# Local database only
docker compose up -d postgres
# Local backend topology: Postgres and Go backend
docker compose up -d postgres server-go
# Optional tools
docker compose --profile tools up studio
docker compose --profile tools run semgrepProduction-oriented Compose and deployment files are docker-compose.prod.yml and deploy.sh. Server images are built and pushed to GHCR by .github/workflows/build-server.yml.
- Do not commit real
.envfiles or production credentials. - PII fields are encrypted with AES-256-GCM-compatible helpers across the TypeScript and Go runtimes.
- Passwords use bcrypt, auth uses JWT access tokens plus refresh-token rotation, and sensitive routes are rate-limited.
- Dwolla webhooks use HMAC verification and idempotency handling.
- Keep
JWT_SECRET,ENCRYPTION_KEY, Plaid keys, Dwolla keys, SendGrid keys, GHCR PATs, and Sentry tokens out of logs and docs.
docs/DEPLOYMENT.mdfor deployment stepsdocs/client-observation.mdfor frontend observationsdocs/server-observation.mdfor backend observationsserver-go/README.mdfor Go backend detailsserver-go/contracts/GO_MIGRATION_STATUS.mdfor migration status