A web application that helps people communicate expectations and track relationship health through transparent, mutual rating systems.
- Magic Link Authentication - Password-free sign in via email
- Relationship Creation - Define what matters to you with customizable aspects
- Mutual Ratings - Rate each other privately until both submit
- Table Stakes - Mark must-have aspects with minimum requirements
- Transparent Communication - Share expectations clearly
- Privacy-Focused - No PII storage, email hashing, anonymous nicknames
- Node.js + Express + TypeScript
- PostgreSQL database
- JWT authentication
- Magic link email flow
- React 18 + TypeScript
- Tailwind CSS for styling
- React Router for navigation
- Axios for API calls
- Recharts for data visualization
- Monorepo - Single deployment for both frontend and backend
- Backend serves frontend static files in production
- Railway for hosting (backend + frontend + database)
- Node.js 18+
- PostgreSQL 14+
- npm
- Clone the repository
git clone <your-repo-url>
cd samepage-app- Install dependencies
# Install backend dependencies
cd backend
npm install
# Install frontend dependencies
cd ../frontend
npm install- Set up environment variables
Backend (.env in backend folder):
cp backend/.env.example backend/.env
# Edit backend/.env with your configurationKey environment variables:
DATABASE_URL- PostgreSQL connection stringJWT_SECRET- Secret key for JWT tokens (generate with:openssl rand -base64 32)EMAIL_HASH_SALT- Salt for email hashing (generate with:openssl rand -base64 32)EMAIL_API_KEY- Email service API key (optional for dev)NODE_ENV- Set toproductionfor Railway deployment
- Set up the database
# Create database
createdb samepage
# Run migrations
cd backend
psql -d samepage -f migrations/001_initial_schema.sql
psql -d samepage -f migrations/002_seed_data.sqlRun backend and frontend in separate terminals:
# Terminal 1 - Backend
cd backend
npm run dev
# Runs on http://localhost:3000
# Terminal 2 - Frontend
cd frontend
npm run dev
# Runs on http://localhost:5173 (with proxy to backend)The app will be available at:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000/api
This app uses a monorepo architecture - one Railway service deploys both frontend and backend together.
- Build Phase: Railway builds both backend TypeScript and frontend React app
- Runtime: Backend Express server serves:
- API endpoints at
/api/* - Frontend static files at all other routes
- API endpoints at
- One URL: Everything runs on a single domain (e.g.,
https://thesamepage.up.railway.app)
- Go to Railway
- Click "New Project"
- Select "Deploy from GitHub repo"
- Connect your repository
- Set Root Directory to
/(root of repo)
- Click "New" → "Database" → "PostgreSQL"
- Railway automatically provisions database and sets
DATABASE_URL
Add these in Railway → Variables:
DATABASE_URL=${DATABASE_URL} # Auto-populated by Railway
JWT_SECRET=<generate-with-openssl-rand-base64-32>
EMAIL_HASH_SALT=<generate-with-openssl-rand-base64-32>
NODE_ENV=production
# Optional (for email in production):
EMAIL_API_KEY=your-email-service-api-key
EMAIL_FROM=noreply@yourdomain.comIn Railway's terminal or locally:
railway run psql $DATABASE_URL -f backend/migrations/001_initial_schema.sql
railway run psql $DATABASE_URL -f backend/migrations/002_seed_data.sql- Railway → Service → Settings → Networking
- Click "Generate Domain"
- You'll get:
https://your-app.up.railway.app
- Push to GitHub main branch
- Railway automatically builds and deploys
- Wait 2-3 minutes for build to complete
- Visit your Railway URL to see the app!
The project includes:
railway.json- Builds both backend and frontend, starts withNODE_ENV=productionbackend/src/index.ts- Serves frontend static files whenNODE_ENV=productionfrontend/.env- Uses relative/apiURLs (works in both dev and production)
samepage-app/
├── backend/
│ ├── src/
│ │ ├── config/ # Database config
│ │ ├── controllers/ # Route handlers
│ │ ├── middleware/ # Auth middleware
│ │ ├── routes/ # API routes
│ │ ├── services/ # Business logic
│ │ ├── utils/ # Helper functions
│ │ └── index.ts # Entry point (serves frontend in production)
│ ├── migrations/ # SQL migrations
│ └── package.json
│
├── frontend/
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── context/ # React context (auth)
│ │ ├── pages/ # Page components
│ │ ├── services/ # API calls
│ │ ├── types/ # TypeScript types
│ │ ├── App.tsx # Main app component
│ │ └── vite-env.d.ts # Vite TypeScript definitions
│ ├── dist/ # Built frontend (created during build)
│ └── package.json
│
├── railway.json # Railway monorepo deployment config
├── package.json # Root package.json with workspace scripts
└── README.md
| Variable | Description | Required | Default |
|---|---|---|---|
| DATABASE_URL | PostgreSQL connection string | Yes | - |
| JWT_SECRET | Secret for JWT signing | Yes | - |
| EMAIL_HASH_SALT | Salt for email hashing | Yes | - |
| NODE_ENV | Environment (production/development) | No | development |
| PORT | Port for server (Railway sets this) | No | 3000 |
| EMAIL_API_KEY | Email service API key | No* | - |
| EMAIL_FROM | Sender email address | No* | - |
| CORS_ORIGIN | Allowed CORS origin | No | http://localhost:5173 |
*Required in production for magic link emails to work
| Variable | Description | Default |
|---|---|---|
| VITE_API_URL | API base URL | /api |
In development, Vite proxies /api to http://localhost:3000
In production, backend serves frontend, so /api works directly
GET /api/health- Health checkGET /api/reference/relationship-types- Get relationship typesGET /api/reference/aspects- Get default aspectsGET /api/invite/:token- Get invite details (public)
POST /api/auth/request-magic-link- Request magic link- Body:
{ "email": "user@example.com" }
- Body:
GET /api/auth/verify?token=...- Verify magic link- Returns: JWT session token
User:
GET /api/user/profile- Get user profileGET /api/user/relationships- Get user's relationships
Relationships:
POST /api/relationships/create- Create new relationshipGET /api/relationships/:id- Get relationship details
Invites:
POST /api/invite/:token/accept- Accept invite
- Email hashing (SHA-256) - emails never stored in plain text
- Anonymous nicknames auto-generated
- JWT-based authentication
- Magic links with 15-minute expiry
- Session tokens with 7-day expiry
- Rate limiting on auth endpoints
- CORS protection
- Helmet.js security headers
✅ One deployment instead of separate frontend/backend ✅ No CORS issues (same origin) ✅ Simpler configuration ✅ Lower cost (one Railway service instead of two) ✅ Easier to manage and update
Build fails:
- Check deploy logs in Railway dashboard
- Ensure both backend and frontend build successfully
- Verify all dependencies are in package.json files
Frontend shows API JSON instead of React app:
- Ensure
NODE_ENV=productionis set in Railway variables - Check that
frontend/distfolder was created during build - Verify
railway.jsonbuilds both backend and frontend
API calls fail with 404:
- Check that API routes use
/apiprefix - Ensure
app.use('/api', routes)comes before static file serving inbackend/src/index.ts
502 Error:
- Check Railway logs for crashes
- Verify
DATABASE_URLis set - Ensure database migrations ran successfully
Database connection error:
# Check if PostgreSQL is running
pg_isready
# Check if database exists
psql -l | grep samepage
# Verify DATABASE_URL in backend/.envPort already in use:
# Find process using port 3000
lsof -i :3000
# Kill it or change PORT in backend/.envModule not found:
# Reinstall dependencies
cd backend && rm -rf node_modules && npm install
cd ../frontend && rm -rf node_modules && npm installVite build fails:
- Check for TypeScript errors:
cd frontend && npm run build - Ensure
vite-env.d.tsexists with proper types
- MONOREPO_DEPLOYMENT.md - Detailed monorepo deployment guide
- DEPLOYMENT_SUCCESS.md - Successful deployment verification
- QUICKSTART.md - Get running locally in 5 minutes
- PROJECT_SUMMARY.md - What's been built
- requirements.md - Full project specification
MIT
For issues and questions, please open a GitHub issue.
Live Demo: Deployed on Railway at your generated URL Stack: React + TypeScript + Express + PostgreSQL Architecture: Monorepo with single-service deployment