Chapter 2: Getting Started and Basic Usage
Overview
This chapter provides a hands-on introduction to Vektagraf, guiding you through installation, setup, and your first application. You'll learn to define schemas, generate code, and perform basic database operations using Vektagraf's fluent API. By the end of this chapter, you'll have a working Vektagraf application and understand the fundamental patterns for building data-driven applications.
Learning Objectives
By the end of this chapter, you will be able to:
- Install and configure Vektagraf in your Dart project
- Create and validate JSON schemas for your data models
- Generate type-safe Dart code from schemas
- Perform basic CRUD operations with fluent method chaining
- Handle common errors and troubleshoot setup issues
- Configure Vektagraf for different deployment scenarios
Prerequisites
- Dart SDK 3.0.0 or later installed
- Basic familiarity with Dart programming
- Understanding of JSON format
- Text editor or IDE with Dart support (VS Code, IntelliJ, etc.)
Installation and Setup
Step 1: Create a New Dart Project
Start by creating a new Dart project or adding Vektagraf to an existing one:
# Create a new project
dart create my_vektagraf_app
cd my_vektagraf_app
# Or navigate to your existing project
cd my_existing_project
Step 2: Add Vektagraf Dependency
Add Vektagraf to your pubspec.yaml file:
name: my_vektagraf_app
description: A sample Vektagraf application
version: 1.0.0
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
vektagraf: ^0.1.0
dev_dependencies:
test: ^1.24.0
lints: ^6.0.0
Step 3: Install Dependencies
Run the following command to install Vektagraf and its dependencies:
dart pub get
Step 4: Verify Installation
Create a simple test file to verify the installation:
// test/installation_test.dart
import 'package:test/test.dart';
import 'package:vektagraf/vektagraf.dart';
void main() {
test('Vektagraf installation', () {
// Test basic imports and ID generation
final id = VektagrafId(
timestamp: DateTime.now().millisecondsSinceEpoch,
machineId: 1,
sequence: 1,
);
expect(id.toString(), isNotEmpty);
expect(id.toInt(), isPositive);
});
}
Run the test to confirm everything is working:
dart test test/installation_test.dart
Your First Vektagraf Application
Let's build a simple blog application to demonstrate Vektagraf's core features. This example will showcase schema definition, code generation, and basic operations.
Step 1: Define Your Schema
Create a schema file that defines your data models:
// schema/blog.json
{
"$schema": "vektagraf://schema/v1",
"models": {
"user": {
"$meta": {
"description": "Blog user with profile and content creation capabilities",
"timestamps": true
},
"id": "id",
"username": {
"type": "string",
"validation": {
"minLength": 3,
"maxLength": 50,
"pattern": "^[a-zA-Z0-9_]+$"
}
},
"email": {
"type": "string",
"validation": {
"format": "email"
}
},
"display_name": "string",
"bio": {
"type": "string",
"optional": true,
"validation": {
"maxLength": 500
}
},
"profile_embedding": {
"type": "vector",
"dimensions": 384,
"distance_function": "cosine",
"optional": true
},
"posts": "[@post]",
"followers": "{@user}",
"following": "{@user}",
"created_at": "datetime",
"updated_at": "datetime"
},
"post": {
"$meta": {
"description": "Blog post with content and metadata",
"timestamps": true
},
"id": "id",
"title": {
"type": "string",
"validation": {
"minLength": 1,
"maxLength": 200
}
},
"content": {
"type": "string",
"validation": {
"minLength": 1,
"maxLength": 10000
}
},
"content_embedding": {
"type": "vector",
"dimensions": 1536,
"distance_function": "cosine",
"optional": true
},
"author_id": "id",
"author": "@user",
"tags": "{@tag}",
"published": {
"type": "bool",
"default": false
},
"view_count": {
"type": "int32",
"default": 0
},
"created_at": "datetime",
"updated_at": "datetime"
},
"tag": {
"$meta": {
"description": "Content categorization tags",
"timestamps": true
},
"id": "id",
"name": {
"type": "string",
"validation": {
"minLength": 1,
"maxLength": 50,
"pattern": "^[a-zA-Z0-9_-]+$"
}
},
"description": {
"type": "string",
"optional": true,
"validation": {
"maxLength": 200
}
},
"posts": "{@post}",
"created_at": "datetime"
}
},
"metadata": {
"version": "1.0.0",
"description": "Simple blog application schema"
}
}
Step 2: Generate Dart Code
Vektagraf provides both CLI and programmatic approaches for code generation. Choose the method that fits your workflow:
Option A: CLI Code Generation
# Generate code using the CLI
dart run vektagraf:generate \
--schema schema/blog.json \
--output lib/generated/ \
--package-name my_vektagraf_app
Option B: Programmatic Code Generation
Create a build script for more control over the generation process:
// tool/generate.dart
import 'dart:io';
import 'package:vektagraf/vektagraf.dart';
Future<void> main() async {
print('Generating Vektagraf code...');
try {
// Parse the schema
final parser = VektagrafSchemaParser();
final schema = await parser.parseFromFile('schema/blog.json');
print('Parsed schema with ${schema.models.length} models:');
for (final modelName in schema.models.keys) {
print(' - $modelName');
}
// Generate code
final generator = VektagrafCodeGenerator();
final generatedFiles = await generator.generateFromSchema(
schema,
outputDirectory: 'lib/generated',
packageName: 'my_vektagraf_app',
);
print('\nGenerated ${generatedFiles.length} files:');
for (final file in generatedFiles) {
print(' - $file');
}
print('\nCode generation completed successfully!');
} catch (e, stackTrace) {
print('Error during code generation: $e');
print('Stack trace: $stackTrace');
exit(1);
}
}
Run the generation script:
dart run tool/generate.dart
Step 3: Examine Generated Code
After generation, you'll find several files in the lib/generated/ directory:
lib/generated/
├── models.dart # Model classes (User, Post, Tag)
├── database.dart # Database extensions
├── repositories.dart # Repository classes
└── extensions.dart # Fluent API extensions
Let's look at what gets generated:
// lib/generated/models.dart (excerpt)
class User {
final VektagrafId id;
final String username;
final String email;
final String displayName;
final String? bio;
final List<double>? profileEmbedding;
final List<Post> posts;
final Set<User> followers;
final Set<User> following;
final DateTime createdAt;
final DateTime updatedAt;
const User({
required this.id,
required this.username,
required this.email,
required this.displayName,
this.bio,
this.profileEmbedding,
this.posts = const [],
this.followers = const {},
this.following = const {},
required this.createdAt,
required this.updatedAt,
});
// Validation methods
bool get isValid => _validate().isEmpty;
List<String> _validate() {
final errors = <String>[];
if (username.length < 3 || username.length > 50) {
errors.add('Username must be between 3 and 50 characters');
}
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(username)) {
errors.add('Username can only contain letters, numbers, and underscores');
}
// Additional validation...
return errors;
}
// Serialization methods
Map<String, dynamic> toJson() => {
'id': id.toString(),
'username': username,
'email': email,
'display_name': displayName,
if (bio != null) 'bio': bio,
if (profileEmbedding != null) 'profile_embedding': profileEmbedding,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
factory User.fromJson(Map<String, dynamic> json) => User(
id: VektagrafId.parse(json['id']),
username: json['username'],
email: json['email'],
displayName: json['display_name'],
bio: json['bio'],
profileEmbedding: json['profile_embedding']?.cast<double>(),
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
);
// Copy methods for immutable updates
User copyWith({
String? username,
String? email,
String? displayName,
String? bio,
List<double>? profileEmbedding,
}) => User(
id: id,
username: username ?? this.username,
email: email ?? this.email,
displayName: displayName ?? this.displayName,
bio: bio ?? this.bio,
profileEmbedding: profileEmbedding ?? this.profileEmbedding,
posts: posts,
followers: followers,
following: following,
createdAt: createdAt,
updatedAt: DateTime.now(),
);
}
Step 4: Create Your Application
Now create your main application file:
// lib/main.dart
import 'package:vektagraf/vektagraf.dart';
import 'generated/models.dart';
import 'generated/database.dart';
Future<void> main() async {
print('=== Vektagraf Blog Application ===\n');
// Initialize database
final db = VektagrafDatabase();
await db.open('blog.vektagraf', config: VektagrafConfig.embedded());
try {
await runBlogExample(db);
} finally {
await db.close();
}
}
Future<void> runBlogExample(VektagrafDatabase db) async {
// 1. Create users
print('1. Creating users...');
final alice = User(
id: VektagrafId.generate(),
username: 'alice_dev',
email: 'alice@example.com',
displayName: 'Alice Johnson',
bio: 'Software engineer passionate about databases and AI',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
final bob = User(
id: VektagrafId.generate(),
username: 'bob_writer',
email: 'bob@example.com',
displayName: 'Bob Smith',
bio: 'Technical writer and developer advocate',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
// Save users to database
await db.users().save(alice);
await db.users().save(bob);
print('Created users: ${alice.displayName}, ${bob.displayName}');
// 2. Create tags
print('\n2. Creating tags...');
final dartTag = Tag(
id: VektagrafId.generate(),
name: 'dart',
description: 'Dart programming language',
createdAt: DateTime.now(),
);
final databaseTag = Tag(
id: VektagrafId.generate(),
name: 'database',
description: 'Database technologies and concepts',
createdAt: DateTime.now(),
);
await db.tags().save(dartTag);
await db.tags().save(databaseTag);
print('Created tags: ${dartTag.name}, ${databaseTag.name}');
// 3. Create posts
print('\n3. Creating posts...');
final post1 = Post(
id: VektagrafId.generate(),
title: 'Getting Started with Vektagraf',
content: '''
Vektagraf is a revolutionary database that combines object storage,
vector search, and graph operations in a single platform. In this post,
we'll explore how to build your first application.
Key features:
- Schema-driven development
- Type-safe Dart integration
- Built-in vector search
- Natural graph operations
- Enterprise security
''',
authorId: alice.id,
author: alice,
tags: {dartTag, databaseTag},
published: true,
viewCount: 0,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
final post2 = Post(
id: VektagrafId.generate(),
title: 'Advanced Vector Search Patterns',
content: '''
Vector search is becoming increasingly important for modern applications.
This post covers advanced patterns for implementing semantic search,
recommendation systems, and similarity matching using Vektagraf.
Topics covered:
- Embedding generation strategies
- Distance function selection
- Performance optimization
- Hybrid search patterns
''',
authorId: alice.id,
author: alice,
tags: {databaseTag},
published: true,
viewCount: 0,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
await db.posts().save(post1);
await db.posts().save(post2);
print('Created posts: "${post1.title}", "${post2.title}"');
// 4. Demonstrate basic queries
print('\n4. Querying data...');
// Find all users
final allUsers = await db.users().toList();
print('Total users: ${allUsers.length}');
// Find posts by author
final alicePosts = await db.posts()
.where((post) => post.authorId == alice.id)
.toList();
print('Posts by Alice: ${alicePosts.length}');
// Find published posts
final publishedPosts = await db.posts()
.where((post) => post.published)
.toList();
print('Published posts: ${publishedPosts.length}');
// Find posts with specific tag
final dartPosts = await db.posts()
.where((post) => post.tags.any((tag) => tag.name == 'dart'))
.toList();
print('Posts tagged with "dart": ${dartPosts.length}');
// 5. Demonstrate updates
print('\n5. Updating data...');
// Update post view count
final updatedPost1 = post1.copyWith();
// Note: In a real implementation, you'd increment the view count
await db.posts().save(updatedPost1);
// Add follower relationship
final updatedBob = bob.copyWith();
// Note: In a real implementation, you'd add Alice to Bob's following set
await db.users().save(updatedBob);
print('Updated post view count and user relationships');
// 6. Demonstrate complex queries
print('\n6. Complex queries...');
// Find recent posts by followed users (simulated)
final recentPosts = await db.posts()
.where((post) => post.createdAt.isAfter(
DateTime.now().subtract(Duration(days: 7))
))
.orderBy((post) => post.createdAt, descending: true)
.take(5);
print('Recent posts (last 7 days): ${recentPosts.length}');
// Find popular tags (posts count > 0)
final popularTags = await db.tags()
.where((tag) => tag.posts.isNotEmpty)
.toList();
print('Popular tags: ${popularTags.map((t) => t.name).join(', ')}');
print('\n=== Blog Example Completed Successfully ===');
}
Step 5: Run Your Application
Execute your application to see Vektagraf in action:
dart run lib/main.dart
Expected output:
=== Vektagraf Blog Application ===
1. Creating users...
Created users: Alice Johnson, Bob Smith
2. Creating tags...
Created tags: dart, database
3. Creating posts...
Created posts: "Getting Started with Vektagraf", "Advanced Vector Search Patterns"
4. Querying data...
Total users: 2
Posts by Alice: 2
Published posts: 2
Posts tagged with "dart": 1
5. Updating data...
Updated post view count and user relationships
6. Complex queries...
Recent posts (last 7 days): 2
Popular tags: dart, database
=== Blog Example Completed Successfully ===
Basic CRUD Operations
Now let's explore the fundamental Create, Read, Update, and Delete operations in detail.
Create Operations
Vektagraf provides several ways to create objects:
// Single object creation
final user = User(
id: VektagrafId.generate(),
username: 'new_user',
email: 'user@example.com',
displayName: 'New User',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
await db.users().save(user);
// Batch creation
final users = [
User(/* ... */),
User(/* ... */),
User(/* ... */),
];
await db.users().saveAll(users);
// Create with validation
try {
final invalidUser = User(
id: VektagrafId.generate(),
username: 'x', // Too short - will fail validation
email: 'invalid-email', // Invalid format
displayName: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
if (invalidUser.isValid) {
await db.users().save(invalidUser);
} else {
print('Validation errors: ${invalidUser._validate()}');
}
} catch (e) {
print('Save failed: $e');
}
Read Operations
Vektagraf's fluent API makes querying intuitive and powerful:
// Find by ID
final user = await db.users().findById(userId);
// Find single object with condition
final alice = await db.users()
.where((u) => u.username == 'alice_dev')
.firstOrNull;
// Find multiple objects
final activeUsers = await db.users()
.where((u) => u.createdAt.isAfter(DateTime.now().subtract(Duration(days: 30))))
.toList();
// Pagination
final page1 = await db.posts()
.where((p) => p.published)
.orderBy((p) => p.createdAt, descending: true)
.skip(0)
.take(10);
final page2 = await db.posts()
.where((p) => p.published)
.orderBy((p) => p.createdAt, descending: true)
.skip(10)
.take(10);
// Count operations
final totalUsers = await db.users().count();
final publishedPostCount = await db.posts()
.where((p) => p.published)
.count();
// Existence checks
final hasUsers = await db.users().any();
final hasUnpublishedPosts = await db.posts()
.where((p) => !p.published)
.any();
Update Operations
Updates in Vektagraf use immutable patterns with copy methods:
// Single field update
final user = await db.users().findById(userId);
final updatedUser = user.copyWith(
displayName: 'Updated Display Name',
bio: 'New bio content',
);
await db.users().save(updatedUser);
// Conditional updates
final posts = await db.posts()
.where((p) => p.authorId == userId)
.toList();
for (final post in posts) {
if (!post.published) {
final publishedPost = post.copyWith(
published: true,
updatedAt: DateTime.now(),
);
await db.posts().save(publishedPost);
}
}
// Batch updates
final unpublishedPosts = await db.posts()
.where((p) => !p.published)
.toList();
final publishedPosts = unpublishedPosts.map((p) => p.copyWith(
published: true,
updatedAt: DateTime.now(),
)).toList();
await db.posts().saveAll(publishedPosts);
Delete Operations
Vektagraf supports both soft and hard deletion patterns:
// Delete by ID
await db.users().deleteById(userId);
// Delete by condition
await db.posts()
.where((p) => !p.published && p.createdAt.isBefore(
DateTime.now().subtract(Duration(days: 30))
))
.delete();
// Delete all objects of a type (use with caution)
await db.tags().deleteAll();
// Soft delete pattern (using a deleted field)
final user = await db.users().findById(userId);
final softDeletedUser = user.copyWith(
// Assuming you have a 'deleted' field in your schema
// deleted: true,
updatedAt: DateTime.now(),
);
await db.users().save(softDeletedUser);
Configuration Options
Vektagraf supports various configuration options for different deployment scenarios.
Embedded Configuration
For single-application deployments:
final config = VektagrafConfig.embedded(
maxMemoryBytes: 512 * 1024 * 1024, // 512MB
enableCompression: true,
enableEncryption: false, // Disable for development
backupInterval: Duration(hours: 6),
);
final db = VektagrafDatabase();
await db.open('myapp.vektagraf', config: config);
Hosted Configuration
For client-server deployments:
final config = VektagrafConfig.hosted(
connectionTimeout: Duration(seconds: 10),
maxConnections: 20,
enableTLS: true,
authToken: await getAuthToken(),
retryAttempts: 3,
);
final db = VektagrafDatabase();
await db.open('vektagraf://server:8080/mydb', config: config);
Development Configuration
Optimized for development and testing:
final config = VektagrafConfig(
// Fast startup
maxMemoryBytes: 128 * 1024 * 1024, // 128MB
enableCompression: false,
enableEncryption: false,
// Detailed logging
enableLogging: true,
logLevel: LogLevel.debug,
// Relaxed timeouts
requestTimeout: Duration(seconds: 30),
transactionTimeout: Duration(minutes: 5),
// Development features
enableMetrics: true,
enableProfiler: true,
);
Production Configuration
Optimized for production environments:
final config = VektagrafConfig(
// Performance settings
maxMemoryBytes: 2 * 1024 * 1024 * 1024, // 2GB
enableCompression: true,
enableEncryption: true,
// Security settings
enableTLS: true,
tlsCertificatePath: '/etc/ssl/certs/vektagraf.crt',
tlsKeyPath: '/etc/ssl/private/vektagraf.key',
// Reliability settings
backupInterval: Duration(hours: 1),
maxRetryAttempts: 5,
connectionTimeout: Duration(seconds: 5),
// Monitoring
enableMetrics: true,
enableAuditLog: true,
logLevel: LogLevel.warning,
);
Common Error Scenarios and Troubleshooting
Understanding common errors and their solutions will help you develop more robust applications.
Schema Validation Errors
// Error: Invalid schema format
try {
final parser = VektagrafSchemaParser();
final schema = await parser.parseFromFile('invalid_schema.json');
} catch (e) {
if (e is SchemaParseException) {
print('Schema error in ${e.model}.${e.field}: ${e.message}');
// Handle specific schema issues
}
}
Connection Errors
// Error: Database connection failed
try {
final db = VektagrafDatabase();
await db.open('vektagraf://unreachable:8080/db');
} catch (e) {
print('Connection failed: $e');
// Implement retry logic
for (int attempt = 1; attempt <= 3; attempt++) {
try {
await Future.delayed(Duration(seconds: attempt * 2));
await db.open('vektagraf://backup:8080/db');
break;
} catch (retryError) {
if (attempt == 3) {
print('All connection attempts failed');
rethrow;
}
}
}
}
Validation Errors
// Error: Object validation failed
final user = User(
id: VektagrafId.generate(),
username: 'x', // Too short
email: 'invalid', // Invalid format
displayName: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
if (!user.isValid) {
final errors = user._validate();
print('Validation failed:');
for (final error in errors) {
print(' - $error');
}
// Handle validation errors appropriately
return;
}
Transaction Errors
// Error: Transaction conflict or timeout
try {
await db.transaction((tx) async {
final user = await tx.users().findById(userId);
final updatedUser = user.copyWith(displayName: 'New Name');
await tx.users().save(updatedUser);
// Simulate long operation that might timeout
await Future.delayed(Duration(minutes: 10));
});
} catch (e) {
if (e is TransactionTimeoutException) {
print('Transaction timed out - consider breaking into smaller operations');
} else if (e is TransactionConflictException) {
print('Transaction conflict - retry with exponential backoff');
}
}
Performance Issues
// Issue: Slow queries
// Solution: Add proper indexing and optimize queries
// Instead of loading all objects
final allPosts = await db.posts().toList(); // Slow for large datasets
// Use pagination and filtering
final recentPosts = await db.posts()
.where((p) => p.createdAt.isAfter(DateTime.now().subtract(Duration(days: 7))))
.orderBy((p) => p.createdAt, descending: true)
.take(20); // Much faster
// Use count instead of loading objects
final totalCount = await db.posts().count(); // Fast
// Instead of: final count = (await db.posts().toList()).length; // Slow
Memory Issues
// Issue: Out of memory errors
// Solution: Process data in batches
// Instead of loading everything at once
final allUsers = await db.users().toList(); // May cause OOM
// Process in batches
const batchSize = 100;
int offset = 0;
while (true) {
final batch = await db.users()
.skip(offset)
.take(batchSize);
if (batch.isEmpty) break;
// Process batch
for (final user in batch) {
// Process individual user
}
offset += batchSize;
}
Best Practices
Schema Design
- Use descriptive names: Choose clear, consistent naming for models and fields
- Add validation: Define validation rules in your schema to catch errors early
- Plan relationships: Think about how your objects relate before defining relationships
- Version your schemas: Use schema versioning for production applications
Code Organization
// Organize your code into logical modules
lib/
├── models/ # Generated model classes
├── repositories/ # Data access layer
├── services/ # Business logic
├── utils/ # Helper functions
└── main.dart # Application entry point
Error Handling
// Always handle potential errors gracefully
Future<User?> findUserSafely(VektagrafId userId) async {
try {
return await db.users().findById(userId);
} catch (e) {
logger.error('Failed to find user $userId: $e');
return null;
}
}
Performance Optimization
// Use appropriate query patterns
// Good: Specific queries with conditions
final activeUsers = await db.users()
.where((u) => u.lastLoginAt.isAfter(cutoffDate))
.take(100);
// Bad: Loading everything then filtering
final allUsers = await db.users().toList();
final activeUsers = allUsers.where((u) =>
u.lastLoginAt.isAfter(cutoffDate)).take(100).toList();
Summary
This chapter introduced you to Vektagraf's fundamental concepts and operations. You learned how to:
- Install and configure Vektagraf in your Dart project
- Define schemas using JSON with proper validation and relationships
- Generate type-safe code from schemas using CLI or programmatic approaches
- Perform CRUD operations using Vektagraf's fluent API
- Handle common errors and troubleshoot issues effectively
- Configure Vektagraf for different deployment scenarios
Key takeaways:
- Schema-first development ensures consistency and type safety
- Fluent APIs make complex queries readable and maintainable
- Generated code provides compile-time validation and IDE support
- Proper error handling is essential for robust applications
- Configuration matters for performance and reliability
In the next chapter, we'll dive deeper into schema design and explore advanced code generation features that enable more sophisticated applications.
Next Steps
- Chapter 3: Schema Design and Code Generation - Master advanced schema patterns
- Chapter 4: Database Operations and Transactions - Learn advanced query patterns
- Chapter 5: Vector Search and Similarity Operations - Implement semantic search
- Chapter 6: Graph Operations and Relationship Modeling - Build connected applications
No Comments