This guide will help you create a complete backend in less than 30 minutes using Flowfull's core concepts.
- Prerequisites
- Initial Setup
- Database Configuration
- Flowless Configuration
- Create Protected Routes
- Implement Cache
- Testing
- Deployment
- Bun v1.0+ (Install)
- Database (PostgreSQL, MySQL, or LibSQL/Turso)
- Flowless backend instance (for authentication)
- Git (optional)
- Basic TypeScript
- REST APIs
- Basic SQL
- Environment variables
# Option 1: Copy directly
cp -r 2/flowfull my-new-backend
cd my-new-backend
# Option 2: Use as Git template
git clone <flowfull-repo> my-new-backend
cd my-new-backend
rm -rf .git
git initbun install# Copy template
cp .env.example .env
# Edit with your values
nano .env # or your preferred editorMinimum required values:
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# Flowless Integration
FLOWLESS_API_URL=http://localhost:3000
BRIDGE_VALIDATION_SECRET=your-super-secret-key-min-32-chars
# Server
PORT=3001
NODE_ENV=developmentbun run validate-configIf everything is correct, you'll see:
✅ Configuration validated successfully
1. Install PostgreSQL:
# macOS
brew install postgresql@15
brew services start postgresql@15
# Ubuntu/Debian
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql2. Create Database:
createdb myapp_db3. Configure .env:
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp_db
DATABASE_TYPE=postgresql
DATABASE_SSL=false
DATABASE_POOL_MIN=2
DATABASE_POOL_MAX=101. Create Turso account:
# Install Turso CLI
curl -sSfL https://get.tur.so/install.sh | bash
# Login
turso auth login
# Create database
turso db create myapp-db2. Get credentials:
turso db show myapp-db3. Configure .env:
DATABASE_URL=libsql://myapp-db-username.turso.io
LIBSQL_AUTH_TOKEN=your_auth_token_here
DATABASE_TYPE=libsql1. Install MySQL:
# macOS
brew install mysql
brew services start mysql
# Ubuntu/Debian
sudo apt install mysql-server
sudo systemctl start mysql2. Create Database:
mysql -u root -p
CREATE DATABASE myapp_db;3. Configure .env:
DATABASE_URL=mysql://root:password@localhost:3306/myapp_db
DATABASE_TYPE=mysqlFlowfull does NOT include migrations by default. You must create your own tables according to your application.
Basic example:
-- users table (if not using Flowless for users)
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- items table (example)
CREATE TABLE items (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);Flowless is the central authentication server that handles:
- User registration
- Login/Logout
- Session management
- Session validation (Bridge Validation)
Option 1: Use existing Flowless
If you already have a Flowless server running:
FLOWLESS_API_URL=https://your-flowless-instance.com
BRIDGE_VALIDATION_SECRET=shared-secret-between-flowless-and-flowfullOption 2: Run local Flowless
# In another terminal
cd ../flowless
bun install
cp .env.example .env
# Configure Flowless .env
bun run devIMPORTANT: The BRIDGE_VALIDATION_SECRET must be the same in both Flowless and Flowfull.
In Flowless (.env):
BRIDGE_VALIDATION_SECRET=my-super-secret-key-min-32-characters-longIn Flowfull (.env):
BRIDGE_VALIDATION_SECRET=my-super-secret-key-min-32-characters-long# Start Flowfull
bun run dev
# In another terminal, test health check
curl http://localhost:3001/healthYou should see:
{
"status": "healthy",
"timestamp": "2024-01-01T00:00:00.000Z",
"uptime": 123
}Location: src/routes/
src/routes/
├── api.ts # Main API routes
├── health.ts # Health check (already included)
└── items.ts # Example: Items CRUD
Create file: src/routes/items.ts
import { Hono } from 'hono';
import { requireAuth, optionalAuth } from '../lib/auth/middleware';
import { db } from '../config/database';
import { z } from 'zod';
import { nanoid } from 'nanoid';
const items = new Hono();
// Validation schemas
const createItemSchema = z.object({
title: z.string().min(1).max(255),
description: z.string().optional()
});
// GET /items - List items (optional auth)
items.get('/', optionalAuth(), async (c) => {
const isGuest = c.get('is_guest');
const userId = c.get('user_id');
let query = db.selectFrom('items').selectAll();
if (!isGuest && userId) {
// Authenticated: show user's items
query = query.where('user_id', '=', userId);
} else {
// Guest: show public items only
query = query.where('is_public', '=', true);
}
const items = await query.execute();
return c.json({
success: true,
items,
count: items.length
});
});
// POST /items - Create item (requires auth)
items.post('/', requireAuth(), async (c) => {
const userId = c.get('user_id');
const body = await c.req.json();
// Validate input
const validation = createItemSchema.safeParse(body);
if (!validation.success) {
return c.json({
success: false,
error: 'Validation failed',
details: validation.error.errors
}, 400);
}
const { title, description } = validation.data;
// Create item
const newItem = {
id: nanoid(),
user_id: userId,
title,
description: description || null,
is_public: false,
created_at: new Date().toISOString()
};
await db.insertInto('items').values(newItem).execute();
return c.json({
success: true,
item: newItem
}, 201);
});
// PUT /items/:id - Update item (requires auth + ownership)
items.put('/:id', requireAuth(), async (c) => {
const id = c.req.param('id');
const userId = c.get('user_id');
// Check ownership
const item = await db
.selectFrom('items')
.select('user_id')
.where('id', '=', id)
.executeTakeFirst();
if (!item) {
return c.json({ success: false, error: 'Item not found' }, 404);
}
if (item.user_id !== userId) {
return c.json({ success: false, error: 'Access denied' }, 403);
}
// Update item
const updated = await db
.updateTable('items')
.set({ title: 'Updated' })
.where('id', '=', id)
.returningAll()
.executeTakeFirst();
return c.json({ success: true, item: updated });
});
export default items;Edit: src/index.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { config } from './config/environment';
import health from './routes/health';
import items from './routes/items'; // ← Import
const app = new Hono();
// CORS
app.use('/*', cors({
origin: config.CORS_ORIGINS,
credentials: true
}));
// Routes
app.route('/health', health);
app.route('/api/v1/items', items); // ← Register
export default {
port: config.PORT,
fetch: app.fetch
};# 1. Create item (requires authentication)
curl -X POST http://localhost:3001/api/v1/items \
-H "Content-Type: application/json" \
-H "X-Session-ID: your-session-id" \
-d '{"title": "My First Item", "description": "Test item"}'
# 2. List items
curl http://localhost:3001/api/v1/items \
-H "X-Session-ID: your-session-id"
# 3. Get specific item
curl http://localhost:3001/api/v1/items/item-id
# 4. Update item
curl -X PUT http://localhost:3001/api/v1/items/item-id \
-H "Content-Type: application/json" \
-H "X-Session-ID: your-session-id" \
-d '{"title": "Updated Title"}'Flowfull already includes LRU cache for sessions in src/lib/auth/bridge-validator.ts.
Current configuration:
const sessionCache = new LRUCache<string, SessionData>({
max: 10000, // 10k sessions
ttl: 5 * 60 * 1000 // 5 minutes
});HybridCache is NOT implemented in Flowfull, but you can copy it from pubflow-flowfull.
1. Copy files:
# From pubflow-flowfull
cp ../pubflow-flowfull/src/lib/cache/hybrid-cache.ts src/lib/cache/
cp ../pubflow-flowfull/src/lib/cache/cache-instances.ts src/lib/cache/2. Install dependencies:
bun add ioredis lru-cache3. Configure Redis (.env):
CACHE_ENABLED=true
REDIS_URL=redis://localhost:63794. Use HybridCache:
import { HybridCache } from './lib/cache/hybrid-cache';
const userCache = new HybridCache<UserData>({
cacheType: 'userContext',
ttl: 300,
maxSize: 10000,
keyPrefix: 'user_ctx'
});
// Get from cache
const user = await userCache.get(userId);
// Set in cache
await userCache.set(userId, userData, 300);1. Install dependencies:
bun add -d @types/bun2. Create test file: src/routes/items.test.ts
import { describe, test, expect, beforeAll } from 'bun:test';
import app from '../index';
describe('Items API', () => {
let sessionId: string;
let itemId: string;
beforeAll(async () => {
// Get session from Flowless (mock or real)
sessionId = 'test-session-id';
});
test('POST /api/v1/items - Create item', async () => {
const res = await app.request('/api/v1/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify({
title: 'Test Item',
description: 'Test description'
})
});
expect(res.status).toBe(201);
const data = await res.json();
expect(data.success).toBe(true);
expect(data.item.title).toBe('Test Item');
itemId = data.item.id;
});
test('GET /api/v1/items - List items', async () => {
const res = await app.request('/api/v1/items', {
headers: {
'X-Session-ID': sessionId
}
});
expect(res.status).toBe(200);
const data = await res.json();
expect(data.success).toBe(true);
expect(Array.isArray(data.items)).toBe(true);
});
});3. Run tests:
bun test1. Prepare server:
# SSH to server
ssh user@your-server.com
# Install Bun
curl -fsSL https://bun.sh/install | bash
# Install PostgreSQL (if using)
sudo apt update
sudo apt install postgresql2. Clone project:
git clone your-repo.git
cd your-repo
bun install3. Configure environment:
nano .env
# Configure production values4. Build and run:
bun run build
bun run start5. Use PM2 to keep running:
npm install -g pm2
pm2 start "bun run start" --name flowfull-api
pm2 save
pm2 startupCreate: Dockerfile
FROM oven/bun:1 as base
WORKDIR /app
# Install dependencies
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# Copy source
COPY . .
# Build
RUN bun run build
# Expose port
EXPOSE 3001
# Start
CMD ["bun", "run", "start"]Create: docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "3001:3001"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- FLOWLESS_API_URL=http://flowless:3000
- BRIDGE_VALIDATION_SECRET=${BRIDGE_VALIDATION_SECRET}
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:Run:
docker-compose up -dBefore going to production, verify:
- Environment variables configured correctly
- Database with tables created
- Flowless connected and working
- Bridge secret shared between Flowless and Flowfull
- CORS configured with correct origins
- Rate limiting enabled
- Validation mode configured (STANDARD or ADVANCED)
- Logs configured
- Health check working
- Tests passing
- SSL/HTTPS configured (production)
- Database backups configured
- Core Concepts: See
docs/CORE-CONCEPTS-EN.md - Flowfull Repository:
2/flowfull/ - Pubflow-Flowfull:
2/pubflow-flowfull/(complete implementation)
Cause: Secret key doesn't match between Flowless and Flowfull
Solution:
# Verify they are equal
# In Flowless .env
echo $BRIDGE_VALIDATION_SECRET
# In Flowfull .env
echo $BRIDGE_VALIDATION_SECRETCause: Incorrect DATABASE_URL or database not accessible
Solution:
# Test connection manually
psql $DATABASE_URL
# Verify server is running
sudo systemctl status postgresqlCause: Flowless not responding or slow
Solution:
# Increase timeout in .env
BRIDGE_VALIDATION_TIMEOUT=10000
BRIDGE_RETRY_ATTEMPTS=5Now you have a complete backend with:
✅ Authentication with Bridge Validation ✅ Protected routes with middleware ✅ Input validation with Zod ✅ Session cache ✅ Multi-database support ✅ Environment configuration ✅ Testing setup ✅ Deployment ready
Total time: ~30 minutes 🚀