Skip to content

Commit 11bbdbb

Browse files
Initial commit
0 parents  commit 11bbdbb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3913
-0
lines changed

.env.example

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ----------------------------------------
2+
# Environment
3+
# ----------------------------------------
4+
NODE_ENV=development
5+
PORT=9000
6+
LOG_LEVEL=info
7+
8+
# ----------------------------------------
9+
# App Metadata
10+
# ----------------------------------------
11+
APP_NAME=Foundry API
12+
APP_VERSION=0.1.0
13+
APP_SCHEME=foundry
14+
APP_WEB_URL=http://localhost:3000
15+
16+
# ----------------------------------------
17+
# Database
18+
# ----------------------------------------
19+
DATABASE_URL=postgresql://user:password@localhost:5432/foundry
20+
21+
# ----------------------------------------
22+
# Frontend + Auth URLs
23+
# ----------------------------------------
24+
FRONTEND_URL=http://localhost:3000
25+
BETTER_AUTH_URL=http://localhost:9000
26+
BETTER_AUTH_SECRET=CHANGE_ME_SUPER_SECRET
27+
CORS_EXTRA_ORIGINS=
28+
29+
# ----------------------------------------
30+
# Email (Resend)
31+
# ----------------------------------------
32+
RESEND_API_KEY=CHANGE_ME
33+
EMAIL_FROM="Foundry <no-reply@localhost>"
34+
35+
# ----------------------------------------
36+
# Observability
37+
# ----------------------------------------
38+
SENTRY_DSN=
39+
SENTRY_ENV=development
40+
SENTRY_RELEASE=local
41+
42+
# ----------------------------------------
43+
# Redis (rate limiting, caching)
44+
# ----------------------------------------
45+
REDIS_URL=redis://localhost:6379
46+
47+
# ----------------------------------------
48+
# Rate limits (per IP)
49+
# ----------------------------------------
50+
RATE_LIMIT_GLOBAL_MAX=60
51+
RATE_LIMIT_AUTH_MAX=20
52+
53+
# ----------------------------------------
54+
# Object storage (MinIO/S3)
55+
# ----------------------------------------
56+
STORAGE_ENDPOINT=http://localhost:9002
57+
STORAGE_ACCESS_KEY=minioadmin
58+
STORAGE_SECRET_KEY=minioadmin
59+
STORAGE_BUCKET=foundry-bucket
60+
STORAGE_USE_SSL=false
61+
62+
# ----------------------------------------
63+
# Inngest (async jobs/events)
64+
# ----------------------------------------
65+
INNGEST_EVENT_KEY=

.env.production.example

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ----------------------------------------
2+
# Environment
3+
# ----------------------------------------
4+
NODE_ENV=production
5+
PORT=9000
6+
LOG_LEVEL=info
7+
8+
# ----------------------------------------
9+
# App Metadata
10+
# ----------------------------------------
11+
APP_NAME=Foundry API
12+
APP_VERSION=1.0.0
13+
APP_SCHEME=foundry
14+
APP_WEB_URL=https://app.foundrystack.com
15+
16+
# ----------------------------------------
17+
# Database
18+
# ----------------------------------------
19+
DATABASE_URL=postgresql://user:password@db:5432/foundry
20+
21+
# ----------------------------------------
22+
# Frontend + Auth URLs
23+
# ----------------------------------------
24+
FRONTEND_URL=https://app.foundrystack.com
25+
BETTER_AUTH_URL=https://api.foundrystack.com
26+
BETTER_AUTH_SECRET=SUPER_LONG_RANDOM_SECRET
27+
CORS_EXTRA_ORIGINS=
28+
29+
# ----------------------------------------
30+
# Email (Resend)
31+
# ----------------------------------------
32+
RESEND_API_KEY=CHANGE_ME
33+
EMAIL_FROM="Foundry <[email protected]>"
34+
35+
# ----------------------------------------
36+
# Observability
37+
# ----------------------------------------
38+
SENTRY_DSN=
39+
SENTRY_ENV=production
40+
SENTRY_RELEASE=1.0.0
41+
42+
# ----------------------------------------
43+
# Redis (rate limiting, caching)
44+
# ----------------------------------------
45+
REDIS_URL=redis://redis:6379
46+
47+
# ----------------------------------------
48+
# Rate limits (per IP)
49+
# ----------------------------------------
50+
RATE_LIMIT_GLOBAL_MAX=120
51+
RATE_LIMIT_AUTH_MAX=30
52+
53+
# ----------------------------------------
54+
# Object storage (MinIO/S3)
55+
# ----------------------------------------
56+
STORAGE_ENDPOINT=https://minio.foundrystack.com
57+
STORAGE_ACCESS_KEY=CHANGE_ME
58+
STORAGE_SECRET_KEY=CHANGE_ME
59+
STORAGE_BUCKET=foundry-prod
60+
STORAGE_USE_SSL=true
61+
62+
# ----------------------------------------
63+
# Inngest (async jobs/events)
64+
# ----------------------------------------
65+
INNGEST_EVENT_KEY=CHANGE_ME

