Quick Start
This guide will walk you through creating your first ScyllinX application. We'll build a simple blog system with users, posts, and comments to demonstrate the core features.
Prerequisites
Make sure you have completed the Installation guide and have:
- ScyllinX installed in your project
- A database connection configured
- TypeScript set up (recommended)
Project Overview
We'll create a blog system with these models:
- User - Blog authors
- Post - Blog posts
- Comment - Comments on posts
Step 1: Define Model Interfaces
First, let's define TypeScript interfaces for our models:
// src/types/models.ts
export interface UserAttributes {
id: string;
name: string;
email: string;
password: string;
bio?: string;
created_at?: Date;
updated_at?: Date;
}
export interface PostAttributes {
id: string;
title: string;
content: string;
slug: string;
published: boolean;
user_id: string;
published_at?: Date;
created_at?: Date;
updated_at?: Date;
}
export interface CommentAttributes {
id: string;
content: string;
post_id: string;
user_id: string;
created_at?: Date;
updated_at?: Date;
}
Step 2: Create Models
Now let's create our models with relationships:
User Model
// src/models/User.ts
import { Model, HasMany } from 'scyllinx';
import { UserAttributes } from '@/types/index/models';
import { Post } from './Post';
import { Comment } from './Comment';
export class User extends Model<UserAttributes> {
protected static table = 'users';
protected static primaryKey = 'id';
protected static fillable = ['name', 'email', 'password', 'bio'];
protected static hidden = ['password'];
protected static timestamps = true;
// Relationships
postsRelation(): HasMany<User, Post> {
return this.hasMany(Post, 'user_id', 'id');
}
commentsRelation(): HasMany<User, Comment> {
return this.hasMany(Comment, 'user_id', 'id');
}
// Custom methods
getDisplayName(): string {
return this.name || 'Anonymous';
}
async getPublishedPosts() {
return await this.postsRelation()
.where('published', true)
.orderBy('published_at', 'desc')
.get();
}
}
Post Model
// src/models/Post.ts
import { Model, HasMany, BelongsTo } from 'scyllinx';
import { PostAttributes } from '@/types/index/models';
import { User } from './User';
import { Comment } from './Comment';
export class Post extends Model<PostAttributes> {
protected static table = 'posts';
protected static primaryKey = 'id';
protected static fillable = ['title', 'content', 'slug', 'published', 'user_id'];
protected static timestamps = true;
// Relationships
userRelation(): BelongsTo<Post, User> {
return this.belongsTo(User, 'user_id', 'id');
}
commentsRelation(): HasMany<Post, Comment> {
return this.hasMany(Comment, 'post_id', 'id');
}
// Scopes
static published() {
return this.query().where('published', true);
}
static bySlug(slug: string) {
return this.query().where('slug', slug);
}
// Custom methods
async getCommentCount(): Promise<number> {
return await this.comments().count();
}
getExcerpt(length = 150): string {
if (this.content.length <= length) {
return this.content;
}
return this.content.substring(0, length) + '...';
}
// Mutators
setTitleAttribute(value: string): string {
return value.toUpperCase()
}
private generateSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
}
Comment Model
// src/models/Comment.ts
import { Model, BelongsTo } from 'scyllinx';
import { CommentAttributes } from '@/types/index/models';
import { User } from './User';
import { Post } from './Post';
export class Comment extends Model<CommentAttributes> {
protected static table = 'comments';
protected static primaryKey = 'id';
protected static fillable = ['content', 'post_id', 'user_id'];
protected static timestamps = true;
// Relationships
user(): BelongsTo<Comment, User> {
return this.belongsTo(User, 'user_id', 'id');
}
post(): BelongsTo<Comment, Post> {
return this.belongsTo(Post, 'post_id', 'id');
}
// Custom methods
getFormattedDate(): string {
return this.created_at?.toLocaleDateString() || '';
}
}
Step 3: Create Migrations
Let's create migrations to set up our database schema:
Users Table Migration
// src/migrations/2024_01_01_000001_create_users_table.ts
import { Migration, Schema } from 'scyllinx';
export class CreateUsersTable extends Migration {
async up(schema: Schema): Promise<void> {
await schema.createTable('users', (table) => {
table.uuid('id').primary();
table.string('name');
table.string('email').unique();
table.string('password');
table.text('bio').nullable();
table.timestamps();
});
// Create index on email for faster lookups
await schema.createIndex('users', 'email', 'idx_users_email');
}
async down(schema: Schema): Promise<void> {
await schema.dropTable('users');
}
}
Posts Table Migration
// src/migrations/2024_01_01_000002_create_posts_table.ts
import { Migration, Schema } from 'scyllinx';
export class CreatePostsTable extends Migration {
async up(schema: Schema): Promise<void> {
await schema.createTable('posts', (table) => {
table.uuid('id').primary();
table.string('title');
table.text('content');
table.string('slug').unique();
table.boolean('published').default(false);
table.uuid('user_id');
table.timestamp('published_at').nullable();
table.timestamps();
// Foreign key constraint (if supported by your database)
table.foreign('user_id').references('id').on('users');
});
// Indexes for better query performance
await schema.createIndex('posts', 'slug', 'idx_posts_slug');
await schema.createIndex('posts', 'user_id', 'idx_posts_user_id');
await schema.createIndex('posts', 'published', 'idx_posts_published');
}
async down(schema: Schema): Promise<void> {
await schema.dropTable('posts');
}
}
Comments Table Migration
// src/migrations/2024_01_01_000003_create_comments_table.ts
import { Migration, Schema } from 'scyllinx';
export class CreateCommentsTable extends Migration {
async up(schema: Schema): Promise<void> {
await schema.createTable('comments', (table) => {
table.uuid('id').primary();
table.text('content');
table.uuid('post_id');
table.uuid('user_id');
table.timestamps();
// Foreign key constraints
table.foreign('post_id').references('id').on('posts');
table.foreign('user_id').references('id').on('users');
});
// Indexes
await schema.createIndex('comments', 'post_id', 'idx_comments_post_id');
await schema.createIndex('comments', 'user_id', 'idx_comments_user_id');
}
async down(schema: Schema): Promise<void> {
await schema.dropTable('comments');
}
}
Step 4: Run Migrations
Create a migration runner script:
// src/migrate.ts
import { ConnectionManager, MigrationManager } from 'scyllinx';
import { databaseConfig } from './config/database';
import { CreateUsersTable } from './migrations/2024_01_01_000001_create_users_table';
import { CreatePostsTable } from './migrations/2024_01_01_000002_create_posts_table';
import { CreateCommentsTable } from './migrations/2024_01_01_000003_create_comments_table';
async function runMigrations() {
try {
// Initialize connection
const connectionManager = ConnectionManager.getInstance();
await connectionManager.initialize(databaseConfig);
// Create migration manager
const migrationManager = new MigrationManager(connectionManager);
// Define migrations in order
const migrations = [
new CreateUsersTable(),
new CreatePostsTable(),
new CreateCommentsTable()
];
// Run migrations
console.log('Running migrations...');
await migrationManager.migrate(migrations);
console.log('✅ Migrations completed successfully!');
} catch (error) {
console.error('❌ Migration failed:', error);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
runMigrations();
}
Run the migrations:
npx ts-node src/migrate.ts
Step 5: Create Seeders
Let's create some sample data:
User Factory
// src/factories/UserFactory.ts
import { faker } from "@faker-js/faker"
import { defineFactory } from "../../../src"
import { User, UserAttributes } from "../models/User"
export const UserFactory = defineFactory<User, UserAttributes>("User", {
id: () => faker.string.uuid(),
name: () => faker.person.fullName(),
email: () => faker.internet.email(),
password: () => faker.internet.password(),
is_admin: () => false, // %10 şans admin olma
})
.state("admin", () => ({
is_admin: true,
}))
Database Seeder
// src/seeders/DatabaseSeeder.ts
import { Seeder } from 'scyllinx';
import { UserFactory } from '../factories/UserFactory';
import { User } from '../models/User';
import { Post } from '../models/Post';
import { Comment } from '../models/Comment';
export class DatabaseSeeder extends Seeder {
async run(): Promise<void> {
console.log('Seeding database...');
// Create users
const users = await this.factory(() => UserFactory)
.times(10)
.create();
console.log(`Created ${users.length} users`);
// Create admin user
const admin = await this.factory(() => UserFactory)
.as("admin")
.create({
name: "Admin User",
email: "admin@example.com",
});
console.log('Created admin user');
// Create posts
const posts = [];
for (const user of users.slice(0, 5)) {
const userPosts = await Post.createMany([
{
title: 'Getting Started with ScyllinX',
content: 'ScyllinX is a powerful TypeScript ORM...',
slug: 'quick-start-with-scyllinx',
published: true,
user_id: user.id,
published_at: new Date()
},
{
title: 'Advanced Query Building',
content: 'Learn how to build complex queries...',
slug: 'advanced-query-building',
published: true,
user_id: user.id,
published_at: new Date()
},
{
title: 'Draft Post',
content: 'This is a draft post...',
slug: 'draft-post',
published: false,
user_id: user.id
}
]);
posts.push(...userPosts);
}
console.log(`Created ${posts.length} posts`);
// Create comments
let commentCount = 0;
for (const post of posts.filter(p => p.published)) {
const numComments = Math.floor(Math.random() * 5) + 1;
for (let i = 0; i < numComments; i++) {
await Comment.create({
content: `This is comment ${i + 1} on ${post.title}`,
post_id: post.id,
user_id: users[Math.floor(Math.random() * users.length)].id
});
commentCount++;
}
}
console.log(`Created ${commentCount} comments`);
console.log('✅ Database seeding completed!');
}
}
Step 6: Basic Usage Examples
Now let's see how to use our models:
Creating Records
// src/examples/create-records.ts
import { User, Post, Comment } from '../models';
async function createRecords() {
// Create a user
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
bio: 'A passionate developer'
});
console.log('Created user:', user);
// console.log('Created user:', user.toObject());
// Create a post
const post = await Post.create({
title: 'My First Post',
content: 'This is the content of my first post...',
slug: 'my-first-post',
published: true,
user_id: user.id,
published_at: new Date()
});
console.log('Created post:', post);
// console.log('Created post:', post.toObject());
// Create a comment
const comment = await Comment.create({
content: 'Great post!',
post_id: post.id,
user_id: user.id
});
console.log('Created comment:', comment);
// console.log('Created comment:', comment.toObject());
}
Querying Records
// src/examples/query-records.ts
import { User, Post, Comment } from '../models';
async function queryRecords() {
// Find a user by ID
const user = await User.find('user-id-here');
if (user) {
console.log('Found user:', user.name);
}
// Find user by email
const userByEmail = await User.query()
.where('email', 'john@example.com')
.first();
// Get all published posts with their authors
const publishedPosts = await Post.query()
.where('published', true)
.with('user')
.orderBy('published_at', 'desc')
.get();
console.log('Published posts:', publishedPosts.length);
// Get posts with comment counts
const postsWithComments = await Post.query()
.with('comments')
.get();
postsWithComments.forEach(post => {
console.log(`${post.title}: ${post.comments?.length || 0} comments`);
});
// Complex query with multiple conditions
const recentPosts = await Post.query()
.where('published', true)
.where('created_at', '>', new Date('2024-01-01'))
.whereIn('user_id', ['user1', 'user2', 'user3'])
.with('user', 'comments.user')
.orderBy('created_at', 'desc')
.limit(10)
.get();
console.log('Recent posts:', recentPosts.length);
}
Working with Relationships
// src/examples/relationships.ts
import { User, Post, Comment } from '../models';
async function workWithRelationships() {
const user = await User.find('user-id-here');
if (!user) return;
// Get user's posts
const userPosts = await user.postsRelation().getResults();
console.log(`User has ${userPosts.length} posts`);
// Get only published posts
const publishedPosts = await user.postsRelation()
.where('published', true)
.get();
// Create a new post for the user
const newPost = await user.postsRelation().create({
title: 'New Post Title',
content: 'Post content here...',
slug: 'new-post-title',
published: true,
published_at: new Date()
});
// Get post with its author and comments
const postWithRelations = await Post.query()
.where('id', newPost.id)
.with('user', 'comments.user')
.first();
if (postWithRelations) {
console.log('Post author:', postWithRelations.user?.name);
console.log('Comments:', postWithRelations.comments?.length);
}
// Add a comment to the post
const comment = await newPost.commentsRelation().create({
content: 'This is a great post!',
user_id: user.id
});
console.log('Added comment:', comment.content);
}
Updating and Deleting
// src/examples/update-delete.ts
import { User, Post, Comment } from '../models';
async function updateAndDelete() {
// Update a user
const user = await User.find('user-id-here');
if (user) {
await user.update({
bio: 'Updated bio content'
});
console.log('User updated');
}
// Update multiple posts
const updatedCount = await Post.query()
.where('published', false)
.update({
published: true,
published_at: new Date()
});
console.log(`Published ${updatedCount} posts`);
// Delete a comment
const comment = await Comment.find('comment-id-here');
if (comment) {
await comment.delete();
console.log('Comment deleted');
}
// Delete multiple records
const deletedCount = await Comment.query()
.where('created_at', '<', new Date('2023-01-01'))
.delete();
console.log(`Deleted ${deletedCount} old comments`);
}
Step 7: Put It All Together
Create a main application file:
// src/app.ts
import { ConnectionManager } from 'scyllinx';
import { databaseConfig } from './config/database';
import { User, Post, Comment } from './models';
class BlogApp {
private connectionManager: ConnectionManager;
constructor() {
this.connectionManager = ConnectionManager.getInstance();
}
async initialize() {
await this.connectionManager.initialize(databaseConfig);
console.log('✅ Database connected');
}
async createUser(userData: { name: string; email: string; password: string }) {
return await User.create(userData);
}
async createPost(userId: string, postData: { title: string; content: string }) {
const user = await User.find(userId);
if (!user) throw new Error('User not found');
return await user.postsRelation().create({
...postData,
slug: this.generateSlug(postData.title),
published: true,
published_at: new Date()
});
}
async getPublishedPosts(limit = 10) {
return await Post.query()
.where('published', true)
.with('user', 'comments')
.orderBy('published_at', 'desc')
.limit(limit)
.get();
}
async getPostBySlug(slug: string) {
return await Post.query()
.where('slug', slug)
.where('published', true)
.with('user', 'comments.user')
.first();
}
async addComment(postId: string, userId: string, content: string) {
const post = await Post.find(postId);
if (!post) throw new Error('Post not found');
return await post.commentsRelation().create({
content,
user_id: userId
});
}
private generateSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
}
// Usage example
async function main() {
const app = new BlogApp();
await app.initialize();
try {
// Create a user
const user = await app.createUser({
name: 'Jane Doe',
email: 'jane@example.com',
password: 'password123'
});
// Create a post
const post = await app.createPost(user.id, {
title: 'Welcome to My Blog',
content: 'This is my first blog post using ScyllinX!'
});
// Add a comment
await app.addComment(post.id, user.id, 'Great first post!');
// Get published posts
const posts = await app.getPublishedPosts(5);
console.log(`Found ${posts.length} published posts`);
// Get specific post
const specificPost = await app.getPostBySlug('welcome-to-my-blog');
if (specificPost) {
console.log(`Post: ${specificPost.title}`);
console.log(`Author: ${specificPost.user?.name}`);
console.log(`Comments: ${specificPost.comments?.length}`);
}
} catch (error) {
console.error('Error:', error);
}
}
if (require.main === module) {
main().catch(console.error);
}
export { BlogApp };
Next Steps
Congratulations! You've built a complete blog system with ScyllinX. Here's what you can explore next:
Advanced Features
- Query Builder - Learn advanced querying techniques
- Relationships - Master all relationship types
- ScyllaDB Features - Leverage ScyllaDB-specific features
Best Practices
- Performance - Optimize your queries and models
- Testing - Write tests for your models and queries
- Deployment - Deploy your ScyllinX application
Real-world Examples
- API Development - Build REST APIs with ScyllinX
- Microservices - Use ScyllinX in microservice architectures
- Data Analytics - Leverage ScyllaDB for analytics workloads
Common Patterns
Repository Pattern
// src/repositories/PostRepository.ts
import { Post } from '../models/Post';
export class PostRepository {
async findPublishedBySlug(slug: string) {
return await Post.query()
.where('slug', slug)
.where('published', true)
.with('user', 'comments.user')
.first();
}
async findByAuthor(userId: string, published = true) {
const query = Post.query().where('user_id', userId);
if (published) {
query.where('published', true);
}
return await query
.orderBy('created_at', 'desc')
.get();
}
async getPopularPosts(limit = 10) {
// This would require a comment count column or subquery
return await Post.query()
.where('published', true)
.with('comments')
.orderBy('created_at', 'desc')
.limit(limit)
.get();
}
}
Service Layer
// src/services/BlogService.ts
import { PostRepository } from '../repositories/PostRepository';
import { User } from '../models/User';
export class BlogService {
constructor(private postRepository: PostRepository) {}
async createPost(userId: string, data: { title: string; content: string }) {
const user = await User.find(userId);
if (!user) {
throw new Error('User not found');
}
return await user.postsRelation().create({
...data,
slug: this.generateSlug(data.title),
published: true,
published_at: new Date()
});
}
async getPostWithStats(slug: string) {
const post = await this.postRepository.findPublishedBySlug(slug);
if (!post) return null;
const commentCount = await post.commentsRelation().count();
return {
...post.toObject(),
stats: {
commentCount
}
};
}
private generateSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
}
This getting started guide covers the fundamentals of building applications with ScyllinX. You now have a solid foundation to build upon!