Skip to content

Kaernest Backend - Complete Implementation Guide

๐Ÿ“ฆ What You Got

A complete, production-ready backend with: - โœ… Magic link authentication (passwordless) - โœ… 90-day session cookies - โœ… PostgreSQL character storage (JSONB) - โœ… SendGrid email integration - โœ… Full CRUD API for characters - โœ… Auto-cleanup of expired tokens/sessions - โœ… CORS configured for your frontend - โœ… Comprehensive error handling

Total: 12 files, ~1,200 lines of code

๐ŸŽฏ Zero to Running in 30 Minutes

Step 1: Extract Files (2 min)

cd ~/projects  # or wherever you develop
tar -xzf kaernest-backend.tar.gz
cd kaernest-backend

Step 2: Install & Configure (5 min)

# Run quick setup
chmod +x setup.sh
./setup.sh

# Edit .env file
nano .env

Required .env values:

DATABASE_URL=postgresql://your_user:your_pass@localhost:5432/kaernest_builder
FRONTEND_URL=https://velloriam.kaernest.com
SENDGRID_API_KEY=SG.your_actual_key_here
FROM_EMAIL=noreply@kaernest.com

Step 3: Database Setup (3 min)

# Create database
createdb kaernest_builder

# Or via psql:
psql -U postgres -c "CREATE DATABASE kaernest_builder;"

# Run migrations
npm run db:setup

Step 4: SendGrid Setup (10 min)

  1. Go to https://sendgrid.com โ†’ Sign up (free)
  2. Settings โ†’ API Keys โ†’ Create API Key
  3. Copy key to .env as SENDGRID_API_KEY
  4. Settings โ†’ Sender Authentication โ†’ Verify single sender
  5. Use noreply@kaernest.com (or your domain)
  6. Check email and verify

Step 5: Test & Start (10 min)

# Test configuration
node scripts/test-setup.js

# Start development server
npm run dev

# In another terminal, test login:
curl -X POST http://localhost:3001/auth/request-magic-link \
  -H "Content-Type: application/json" \
  -d '{"email":"your@email.com"}'

# Check your email for magic link!

๐ŸŒ Production Deployment Options

You already host velloriam.kaernest.com, so add the backend there:

1. Upload backend

# On your local machine
rsync -avz kaernest-backend/ user@your-server.com:~/kaernest-backend/

2. Install on server

# SSH into server
ssh user@your-server.com

cd ~/kaernest-backend
npm install --production
cp .env.example .env
nano .env  # Configure for production
npm run db:setup

3. Configure Nginx

# Add to your nginx config
location /api/ {
    proxy_pass http://localhost:3001/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
}
sudo nginx -t
sudo systemctl reload nginx

4. Run with PM2

npm install -g pm2

# Start
pm2 start server.js --name kaernest-api

# Save config
pm2 save

# Auto-start on reboot
pm2 startup
# Follow the instructions it prints

# Check logs
pm2 logs kaernest-api

Your API is now at: https://velloriam.kaernest.com/api/


Option B: Railway (Free Tier - Easier)

  1. Push to GitHub (if not already)
  2. Go to https://railway.app
  3. "New Project" โ†’ "Deploy from GitHub"
  4. Select your repo
  5. Add PostgreSQL service
  6. Set environment variables in Railway dashboard
  7. Done! Auto-deploys on git push

Cost: Free for low traffic, ~$5/month if you exceed free tier


Option C: DigitalOcean App Platform

Similar to Railway, $5/month includes database.


๐Ÿ”Œ Frontend Integration

Add to your frontend:

1. Install fetch polyfill (if needed):

# Not needed for modern browsers, but just in case:
npm install whatwg-fetch

2. Create API client:

Create src/api/client.ts:

const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:3001';

async function apiRequest(endpoint: string, options = {}) {
  const response = await fetch(`${API_BASE}${endpoint}`, {
    ...options,
    credentials: 'include', // Send cookies
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({ error: 'Request failed' }));
    throw new Error(error.error || 'Request failed');
  }

  return response.json();
}

