30-Minute Tutorial
Build a complete Task Management API with Flowfull in just 30 minutes! This tutorial will guide you through creating a production-ready backend with authentication, database, and caching.
What You'll Build
A Task Management API with:
- ✅ User authentication (via Pubflow)
- ✅ CRUD operations for tasks
- ✅ Protected routes
- ✅ Database integration
- ✅ Caching for performance
- ✅ Input validation
Prerequisites
- ✅ Bun installed (bun.sh)
- ✅ PostgreSQL running locally
- ✅ Pubflow account (pubflow.com)
- ✅ 30 minutes of your time ⏱️
Step 1: Set Up Pubflow (5 minutes)
1.1 Create Flowless Instance
- Visit pubflow.com
- Sign up or log in
- Click "Create Flowless Instance"
- Name it:
my-tasks-app - Copy your Bridge Secret
Your Flowless URL:
https://my-tasks-app.pubflow.com1.2 Create Test User
Use the Pubflow dashboard to create a test user:
- Email:
test@example.com - Password:
Test123!
Step 2: Clone and Configure (5 minutes)
2.1 Clone Template
We'll use flowfull-node, the official Node.js/TypeScript starter kit:
# Clone the official starter kit
git clone https://github.com/pubflow/flowfull-node.git task-api
cd task-api
bun installTIP
This tutorial uses flowfull-node (Node.js/TypeScript), the official starter kit. Community starter kits for Go, Python, and Rust are coming soon!
2.2 Configure Environment
Create .env:
# Flowless Configuration
FLOWLESS_URL=https://my-tasks-app.pubflow.com
BRIDGE_SECRET=your-bridge-secret-here
# Database
DATABASE_TYPE=postgresql
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/tasks_db
# Server
PORT=3000
NODE_ENV=development
# Security
VALIDATION_MODE=STANDARD
# Cache (Optional)
CACHE_ENABLED=true2.3 Create Database
# Create database
createdb tasks_db
# Run migrations
bun run migrateStep 3: Create Task Model (5 minutes)
3.1 Create Migration
Create migrations/002_create_tasks.ts:
import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('tasks')
.addColumn('id', 'varchar(255)', (col) => col.primaryKey())
.addColumn('user_id', 'varchar(255)', (col) => col.notNull())
.addColumn('title', 'varchar(255)', (col) => col.notNull())
.addColumn('description', 'text')
.addColumn('status', 'varchar(50)', (col) => col.notNull().defaultTo('pending'))
.addColumn('priority', 'varchar(50)', (col) => col.notNull().defaultTo('medium'))
.addColumn('due_date', 'timestamp')
.addColumn('created_at', 'timestamp', (col) => col.notNull())
.addColumn('updated_at', 'timestamp', (col) => col.notNull())
.execute();
// Create index on user_id
await db.schema
.createIndex('tasks_user_id_idx')
.on('tasks')
.column('user_id')
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('tasks').execute();
}Run migration:
bun run migrate3.2 Create Task Types
Create src/types/task.ts:
export interface Task {
id: string;
user_id: string;
title: string;
description?: string;
status: 'pending' | 'in_progress' | 'completed';
priority: 'low' | 'medium' | 'high';
due_date?: string;
created_at: string;
updated_at: string;
}
export interface CreateTaskInput {
title: string;
description?: string;
priority?: 'low' | 'medium' | 'high';
due_date?: string;
}
export interface UpdateTaskInput {
title?: string;
description?: string;
status?: 'pending' | 'in_progress' | 'completed';
priority?: 'low' | 'medium' | 'high';
due_date?: string;
}Step 4: Create Task Routes (10 minutes)
Create src/routes/tasks.ts:
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { nanoid } from 'nanoid';
import { requireAuth } from '../lib/auth/middleware';
import { db } from '../lib/database';
const app = new Hono();
// Validation schemas
const createTaskSchema = z.object({
title: z.string().min(1).max(255),
description: z.string().optional(),
priority: z.enum(['low', 'medium', 'high']).optional(),
due_date: z.string().datetime().optional()
});
const updateTaskSchema = z.object({
title: z.string().min(1).max(255).optional(),
description: z.string().optional(),
status: z.enum(['pending', 'in_progress', 'completed']).optional(),
priority: z.enum(['low', 'medium', 'high']).optional(),
due_date: z.string().datetime().optional()
});
// Get all tasks
app.get('/', requireAuth(), async (c) => {
const userId = c.get('user_id');
const tasks = await db
.selectFrom('tasks')
.where('user_id', '=', userId)
.selectAll()
.orderBy('created_at', 'desc')
.execute();
return c.json({ tasks });
});
// Get single task
app.get('/:id', requireAuth(), async (c) => {
const userId = c.get('user_id');
const taskId = c.req.param('id');
const task = await db
.selectFrom('tasks')
.where('id', '=', taskId)
.where('user_id', '=', userId)
.selectAll()
.executeTakeFirst();
if (!task) {
return c.json({ error: 'Task not found' }, 404);
}
return c.json({ task });
});
// Create task
app.post('/', requireAuth(), zValidator('json', createTaskSchema), async (c) => {
const userId = c.get('user_id');
const data = c.req.valid('json');
const task = await db
.insertInto('tasks')
.values({
id: nanoid(),
user_id: userId,
title: data.title,
description: data.description,
priority: data.priority || 'medium',
status: 'pending',
due_date: data.due_date,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.returningAll()
.executeTakeFirst();
return c.json({ task }, 201);
});
// Update task
app.patch('/:id', requireAuth(), zValidator('json', updateTaskSchema), async (c) => {
const userId = c.get('user_id');
const taskId = c.req.param('id');
const data = c.req.valid('json');
const task = await db
.updateTable('tasks')
.set({
...data,
updated_at: new Date().toISOString()
})
.where('id', '=', taskId)
.where('user_id', '=', userId)
.returningAll()
.executeTakeFirst();
if (!task) {
return c.json({ error: 'Task not found' }, 404);
}
return c.json({ task });
});
// Delete task
app.delete('/:id', requireAuth(), async (c) => {
const userId = c.get('user_id');
const taskId = c.req.param('id');
const result = await db
.deleteFrom('tasks')
.where('id', '=', taskId)
.where('user_id', '=', userId)
.executeTakeFirst();
if (result.numDeletedRows === 0n) {
return c.json({ error: 'Task not found' }, 404);
}
return c.json({ success: true });
});
export default app;4.1 Register Routes
Update src/index.ts:
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import tasksRoutes from './routes/tasks';
const app = new Hono();
app.use('/*', cors());
// Register routes
app.route('/api/tasks', tasksRoutes);
export default app;Step 5: Test Your API (5 minutes)
5.1 Start Server
bun run devServer running at http://localhost:3000
5.2 Login to Get Session
curl -X POST https://my-tasks-app.pubflow.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "Test123!"
}'Response:
{
"session_id": "abc123...",
"user": { "id": "user_123", "email": "test@example.com" }
}5.3 Create a Task
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-H "X-Session-Id: abc123..." \
-d '{
"title": "Build amazing app",
"description": "Using Flowfull and Pubflow",
"priority": "high",
"due_date": "2024-12-31T23:59:59Z"
}'Response:
{
"task": {
"id": "task_abc123",
"user_id": "user_123",
"title": "Build amazing app",
"description": "Using Flowfull and Pubflow",
"status": "pending",
"priority": "high",
"due_date": "2024-12-31T23:59:59Z",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
}5.4 Get All Tasks
curl http://localhost:3000/api/tasks \
-H "X-Session-Id: abc123..."5.5 Update Task
curl -X PATCH http://localhost:3000/api/tasks/task_abc123 \
-H "Content-Type: application/json" \
-H "X-Session-Id: abc123..." \
-d '{
"status": "in_progress"
}'5.6 Delete Task
curl -X DELETE http://localhost:3000/api/tasks/task_abc123 \
-H "X-Session-Id: abc123..."🎉 Congratulations!
You've built a complete Task Management API in 30 minutes!
What You've Learned
✅ How to connect to Pubflow for authentication ✅ How to create protected routes ✅ How to use Bridge Validation ✅ How to implement CRUD operations ✅ How to validate input with Zod ✅ How to use Kysely ORM
Next Steps
Add More Features
- Add Caching - Make it 50x faster
- Add Trust Tokens - Email verification
- Deploy to Production - Go live!
Learn More Concepts
- Validation Modes - Enhance security
- Multi-Database - Switch databases
- Environment Config - Advanced configuration
Build Something Bigger
- Add task categories and tags
- Implement task sharing between users
- Add file attachments to tasks
- Create task templates
- Build a frontend with React/Next.js
Need Help?
- 📖 Documentation - Full documentation
- 🌐 Pubflow - Official website
- 📧 Email: support@pubflow.com
Need Professional Support?
If you need assistance, professional support, or want us to build your backend for you:
- 🌐 Notside.com - Technology firm specializing in Pubflow implementations
- 📧 Email: contact@notside.com
- 💼 Services: Custom backend development, consulting, and enterprise support
Ready to build more?
Explore Guides →