The WhatsApp Assistant Bot uses PostgreSQL as its database and Drizzle ORM for type-safe database operations.
PostgreSQL Setup
Create PostgreSQL Database
Create a new PostgreSQL database for the bot. You can use a managed service like Neon, Supabase, or Railway, or set up your own PostgreSQL instance.# If using local PostgreSQL
createdb whatsapp_bot
Configure Database URL
Set your DATABASE_URL environment variable with the PostgreSQL connection string:DATABASE_URL="postgresql://username:password@host:port/database?sslmode=require"
The application requires SSL connections. Ensure your connection string includes sslmode=require or configure your PostgreSQL instance to accept SSL connections.
Run Database Migrations
Generate and apply database migrations using Drizzle Kit:# Generate migrations from schema
npx drizzle-kit generate
# Apply migrations to database
npx drizzle-kit push
Drizzle ORM Configuration
The Drizzle ORM configuration is defined in drizzle.config.ts:
import { defineConfig } from 'drizzle-kit';
import { config } from 'dotenv';
config();
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});
Configuration Options
- schema: Location of database schema definitions (
./src/db/schema.ts)
- out: Output directory for generated migrations (
./drizzle)
- dialect: Database type (
postgresql)
- verbose: Enable detailed logging during migrations
- strict: Enable strict mode for type checking
Database Schema
The bot uses five main tables to store user data and bot functionality:
Users Table
Stores user profiles and preferences.
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
userId: text('user_id').notNull().unique(),
lastActive: timestamp('last_active').defaultNow(),
notificationsEnabled: boolean('notifications_enabled').default(true),
timezone: text('timezone').default('UTC'),
spotifyRefreshToken: text('spotify_refresh_token'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
Fields:
id - UUID primary key (auto-generated)
userId - WhatsApp user ID (unique)
lastActive - Last activity timestamp
notificationsEnabled - Notification preferences
timezone - User timezone (default: UTC)
spotifyRefreshToken - Spotify integration token
createdAt / updatedAt - Audit timestamps
Todos Table
Manages user todo items.
export const todos = pgTable('todos', {
id: uuid('id').defaultRandom().primaryKey(),
userId: text('user_id').notNull(),
chatId: text('chat_id').notNull(),
task: text('task').notNull(),
completed: boolean('completed').default(false),
completedAt: timestamp('completed_at'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
Fields:
id - UUID primary key
userId - WhatsApp user ID
chatId - WhatsApp chat ID (group or individual)
task - Todo task description
completed - Completion status
completedAt - Completion timestamp
createdAt / updatedAt - Audit timestamps
Reminders Table
Stores scheduled reminders.
export const reminders = pgTable('reminders', {
id: uuid('id').defaultRandom().primaryKey(),
userId: text('user_id').notNull(),
task: text('task').notNull(),
time: timestamp('time').notNull(),
notifyUsers: text('notify_users').array().default([]),
isCompleted: boolean('is_completed').default(false),
createdAt: timestamp('created_at').defaultNow(),
});
Fields:
id - UUID primary key
userId - Creator’s WhatsApp user ID
task - Reminder message
time - Scheduled reminder time
notifyUsers - Array of user IDs to notify
isCompleted - Completion status
createdAt - Creation timestamp
Notes Table
Stores user notes with optional tags.
export const notes = pgTable('notes', {
id: uuid('id').defaultRandom().primaryKey(),
userId: text('user_id').notNull(),
content: text('content').notNull(),
tags: text('tags').array().default([]),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
Fields:
id - UUID primary key
userId - WhatsApp user ID
content - Note content
tags - Array of tags for organization
createdAt / updatedAt - Audit timestamps
Timers Table
Manages active countdown timers.
export const timers = pgTable('timers', {
id: uuid('id').defaultRandom().primaryKey(),
userId: text('user_id').notNull(),
duration: integer('duration').notNull(),
endTime: timestamp('end_time').notNull(),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow(),
});
Fields:
id - UUID primary key
userId - WhatsApp user ID
duration - Timer duration in seconds
endTime - Calculated end time
isActive - Timer status
createdAt - Creation timestamp
Database Connection
The database connection is configured in src/db/index.ts:
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(DATABASE_URL, {
ssl: 'require',
max: 10
});
export const db = drizzle(client, { schema });
Connection Options:
ssl: 'require' - Enforces SSL connections
max: 10 - Connection pool with maximum 10 connections
The application validates the DATABASE_URL on startup and exits if it’s not configured.
Type Safety
Drizzle ORM provides TypeScript types for all schema definitions:
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Todo = typeof todos.$inferSelect;
export type NewTodo = typeof todos.$inferInsert;
export type Reminder = typeof reminders.$inferSelect;
export type NewReminder = typeof reminders.$inferInsert;
export type Note = typeof notes.$inferSelect;
export type NewNote = typeof notes.$inferInsert;
export type Timer = typeof timers.$inferSelect;
export type NewTimer = typeof timers.$inferInsert;
These types are automatically inferred from the schema and provide compile-time type checking for all database operations.
Migration Workflow
Modify Schema
Update the schema definitions in src/db/schema.ts with your changes.
Generate Migration
This creates a new migration file in the ./drizzle directory. Review Migration
Check the generated SQL migration file to ensure it matches your intended changes.
Apply Migration
This applies the migration to your database.
Drizzle Kit supports both push-based (for development) and migration-based (for production) workflows. Use drizzle-kit push for rapid development and drizzle-kit migrate for production deployments.
Database Relationships
While the schema doesn’t use foreign key constraints, tables are related through user IDs:
- All tables reference
userId (WhatsApp user ID)
todos table also includes chatId for group chat support
reminders table includes an array of notifyUsers for multi-user notifications
The schema uses text-based user IDs from WhatsApp rather than foreign keys to maintain flexibility and avoid referential integrity issues with external user identifiers.