Appearance
Database Setup
This guide covers database configuration, migrations, and management for Jubiloop.
Overview
Jubiloop uses PostgreSQL as its primary database with Lucid ORM (built into AdonisJS) for database interactions.
Initial Setup
1. Database Creation
The database is automatically created when you start the local infrastructure:
bash
cd infra/local_dev
docker compose up -dThis creates a PostgreSQL instance with:
- Database name:
jubiloop_dev_db - Username:
postgres - Password:
postgres - Port:
5433
2. Environment Configuration
Configure your database connection in apps/server/.env:
ini
DB_CONNECTION=pg
DB_HOST=localhost
DB_PORT=5433
DB_USER=postgres
DB_PASSWORD=postgres
DB_DATABASE=jubiloop_dev_db3. Run Migrations
bash
# From project root
npm run migration:run
# Or directly from server
cd apps/server
node ace migration:run4. Seed Database (Optional)
bash
# From project root
npm run db:seed
# Or directly from server
cd apps/server
node ace db:seedDatabase Schema
Core Tables
Users
- Stores user accounts and authentication data
- Includes profile information
- Handles authentication tokens
Events
- Main event data
- Event details and settings
- Links to organizers and participants
Organizations
- Company/organization accounts
- Can have multiple users
- Event ownership
Relationships
Users ←→ Organizations (many-to-many)
Organizations → Events (one-to-many)
Users ←→ Events (many-to-many through participants)Migrations
Creating Migrations
bash
cd apps/server
# Create a new migration
node ace make:migration create_table_name
# Create with specific action
node ace make:migration add_column_to_users --table=usersMigration Structure
typescript
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
protected tableName = 'users'
public async up() {
this.schema.createTable(this.tableName, (table) => {
table.uuid('id').primary()
table.string('email', 255).notNullable().unique()
table.string('password', 180).notNullable()
table.timestamp('created_at').notNullable()
table.timestamp('updated_at').nullable()
})
}
public async down() {
this.schema.dropTable(this.tableName)
}
}Running Migrations
bash
# Run all pending migrations
node ace migration:run
# Rollback last batch
node ace migration:rollback
# Rollback all
node ace migration:rollback --batch=0
# Check migration status
node ace migration:status
# Dry run (see SQL without executing)
node ace migration:run --dry-runModels
Creating Models
bash
cd apps/server
# Create a model
node ace make:model User
# Create model with migration
node ace make:model Event -m
# Create model with controller
node ace make:model Organization -cModel Example
typescript
import { DateTime } from 'luxon'
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
import Event from './event.js'
export default class User extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare email: string
@column({ serializeAs: null })
declare password: string
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
@hasMany(() => Event)
declare events: HasMany<typeof Event>
}Database Operations
Query Examples
typescript
// Find by ID
const user = await User.find(1)
// Find by attributes
const user = await User.findBy('email', 'user@example.com')
// Create
const user = await User.create({
email: 'new@example.com',
password: 'hashed_password',
})
// Update
user.name = 'Updated Name'
await user.save()
// Delete
await user.delete()
// Complex queries
const activeEvents = await Event.query()
.where('status', 'active')
.where('start_date', '>', DateTime.now().toSQL())
.preload('organizer')
.orderBy('start_date', 'asc')
.limit(10)Relationships
typescript
// Eager loading
const user = await User.query().preload('events').where('id', 1).first()
// Lazy loading
const user = await User.find(1)
await user.load('events')
// Creating with relationships
const event = await user.related('events').create({
title: 'New Event',
date: DateTime.now(),
})Database Management
Accessing Database
Via Docker:
bash
docker exec -it jubiloop_local_dev_postgres psql -U postgres -d jubiloop_dev_dbVia GUI Tools:
- Host:
localhost - Port:
5433 - Username:
postgres - Password:
postgres - Database:
jubiloop_dev_db
Common SQL Commands
sql
-- List all tables
\dt
-- Describe table structure
\d users
-- View table data
SELECT * FROM users;
-- Check migration status
SELECT * FROM adonis_schema;
-- Database size
SELECT pg_database_size('jubiloop_dev_db');Backup and Restore
Backup:
bash
docker exec jubiloop_local_dev_postgres pg_dump -U postgres jubiloop_dev_db > backup.sqlRestore:
bash
docker exec -i jubiloop_local_dev_postgres psql -U postgres jubiloop_dev_db < backup.sqlEnvironment-Specific Configuration
Development
- Uses Docker PostgreSQL
- Port 5433 to avoid conflicts
- Migrations run on startup
- Sample data via seeders
Production
- Managed PostgreSQL on DigitalOcean
- Automated backups
- Connection pooling
- SSL required
Testing
- Separate test database
- Transactions for test isolation
- Automatic cleanup
Best Practices
Schema Design
- Use UUID v7 for primary keys - Better database performance with timestamp-based ordering
- Add indexes for frequently queried columns
- Use foreign key constraints
- Add NOT NULL constraints where appropriate
- Use appropriate column types
UUID v7 Implementation
Jubiloop uses UUID v7 instead of UUID v4 for improved database performance:
- Location:
apps/server/app/models/base_model_with_uuid.ts - Package: Official
uuidnpm package (v7 function) - Benefits:
- Timestamp-based ordering improves index performance
- Better locality of reference in B-tree indexes
- Maintains uniqueness while providing chronological sorting
- Reduces index fragmentation compared to random UUID v4
All models that need UUID primary keys extend BaseModelWithUuid which automatically assigns UUID v7 values before creation:
typescript
import BaseModelWithUuid from '#models/base_model_with_uuid'
export default class Event extends BaseModelWithUuid {
// UUID v7 will be automatically assigned on creation
@column({ isPrimary: true })
declare id: string
}Performance
- Use eager loading to avoid N+1 queries
- Add database indexes for search columns
- Use query scopes for complex queries
- Monitor slow queries
- Use connection pooling in production
Security
- Never store plain text passwords
- Use parameterized queries (ORM handles this)
- Validate input before database operations
- Use database roles with minimal permissions
- Enable SSL for production connections
Troubleshooting
Connection Refused
Check if PostgreSQL is running:
bashdocker compose psVerify connection settings in
.envCheck PostgreSQL logs:
bashdocker compose logs postgres
Migration Errors
- Check migration syntax
- Verify database connection
- Look for dependency issues
- Use
--dry-runto preview SQL
Performance Issues
- Check for missing indexes
- Analyze query execution plans
- Monitor connection pool usage
- Review eager loading strategy