.github/workflows/ci.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
environment: production
12+
env:
13+
DATABASE_URL: ${{ secrets.DATABASE_URL }}
14+
FRONTEND_URL: ${{ secrets.FRONTEND_URL }}
15+
BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }}
16+
BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
17+
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
18+
EMAIL_FROM: ${{ secrets.EMAIL_FROM }}
19+
REDIS_URL: ${{ secrets.REDIS_URL }}
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Bun
25+
uses: oven-sh/setup-bun@v1
26+
with:
27+
bun-version: latest
28+
29+
- name: Install dependencies
30+
run: bun install --frozen-lockfile
31+
32+
- name: Lint (type-check)
33+
run: bun run lint
34+
35+
- name: Build
36+
run: bun run build
37+
38+
- name: Contracts (OpenAPI emit + client)
39+
run: bun run contracts:build
40+
41+
- name: Contract sanity check
42+
run: bun run contracts:test
43+
44+
- name: Auth contract check
45+
run: bun run test:auth-contract
46+
47+
- name: Smoke tests
48+
run: bun run test:smoke

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# dev
2+
.yarn/
3+
!.yarn/releases
4+
.vscode/*
5+
!.vscode/launch.json
6+
!.vscode/*.code-snippets
7+
.idea/workspace.xml
8+
.idea/usage.statistics.xml
9+
.idea/shelf
10+
11+
# deps
12+
node_modules/
13+
14+
# env
15+
.env
16+
.env.production
17+
18+
# logs
19+
logs/
20+
*.log
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
pnpm-debug.log*
25+
lerna-debug.log*
26+
27+
# misc
28+
.DS_Store
29+
dist/
30+
drizzle/

.pre-commit-config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v4.6.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- repo: https://github.com/pre-commit/mirrors-prettier
8+
rev: v3.3.3
9+
hooks:
10+
- id: prettier
11+
additional_dependencies:
12+

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"semi": true,
3+
"singleQuote": false,
4+
"trailingComma": "es5",
5+
"printWidth": 100,
6+
"tabWidth": 2
7+
}

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
SHELL := /bin/bash
2+
3+
dev:
4+
@bun run dev
5+
6+
lint:
7+
@bun run lint
8+
9+
format:
10+
@bun run format
11+
12+
format-check:
13+
@bun run format:check
14+
15+
build:
16+
@bun run build
17+
18+
db-generate:
19+
@bun run db:generate
20+
21+
db-push:
22+
@bun run db:push
23+
24+
compose-up:
25+
@docker-compose up -d
26+
27+
compose-down:
28+
@docker-compose down

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Foundry API Starter
2+
3+
Production-ready Node.js API starter built with Hono, TypeScript, Drizzle ORM, Better Auth, Redis rate limiting, Prometheus metrics, Pino logging, Sentry tracing, and Resend-powered emails. This repo is meant to be a clean, extensible foundation for modern backend services.
4+
5+
## Highlights
6+
- Hono + TypeScript with Zod-validated OpenAPI routes and auto-generated docs at `/docs`
7+
- Environment validation with sensible defaults and LAN-aware base URL detection
8+
- Pino logging (pretty in dev, JSON in prod) with request logging middleware
9+
- Redis-backed rate limiting and cache utilities
10+
- Drizzle ORM + Neon serverless Postgres driver with ready-to-run auth schema
11+
- Better Auth (Expo + bearer + admin + 2FA + orgs) wired for email flows via Resend
12+
- Prometheus metrics endpoint at `/api/v1/metrics`
13+
- Sentry instrumentation (opt-in via `SENTRY_DSN`)
14+
- CI pipeline for lint + build on push/PR
15+
16+
## Getting Started
17+
1) **Install dependencies**
18+
```bash
19+
bun install
20+
```
21+
22+
2) **Copy env template and fill in secrets**
23+
```bash
24+
cp .env.example .env
25+
```
26+
Required: `DATABASE_URL`, `BETTER_AUTH_SECRET`, `RESEND_API_KEY`, `EMAIL_FROM`, `REDIS_URL`, `FRONTEND_URL`, `BETTER_AUTH_URL`.
27+
28+
3) **Run services** (local examples)
29+
```bash
30+
# Redis
31+
docker run -d --name redis -p 6379:6379 redis:7
32+
```
33+
34+
4) **Start the API**
35+
```bash
36+
bun run dev
37+
```
38+
The server will pick a LAN IP for `BASE_URL` in dev; visit `/api/v1/health` and `/docs`.
39+
40+
## Scripts
41+
- `bun run dev` - watch mode via `tsx`
42+
- `bun run lint` - type-check only (`tsc --noEmit`)
43+
- `bun run build` - compile TypeScript and rewrite paths with `tsc-alias`
44+
- `bun start` - run compiled output (`dist`)
45+
- `bun run db:generate` - drizzle-kit generate
46+
- `bun run db:push` - drizzle-kit push
47+
- `bun run openapi:emit` - generate `docs/openapi.json` from the app
48+
- `bun run openapi:client` - generate typed client at `clients/openapi.ts`
49+
- `bun run contracts:build` - emit OpenAPI + client
50+
- `bun run contracts:test` - sanity-check the emitted OpenAPI JSON
51+
- `bun run test:auth-contract` - minimal auth/OpenAPI contract check
52+
- `bun run test:smoke` - in-process smoke checks for health/docs
53+
- `k6 run k6/smoke.js` - lightweight load/smoke from the outside (set `BASE_URL`)
54+
55+
## Environment
56+
See `.env.example` for defaults. Key variables:
57+
- `APP_NAME`, `APP_VERSION`, `APP_SCHEME`, `APP_WEB_URL`
58+
- `BASE_URL`, `PORT`, `LOG_LEVEL`
59+
- `DATABASE_URL`, `FRONTEND_URL`
60+
- `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET`
61+
- `RESEND_API_KEY`, `EMAIL_FROM`
62+
- `SENTRY_DSN`, `SENTRY_ENV`, `SENTRY_RELEASE`
63+
- `REDIS_URL`
64+
- `CORS_EXTRA_ORIGINS`
65+
- `RATE_LIMIT_GLOBAL_MAX`, `RATE_LIMIT_AUTH_MAX`
66+
- `STORAGE_ENDPOINT`, `STORAGE_ACCESS_KEY`, `STORAGE_SECRET_KEY`, `STORAGE_BUCKET`, `STORAGE_USE_SSL`
67+
- `INNGEST_EVENT_KEY`
68+
69+
## API Surface
70+
- `/api/v1/health` - health check
71+
- `/api/v1/metrics` - Prometheus metrics
72+
- `/api/v1/auth/*` - Better Auth handler (REST + OpenAPI)
73+
- `/docs` - OpenAPI JSON + docs UI
74+
- `/docs/ui` - Scalar-powered API reference UI (OpenAPI from `/docs`)
75+
76+
## Architecture Notes
77+
- **App lifecycle**: graceful signal handling + unhandled error capture to Sentry (prod)
78+
- **Middleware**: Sentry context, Prometheus registry, Pino request logger (with IP/UA + request IDs), Redis rate limiter (global + stricter auth limiter), CORS + security headers, global error handler, 404 handler
79+
- **Auth**: Better Auth with Drizzle adapter (Postgres), Expo support, bearer signing, admin, orgs, 2FA, email verification/reset flows via Resend
80+
- **Email**: React email templates (invite, OTP, verify, reset, welcome) branded via `APP_NAME`
81+
- **Storage**: MinIO/S3 helper for presigned URLs and uploads
82+
- **Async**: Inngest client stub for event-based jobs (set `INNGEST_EVENT_KEY` to enable sending)
83+
- **Contracts**: OpenAPI doc emit + typed client generation via `openapi-typescript`
84+
- **Domain scaffold**: Example `task` table tied to `user` (`src/db/schema/tasks.ts`) and Inngest task handlers (`src/jobs/tasks.ts`) as placeholders to extend
85+
86+
## Observability
87+
- **Sentry**: set `SENTRY_DSN` (plus `SENTRY_ENV` and `SENTRY_RELEASE`) to capture errors; unhandled errors and 500s flush on shutdown.
88+
- **Metrics**: `/api/v1/metrics` Prometheus endpoint; sample scrape config in `docs/observability/prometheus.yml`.
89+
- **Logs**: Pino JSON in production (stdout). Ship to Loki/ELK/Datadog via an agent; sample Promtail file shipping `/var/log/foundry-api.log` in `docs/observability/promtail-config.yaml`.
90+
- **Dashboards**: Import `docs/observability/grafana-dashboard.json` and point datasource UID `PROM_DS` at your Prometheus instance.
91+
- **Crash test**: `/api/v1/crash` throws to validate alerting and error reporting.
92+
93+
## Local Dev Services
94+
- `docker-compose.yml` spins up Postgres, Redis, and MinIO (S3-compatible).
95+
- Defaults assume `STORAGE_ENDPOINT=http://localhost:9002`, Postgres on 5432, Redis on 6379.
96+
- Make sure the API port (9000) does not conflict with MinIO (mapped to 9002).
97+
98+
## Developer Experience
99+
- `Makefile` shortcuts: `make dev`, `make lint`, `make format`, `make build`, `make compose-up/down`.
100+
- Pre-commit config (`.pre-commit-config.yaml`) ready for formatting/lint hooks if you use the `pre-commit` tool.
101+
102+
## CI/CD
103+
GitHub Actions workflow runs on push/PR:
104+
- Install dependencies with Bun
105+
- `bun run lint` (type-check)
106+
- `bun run build`
107+
Extend with deploy, migrations, and smoke tests as you wire up your platform.
108+
109+
## Production Checklist
110+
- Provide real secrets for database, Redis, Resend, Better Auth, and Sentry
111+
- Set `BASE_URL`, `FRONTEND_URL`, and `BETTER_AUTH_URL` to public hosts
112+
- Run behind TLS and put a reverse proxy (Caddy/Nginx) in front
113+
- Configure process manager (systemd/PM2) and health checks on `/api/v1/health`
114+
- Enable log shipping (Pino JSON) and metrics scraping on `/api/v1/metrics`
115+
- Generate and ship OpenAPI (`bun run contracts:build`) and distribute `clients/openapi.ts` to consumers
116+
- Validate crash reporting with `/api/v1/crash`; verify Prometheus scrape and log shipping
117+
- Tighten CORS origins per environment and rotate credentials regularly
118+
- Run external smoke/load: `k6 run k6/smoke.js -e BASE_URL=https://api.yourdomain.com -e VUS=10 -e DURATION=2m`
119+
- Build domain: extend Drizzle schemas/migrations (e.g., `src/db/schema/tasks.ts`), add services/routes, and wire Inngest handlers (`src/jobs/tasks.ts`)
120+
121+
## Reverse Proxy
122+
- See `deploy/nginx.conf` for an example Nginx config (HTTP→HTTPS redirect, gzip, proxy headers). Replace certificate paths and back-end host/port as needed.
123+
124+
## Next Steps (for future build-out)
125+
- Add infra for file storage (e.g., MinIO/S3), background jobs (Inngest), and queueing
126+
- Expand database schemas for domain models (finance/debts/baby steps stubs are placeholders)
127+
- Harden auth flows (rate limiting per route, IP/device fingerprinting, email domain rules)
128+
- Add security headers like CSP and stricter CORS policies per environment
129+
- Add end-to-end smoke tests and load tests

0 commit comments

Comments
 (0)