Blog Engine Example
A comprehensive blog application built with CQL that demonstrates real-world usage patterns, performance monitoring, and best practices for building scalable Crystal applications.
🎯 What You'll Learn
This example teaches you how to:
Configure CQL for different environments
Design database schemas with relationships
Implement migrations for schema evolution
Build Active Record models with associations
Perform CRUD operations efficiently
Write complex queries with joins and aggregations
Monitor performance and detect N+1 queries
Optimize database operations for production
🏗️ Architecture Overview
📊 Database Schema
The blog engine implements a complete content management system with the following entities:
🚀 Getting Started
Prerequisites
Crystal 1.0+ installed
Basic understanding of Crystal syntax
Familiarity with database concepts
Quick Start
Clone and navigate to the example:
cd examples/blog
Run the complete demo:
crystal blog_demo.cr
Explore the output to see CQL in action!
📁 Project Structure
examples/blog/
├── blog_demo.cr # Main demo runner
├── README.md # This documentation
├── models/ # Active Record models
│ ├── user.cr # User model with relationships
│ ├── category.cr # Category model
│ ├── post.cr # Post model with content
│ └── comment.cr # Comment model
├── migrations/ # Database migrations
│ ├── 001_create_users.cr
│ ├── 002_create_categories.cr
│ ├── 003_create_posts.cr
│ └── 004_create_comments.cr
├── demos/ # Feature demonstrations
│ ├── crud_operations.cr # Basic CRUD operations
│ ├── complex_queries.cr # Advanced queries
│ ├── relationships.cr # Model associations
│ ├── performance_features.cr # Optimization features
│ └── statistics.cr # Reporting and analytics
└── seeders.cr # Sample data creation
⚙️ Configuration
The blog engine demonstrates comprehensive CQL configuration:
CQL.configure do |config|
# Database connection
config.database_url = "sqlite3://examples/blog/blog.db"
config.logger = Log.for("cql.*")
config.environment = "development"
# SQLite-specific optimizations
config.sqlite.journal_mode = "wal"
config.sqlite.foreign_keys = true
# Schema management
config.migration_table_name = :cql_schema_migrations
config.schema_path = "./examples/blog/schemas"
config.schema_file_name = "app_schema.cr"
config.schema_constant_name = :BlogDB
config.schema_symbol = :app_schema
# Development features
config.auto_load_models = true
config.enable_auto_schema_sync = true
config.bootstrap_on_startup = true
config.verify_schema_on_startup = true
# Performance monitoring
config.enable_performance_monitoring = true
end
🗄️ Database Schema Definition
Users Table
class CreateUsers < CQL::Migration(1)
def up
schema.table :users do
primary :id, Int64, auto_increment: true
column :username, String, null: false
column :email, String, null: false
column :first_name, String, null: true
column :last_name, String, null: true
column :active, Bool, default: true
timestamps
index [:email], unique: true
index [:username], unique: true
end
schema.users.create!
end
end
Categories Table
class CreateCategories < CQL::Migration(2)
def up
schema.table :categories do
primary :id, Int64, auto_increment: true
column :name, String, null: false
column :slug, String, null: false
timestamps
index [:slug], unique: true
end
schema.categories.create!
end
end
Posts Table
class CreatePosts < CQL::Migration(3)
def up
schema.table :posts do
primary :id, Int64, auto_increment: true
column :title, String, null: false
column :content, String, null: false
column :published, Bool, default: false
column :views_count, Int64, default: 0
column :user_id, Int64, null: false
column :category_id, Int64, null: true
timestamps
foreign_key [:user_id], references: :users, references_columns: [:id]
foreign_key [:category_id], references: :categories, references_columns: [:id]
index [:published]
index [:user_id]
index [:category_id]
end
schema.posts.create!
end
end
Comments Table
class CreateComments < CQL::Migration(4)
def up
schema.table :comments do
primary :id, Int64, auto_increment: true
column :content, String, null: false
column :post_id, Int64, null: false
column :user_id, Int64, null: true
timestamps
foreign_key [:post_id], references: :posts, references_columns: [:id]
foreign_key [:user_id], references: :users, references_columns: [:id]
index [:post_id]
index [:user_id]
end
schema.comments.create!
end
end
🏗️ Active Record Models
User Model
struct User
include CQL::ActiveRecord::Model(Int64)
db_context BlogDB, :users
getter id : Int64?
getter username : String
getter email : String
getter first_name : String?
getter last_name : String?
getter? active : Bool = true
getter created_at : Time?
getter updated_at : Time?
# Relationships
has_many :posts, Post, foreign_key: :user_id
has_many :comments, Comment, foreign_key: :user_id
def initialize(@username : String, @email : String,
@first_name : String? = nil, @last_name : String? = nil,
@active : Bool = true, @created_at : Time? = nil,
@updated_at : Time? = nil)
end
# Custom methods
def full_name
if first_name && last_name
"#{first_name} #{last_name}"
elsif first_name
first_name.not_nil!
else
username
end
end
end
Post Model
struct Post
include CQL::ActiveRecord::Model(Int64)
db_context BlogDB, :posts
getter id : Int64?
getter title : String
getter content : String
getter? published : Bool = false
property views_count : Int64
getter user_id : Int64
getter category_id : Int64?
getter created_at : Time?
getter updated_at : Time?
# Relationships
belongs_to :user, User, foreign_key: :user_id
belongs_to :category, Category, foreign_key: :category_id, optional: true
has_many :comments, Comment, foreign_key: :post_id
def initialize(@title : String, @content : String, @user_id : Int64,
@published : Bool = false, @views_count : Int64 = 0,
@category_id : Int64? = nil, @created_at : Time? = nil,
@updated_at : Time? = nil)
end
# Custom methods
def published?
published
end
def word_count
content.split.size
end
end
🔧 CRUD Operations
Creating Records
# Create a new user
john = User.create!(
username: "john_doe",
email: "john@example.com",
first_name: "John",
last_name: "Doe"
)
# Create a post with relationships
crystal_post = Post.create!(
title: "Getting Started with Crystal",
content: "Crystal is a powerful programming language...",
user_id: john.id.not_nil!,
category_id: tech_cat.id,
published: true,
views_count: 150_i64
)
Reading Records
# Find by ID
user = User.find(1)
# Find by attributes
admin_user = User.find_by(username: "john_doe")
# Query with conditions
published_posts = Post.where(published: true).all(Post)
# Complex queries
most_viewed_posts = Post
.where(published: true)
.order(views_count: :desc)
.limit(5)
.all(Post)
Updating Records
# Update attributes
post = Post.find(1)
post.views_count += 1
post.save!
# Update with conditions
Post.where(published: false)
.update_all(published: true)
Deleting Records
# Delete a single record
post = Post.find(1)
post.destroy!
# Delete with conditions
Post.where(published: false).delete_all
🔗 Relationship Navigation
One-to-Many Relationships
# User -> Posts
user = User.find(1)
user_posts = user.posts.where(published: true).all(Post)
# Post -> Comments
post = Post.find(1)
post_comments = post.comments.all(Comment)
Many-to-One Relationships
# Post -> User (author)
post = Post.find(1)
author = post.user
# Comment -> User (commenter)
comment = Comment.find(1)
commenter = comment.user
Many-to-Many Through Relationships
# Users who commented on a post
post = Post.find(1)
commenters = post.comments.map(&.user).compact
# Posts by category
category = Category.find(1)
category_posts = category.posts.where(published: true).all(Post)
🔍 Complex Queries
Joins and Aggregations
# Posts with author information
published_posts = Post
.where(published: true)
.order(views_count: :desc)
.all(Post)
published_posts.each do |post|
author = post.user
puts "#{post.title} by #{author.full_name}"
end
# Aggregation queries
total_views = Post.where(published: true).sum(:views_count)
avg_views = Post.where(published: true).avg(:views_count)
# Posts by category
Category.all.each do |category|
post_count = category.posts.where(published: true).count
puts "#{category.name}: #{post_count} posts"
end
Raw SQL Queries
# Complex join query
schema.exec(<<-SQL)
SELECT u.username, p.title, c.content
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
LEFT JOIN comments c ON p.id = c.post_id
WHERE p.published = 1
ORDER BY p.views_count DESC, c.created_at DESC
SQL
⚡ Performance Features
Batch Processing
# Process posts in batches
post_count = 0
Post.find_each(batch_size: 10) do |post|
post_count += 1
# Process each post
end
Efficient Data Extraction
# Pluck specific columns
post_titles = Post.where(published: true).pluck(:title, as: String)
user_emails = User.pluck(:email)
# Count operations
total_posts = Post.count
published_count = Post.where(published: true).count
Performance Monitoring
The blog engine includes comprehensive performance monitoring:
════════════════════════════════════════════════════════════════════════════════
🚀 CQL PERFORMANCE MONITORING REPORT
════════════════════════════════════════════════════════════════════════════════
📊 OVERVIEW
────────────────────
⏰ Generated: 2025-06-26 19:32:31 UTC
⚡ Uptime: 00:00:01.008859000
🔍 Monitoring: true
✅ EXCELLENT PERFORMANCE!
──────────────────────────────
🎉 No performance issues detected
🚀 Your application is running smoothly
💪 Keep up the great work!
════════════════════════════════════════════════════════════════════════════════
🔧 CQL Performance Monitoring • 19:32:31
════════════════════════════════════════════════════════════════════════════════
📊 Statistics and Reporting
Blog Analytics
# Basic statistics
puts "Total Users: #{User.count}"
puts "Total Posts: #{Post.count}"
puts "Published Posts: #{Post.where(published: true).count}"
puts "Total Comments: #{Comment.count}"
# Content analysis
avg_word_count = Post.all.sum(&.word_count) / Post.count
most_active_author = User.all.max_by(&.posts.count)
Performance Reports
The demo generates comprehensive performance reports including:
Query Performance Metrics - Execution times and frequency
N+1 Query Detection - Identified inefficient query patterns
Slow Query Analysis - Queries exceeding performance thresholds
Query Pattern Analysis - Understanding application query behavior
Performance Recommendations - Automated optimization suggestions
🎯 Learning Path
Beginner Level
Start with Configuration - Understand how to set up CQL
Explore Models - Learn Active Record model definitions
Practice CRUD - Master basic database operations
Understand Relationships - Learn model associations
Intermediate Level
Study Migrations - Learn schema evolution patterns
Master Complex Queries - Advanced query techniques
Optimize Performance - Batch processing and efficient queries
Monitor Performance - Use performance monitoring tools
Advanced Level
Custom Validations - Add business logic to models
Advanced Relationships - Complex association patterns
Raw SQL Integration - When to use direct SQL
Production Optimization - Performance tuning for scale
🔧 Customization Ideas
Add New Features
User Authentication - Add password fields and authentication
Post Tags - Implement tagging system
User Roles - Add role-based access control
Post Drafts - Enhanced draft management
Comment Moderation - Comment approval workflow
Performance Enhancements
Caching Layer - Add Redis caching for popular posts
Search Functionality - Full-text search implementation
Pagination - Efficient pagination for large datasets
Background Jobs - Async processing for heavy operations
Monitoring Extensions
Custom Metrics - Business-specific performance metrics
Alerting - Performance threshold alerts
Dashboard - Real-time performance dashboard
Logging - Enhanced query logging and analysis
🚀 Production Considerations
Database Optimization
Index Strategy - Optimize indexes for query patterns
Connection Pooling - Configure appropriate pool sizes
Query Optimization - Monitor and optimize slow queries
Schema Design - Plan for future growth
Security
Input Validation - Validate all user inputs
SQL Injection Prevention - Use parameterized queries
Access Control - Implement proper authorization
Data Encryption - Encrypt sensitive data
Monitoring
Performance Monitoring - Track query performance
Error Tracking - Monitor and alert on errors
Health Checks - Database connectivity monitoring
Metrics Collection - Business and technical metrics
📚 Additional Resources
CQL Configuration Guide - Detailed configuration options
Active Record Guide - Complete Active Record documentation
Performance Guide - Performance optimization techniques
Migration Guide - Database migration best practices
🏁 Next Steps
Ready to build your own application? Start by:
Fork this example and customize it for your needs
Explore the CQL documentation for advanced features
Join the community to get help and share your projects
Contribute back by improving this example or CQL itself
Happy coding with CQL! 🚀
Last updated
Was this helpful?