export const api = {
  // Auth
  requestMagicLink: (email: string) =>
    apiRequest('/auth/request-magic-link', {
      method: 'POST',
      body: JSON.stringify({ email }),
    }),

  checkAuth: () => apiRequest('/auth/status'),

  logout: () => apiRequest('/auth/logout', { method: 'POST' }),

  // Characters
  listCharacters: () => apiRequest('/characters'),

  getCharacter: (id: string) => apiRequest(`/characters/${id}`),

  createCharacter: (data: CharacterDraft) =>
    apiRequest('/characters', {
      method: 'POST',
      body: JSON.stringify({ data }),
    }),

  updateCharacter: (id: string, data: CharacterDraft) =>
    apiRequest(`/characters/${id}`, {
      method: 'PUT',
      body: JSON.stringify({ data }),
    }),

  deleteCharacter: (id: string) =>
    apiRequest(`/characters/${id}`, { method: 'DELETE' }),
};

3. Add environment variable:

.env.local:

VITE_API_URL=http://localhost:3001

.env.production:

VITE_API_URL=https://velloriam.kaernest.com/api

4. Use in components:

See FRONTEND_INTEGRATION.md for full React component examples.


๐Ÿ“Š Database Schema

users
  - id (uuid, primary key)
  - email (unique)
  - created_at
  - last_login

magic_tokens
  - token (primary key)
  - user_id โ†’ users.id
  - expires_at
  - created_at

sessions
  - session_id (primary key)
  - user_id โ†’ users.id
  - expires_at
  - created_at

characters
  - id (uuid, primary key)
  - user_id โ†’ users.id
  - name
  - data (jsonb) โ† Full CharacterDraft
  - created_at
  - updated_at

All foreign keys have ON DELETE CASCADE - deleting a user deletes their data.


๐Ÿ”’ Security Checklist

  • No passwords stored
  • Crypto-random tokens (32 bytes)
  • Tokens expire in 15 minutes
  • Tokens are one-time use
  • HttpOnly cookies (JS can't access)
  • Secure cookies in production
  • CORS restricted to your domain
  • Rate limiting recommended for production

For production, consider adding: - Rate limiting on /auth/request-magic-link (5 requests/15min per IP) - Honeypot field in login form - CAPTCHA if abuse occurs


๐Ÿงช Testing

# Test configuration
node scripts/test-setup.js

# Test database
psql kaernest_builder -c "SELECT COUNT(*) FROM users;"

# Test email
curl -X POST http://localhost:3001/auth/request-magic-link \
  -H "Content-Type: application/json" \
  -d '{"email":"your@email.com"}'

# Test health
curl http://localhost:3001/health

๐Ÿ“ˆ Monitoring

# PM2 monitoring
pm2 monit

# Database stats
psql kaernest_builder -c "
  SELECT 
    (SELECT COUNT(*) FROM users) as users,
    (SELECT COUNT(*) FROM characters) as characters,
    (SELECT COUNT(*) FROM sessions WHERE expires_at > NOW()) as active_sessions;
"

# Disk usage
du -sh /var/lib/postgresql/data

๐Ÿ†˜ Common Issues

"Cannot connect to database"

# Check PostgreSQL is running
sudo service postgresql status

# Check connection
psql "postgresql://user:pass@localhost/kaernest_builder"

"SendGrid error"

  • Verify API key is correct
  • Verify sender email is verified in SendGrid
  • Check quota (free = 100/day)

"CORS error"

  • FRONTEND_URL must match exactly (including https://)
  • Check browser console for actual origin

"Session not persisting"

  • Check secure: true only in production
  • Verify cookies enabled
  • Check browser dev tools โ†’ Application โ†’ Cookies

๐Ÿ“ Notes

  • Cost: $0 (SendGrid free tier, self-hosted DB)
  • Scale: Handles 1000s of users easily
  • Maintenance: Almost none (auto-cleanup runs hourly)
  • Data: Characters stored as JSONB (easy to query/update)

๐ŸŽ‰ You're Done!

You now have: - โœ… Passwordless auth - โœ… Character cloud save - โœ… 90-day sessions - โœ… Full API - โœ… Production-ready

Questions? Check README.md or open an issue!