harpy 0.1.3
harpy: ^0.1.3 copied to clipboard
A modern, fast, and lightweight backend framework for Dart
Harpy Backend Framework #
A modern, fast, and lightweight backend framework for Dart that makes building REST APIs easy and enjoyable. Built on top of Dart's powerful shelf package, Harpy provides an Express.js-like experience for Dart developers.
β¨ Features #
- π Fast & Lightweight - Built on Dart's high-performance HTTP server
- π£οΈ Powerful Routing - Express.js-style routing with parameter support
- π§ Middleware System - Flexible and composable middleware architecture
- π JSON First - Built-in JSON parsing and serialization
- βοΈ Configuration Management - Environment-based configuration with file support
- π Authentication - JWT and Basic Auth middleware included
- π CORS Support - Cross-origin resource sharing out of the box
- π Request Logging - Comprehensive request/response logging
- β±οΈ Task Scheduling - Periodic, scheduled, and instant task execution
ποΈ Database & ORM Features #
- Production-Ready SQLite - Full implementation with transactions, migrations, and connection pooling
- PostgreSQL Support - Complete adapter with advanced features
- MySQL Connector - Native MySQL database integration
- MongoDB Integration - NoSQL document database support
- Redis Cache Layer - Key-value store support (stub implementation)
- Enhanced ORM System π - Modern ORM with comprehensive features
- ModelRegistry - Centralized model constructor registration and automatic instantiation
- copyWith() Method - Flexible model copying with attribute changes
- Composite Primary Keys - Advanced primary key handling for complex schemas
- Static ORM Methods - Convenient static query methods (
where(),fetchOne(),fetchAll(), etc.) - Relationship System - Complete relationship support (BelongsTo, HasMany, BelongsToMany, HasOne)
- Eager Loading - Load relationships efficiently to prevent N+1 queries
- Active Record Pattern - Easy model-based database operations
- Query Builder - Type-safe, fluent query construction
- Database Migrations - Version control for your database schema
- ACID Transactions - Full transaction support with automatic rollback
- Connection Pooling - Efficient database connection management
- Security First - Built-in SQL injection prevention
π§ͺ Development & Testing #
- Testing Ready - Easy to test with built-in testing utilities
- π§ CLI Tools - Project scaffolding and development tools
οΏ½ Documentation #
Complete documentation is available in the doc/ folder:
- π Documentation Index - Complete guide and component overview
- π Framework Overview - Core concepts and getting started
- βοΈ Configuration - Environment and file-based configuration
- ποΈ Database System - ORM, adapters, and migrations
- π‘ HTTP Components - Request/response handling
- π§ Middleware - Authentication, CORS, logging, and custom middleware
- π£οΈ Routing - URL routing and parameter extraction
- π₯οΈ Server - Server implementation and deployment
οΏ½π Quick Start #
Installation #
Add Harpy to your pubspec.yaml:
dependencies:
harpy: ^0.1.3
Or install globally for CLI tools:
dart pub global activate harpy
Create a New Project #
harpy create my_api
cd my_api
dart pub get
dart run bin/my_api.dart serve
The generated project includes:
lib/main.dart- Main application codebin/my_api.dart- CLI management tool with commands: serve, migrate, help, version
Basic Usage #
import 'package:harpy/harpy.dart';
void main() async {
final app = Harpy();
// Enable CORS and logging
app.enableCors();
app.enableLogging();
// Basic route
app.get('/', (req, res) {
return res.json({
'message': 'Welcome to Harpy!',
'timestamp': DateTime.now().toIso8601String(),
});
});
// Route with parameters
app.get('/users/:id', (req, res) {
final userId = req.params['id'];
return res.json({'userId': userId});
});
// POST route with JSON body
app.post('/users', (req, res) async {
final body = await req.json();
return res.status(201).json({
'message': 'User created',
'user': body,
});
});
await app.listen(port: 3000);
print('π Server running on http://localhost:3000');
}
π Documentation #
Routing #
Harpy supports all standard HTTP methods with parameter support:
// Basic routes
app.get('/users', handler);
app.post('/users', handler);
app.put('/users/:id', handler);
app.delete('/users/:id', handler);
app.patch('/users/:id', handler);
// Route parameters
app.get('/users/:id/posts/:postId', (req, res) {
final userId = req.params['id'];
final postId = req.params['postId'];
return res.json({'userId': userId, 'postId': postId});
});
// Query parameters
app.get('/search', (req, res) {
final query = req.query['q'];
final limit = int.tryParse(req.query['limit'] ?? '10') ?? 10;
return res.json({'query': query, 'limit': limit});
});
// Multiple methods
app.match(['GET', 'POST'], '/api/data', handler);
app.any('/api/wildcard', handler); // All methods
Request & Response #
Request Object
app.post('/api/data', (req, res) async {
// HTTP method and path
print(req.method); // POST
print(req.path); // /api/data
// Headers
final contentType = req.headers['content-type'];
final userAgent = req.userAgent;
// Route parameters
final id = req.params['id'];
// Query parameters
final filter = req.query['filter'];
// JSON body
final body = await req.json();
// Raw body
final rawBody = await req.text();
// Check content type
if (req.isJson) {
// Handle JSON request
}
return res.json({'received': body});
});
Response Object
app.get('/api/examples', (req, res) {
// JSON response
return res.json({'data': 'value'});
// Text response
return res.text('Hello, World!');
// HTML response
return res.html('<h1>Hello</h1>');
// Status codes
return res.status(201).json({'created': true});
// Headers
return res.header('X-Custom', 'value').json({});
// Redirects
return res.redirect('/new-location');
// File responses
return res.file(File('path/to/file.pdf'));
// Common status helpers
return res.ok({'success': true}); // 200
return res.created({'id': 123}); // 201
return res.badRequest({'error': 'Invalid'}); // 400
return res.unauthorized({'error': 'Auth'}); // 401
return res.notFound({'error': 'Not found'}); // 404
return res.internalServerError({'error': 'Server error'}); // 500
});
Middleware #
Built-in Middleware
final app = Harpy();
// CORS
app.enableCors(
origin: 'https://myapp.com',
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
);
// Logging
app.enableLogging(
logBody: true, // Log request/response bodies
logHeaders: false, // Log headers
);
// Authentication
app.enableAuth(
jwtSecret: 'your-secret-key',
excludePaths: ['/login', '/register', '/health'],
);
Custom Middleware
import 'package:shelf/shelf.dart' as shelf;
// Custom middleware
shelf.Middleware customMiddleware() {
return (shelf.Handler innerHandler) {
return (shelf.Request request) async {
print('Before request: ${request.method} ${request.url}');
final response = await innerHandler(request);
print('After request: ${response.statusCode}');
return response;
};
};
}
// Use custom middleware
app.use(customMiddleware());
Database & ORM #
Harpy includes a complete ORM system with support for multiple databases. The framework provides a unified interface across all database adapters, making it easy to switch between different database systems.
Database Adapters Status
| Database | Status | Features | Production Ready |
|---|---|---|---|
| SQLite | β Complete | Full SQL, Transactions, Migrations, Connection pooling | β Yes |
| PostgreSQL | β Complete | Advanced SQL features, JSON support, Full-text search | β Yes |
| MySQL | β Complete | Standard SQL, Stored procedures, Multi-database | β Yes |
| MongoDB | β Complete | Document queries, Aggregation pipeline, GridFS | β Yes |
| Redis | β οΈ Stub Implementation | Basic key-value operations, Transactions (MULTI/EXEC) | β Development needed |
Note: The Redis adapter is currently implemented as a stub for demonstration purposes. A full Redis implementation is planned for future releases. See the TODO section for more details.
SQLite (Production Ready)
The SQLite adapter provides the most mature and feature-complete implementation:
import 'package:harpy/harpy.dart';
void main() async {
// Direct SQLite connection
final db = await SqliteAdapter.create({
'path': './database.db', // or ':memory:' for in-memory DB
});
// Create tables
await db.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''');
// Insert with parameters (prevents SQL injection)
await db.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
['John Doe', 'john@example.com']
);
// Query data
final result = await db.execute('SELECT * FROM users WHERE name LIKE ?', ['%John%']);
for (final user in result.rows) {
print('User: ${user['name']} (${user['email']})');
}
// Transactions
final transaction = await db.beginTransaction();
try {
await transaction.execute('INSERT INTO users (name, email) VALUES (?, ?)',
['Alice', 'alice@example.com']);
await transaction.execute('INSERT INTO users (name, email) VALUES (?, ?)',
['Bob', 'bob@example.com']);
await transaction.commit();
} on Exception catch (e) {
await transaction.rollback();
rethrow;
}
await db.disconnect();
}
PostgreSQL
Full-featured PostgreSQL support with advanced capabilities:
// PostgreSQL connection
final db = await PostgresqlAdapter.create({
'host': 'localhost',
'port': 5432,
'database': 'myapp',
'username': 'user',
'password': 'password',
});
// Advanced PostgreSQL features
await db.execute('''
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
metadata JSONB,
search_vector TSVECTOR,
created_at TIMESTAMP DEFAULT NOW()
)
''');
// JSON queries
final result = await db.execute(
"SELECT * FROM products WHERE metadata->>'category' = ?",
['electronics']
);
MySQL
Native MySQL support with connection pooling:
// MySQL connection
final db = await MysqlAdapter.create({
'host': 'localhost',
'port': 3306,
'database': 'myapp',
'username': 'user',
'password': 'password',
});
// MySQL-specific features
await db.execute('''
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB
''');
MongoDB
Document-oriented database support:
// MongoDB connection
final db = await MongodbAdapter.create({
'host': 'localhost',
'port': 27017,
'database': 'myapp',
'username': 'user',
'password': 'password',
});
// Document operations
await db.execute('INSERT', ['users'], {
'name': 'John Doe',
'email': 'john@example.com',
'profile': {
'age': 30,
'interests': ['coding', 'music']
}
});
// Query documents
final result = await db.execute('FIND', ['users'], {
'filter': {'profile.age': {'\$gte': 25}},
'sort': {'name': 1}
});
Redis (Stub Implementation)
β οΈ Current Status: The Redis adapter is implemented as a stub for demonstration and testing purposes.
// Redis connection (stub implementation)
final db = await RedisAdapter.create({
'host': 'localhost',
'port': 6379,
'database': 0,
});
// Basic operations (simulated)
await db.execute('SET key value');
final result = await db.execute('GET key');
// Note: This is a stub implementation
// Full Redis features are planned for future releases
Using Database Manager
// Connect through Database manager (supports multiple DB types)
final database = await Database.connect({
'type': 'sqlite', // sqlite, postgresql, mysql, mongodb, redis
'path': './app_database.db',
});
// Use with automatic transaction handling
await database.transaction((tx) async {
await tx.execute('INSERT INTO orders (user_id, total) VALUES (?, ?)', [1, 99.99]);
await tx.execute('UPDATE inventory SET stock = stock - 1 WHERE id = ?', [1]);
// Automatically commits on success, rolls back on error
});
// Get database info
final info = await database.getInfo();
print('Database: ${info['type']} v${info['version']}');
Enhanced ORM System π
Harpy v0.1.3 introduces a completely redesigned ORM system with modern features:
import 'package:harpy/harpy.dart';
// 1. Register models with ModelRegistry
void registerModels() {
ModelRegistry.register<User>(() => User());
ModelRegistry.register<Post>(() => Post());
ModelRegistry.register<Category>(() => Category());
}
// 2. Define enhanced models with new features
class User extends Model with ActiveRecord {
@override
String get tableName => 'users';
// Primary key handling (single or composite)
@override
List<String> get primaryKey => ['id'];
// Standard getters/setters
String? get name => get<String>('name');
set name(String? value) => setAttribute('name', value);
String? get email => get<String>('email');
set email(String? value) => setAttribute('email', value);
bool get isActive => get<bool>('active') ?? false;
set isActive(bool value) => setAttribute('active', value);
// π Enhanced copyWith method
User copyWith({String? name, String? email, bool? isActive}) {
return super.copyWith<User>(attributes: {
if (name != null) 'name': name,
if (email != null) 'email': email,
if (isActive != null) 'active': isActive,
});
}
// π Relationships
Future<List<Post>> get posts => hasMany<Post>('user_id');
Future<Profile?> get profile => hasOne<Profile>('user_id');
// π Many-to-many relationships
Future<List<Category>> get categories => belongsToMany<Category>(
'user_categories', 'user_id', 'category_id');
}
class Post extends Model with ActiveRecord {
@override
String get tableName => 'posts';
String? get title => get<String>('title');
set title(String? value) => setAttribute('title', value);
String? get content => get<String>('content');
set content(String? value) => setAttribute('content', value);
int? get userId => get<int>('user_id');
set userId(int? value) => setAttribute('user_id', value);
// Relationships
Future<User?> get author => belongsTo<User>('user_id');
}
// 3. π Static ORM Methods - Modern query interface
void demonstrateStaticMethods() async {
// Initialize ModelRegistry
registerModels();
// Fetch all users
final users = await User.fetchAll<User>();
// Fetch with conditions
final activeUsers = await User.where<User>('active', true);
// Single record
final user = await User.fetchOne<User>(
where: 'email = ?',
parameters: ['john@example.com']
);
// Pagination
final recentUsers = await User.fetchAll<User>(
orderBy: 'created_at DESC',
limit: 10,
offset: 0,
);
// Count records
final userCount = await User.count<User>();
final activeUserCount = await User.count<User>(where: 'active = ?', parameters: [true]);
// Check existence
final emailExists = await User.exists<User>(
where: 'email = ?',
parameters: ['test@example.com']
);
// Find by attributes (new convenience method)
final usersByName = await User.findBy<User>({'name': 'John', 'active': true});
// Bulk operations
await User.updateWhere<User>(
where: 'last_login < ?',
parameters: [DateTime.now().subtract(Duration(days: 30))],
attributes: {'active': false},
);
await User.deleteWhere<User>(
where: 'active = ? AND created_at < ?',
parameters: [false, DateTime.now().subtract(Duration(days: 90))],
);
}
// 4. π Advanced relationship usage
void demonstrateRelationships() async {
final user = await User.fetchOne<User>(where: 'id = ?', parameters: [1]);
// Load relationships
final userPosts = await user?.posts;
final userProfile = await user?.profile;
final userCategories = await user?.categories;
// Many-to-many operations
if (user != null) {
final relationship = user.belongsToManyRelation<Category>(
'user_categories', 'user_id', 'category_id');
// Attach categories
await relationship.attach([1, 2, 3], pivotData: {'assigned_at': DateTime.now()});
// Detach categories
await relationship.detach([2]);
// Sync categories (replaces all)
await relationship.sync([1, 3, 4]);
}
}
// 5. π Enhanced model copying and comparison
void demonstrateCopyWith() async {
final user = await User.fetchOne<User>(where: 'id = ?', parameters: [1]);
if (user != null) {
// Create modified copy
final updatedUser = user.copyWith(
name: 'New Name',
isActive: false,
);
// Original user unchanged
print('Original: ${user.name}'); // 'John Doe'
print('Updated: ${updatedUser.name}'); // 'New Name'
// Save the updated copy
await updatedUser.save();
// Equality comparison (uses primary key)
print('Same user: ${user == updatedUser}'); // true (same ID)
}
}
// 6. π Composite primary keys support
class UserRole extends Model with ActiveRecord {
@override
String get tableName => 'user_roles';
@override
List<String> get primaryKey => ['user_id', 'role_id']; // Composite primary key
int? get userId => get<int>('user_id');
set userId(int? value) => setAttribute('user_id', value);
int? get roleId => get<int>('role_id');
set roleId(int? value) => setAttribute('role_id', value);
}
Models & Migrations
// Database migrations
class CreateUsersTable extends Migration {
@override
Future<void> up() async {
await createTable('users', (table) {
table.id();
table.string('name', nullable: false);
table.string('email', nullable: false);
table.boolean('active', defaultValue: true);
table.timestamps();
table.unique(['email']);
table.index(['name']);
table.index(['active']);
});
}
@override
Future<void> down() async {
await dropTable('users');
}
}
class CreatePostsTable extends Migration {
@override
Future<void> up() async {
await createTable('posts', (table) {
table.id();
table.string('title', nullable: false);
table.text('content');
table.integer('user_id', nullable: false);
table.timestamps();
table.foreignKey('user_id', references: 'users', column: 'id');
table.index(['user_id']);
});
}
@override
Future<void> down() async {
await dropTable('posts');
}
}
// Many-to-many pivot table
class CreateUserCategoriesTable extends Migration {
@override
Future<void> up() async {
await createTable('user_categories', (table) {
table.integer('user_id', nullable: false);
table.integer('category_id', nullable: false);
table.timestamp('assigned_at', defaultValue: 'CURRENT_TIMESTAMP');
table.primaryKey(['user_id', 'category_id']); // Composite primary key
table.foreignKey('user_id', references: 'users', column: 'id');
table.foreignKey('category_id', references: 'categories', column: 'id');
});
}
@override
Future<void> down() async {
await dropTable('user_categories');
}
}
Database Features
- β Multiple Database Support: SQLite (production-ready), PostgreSQL, MySQL, MongoDB
- β οΈ Redis Support: Basic key-value operations (stub implementation - full version planned)
- β ACID Transactions: Full transaction support with automatic rollback
- β Query Builder: Type-safe, fluent query construction
- β Active Record Pattern: Easy model-based database operations
- β Repository Pattern: Clean separation of data access logic
- β Database Migrations: Version control for your database schema
- β Connection Pooling: Efficient database connection management
- β Security: Built-in SQL injection prevention
Task Scheduling #
Schedule background tasks with flexible timing:
import 'package:harpy/harpy.dart';
class CleanupTask extends Task {
CleanupTask() : super.periodic(
id: 'cleanup',
interval: Duration(hours: 1),
);
@override
Future<void> execute() async {
// Cleanup logic
print('Running cleanup...');
}
@override
void finalize() {
print('Cleanup task finalized');
}
}
void main() async {
final app = Harpy();
app.enableScheduler();
// Add periodic task
app.addTask(CleanupTask());
// Add scheduled task (runs at specific time)
app.addTask(Task.scheduled(
id: 'daily-report',
scheduled: DateTime.utc(2025, 10, 8, 9, 0), // 09:00 UTC
));
// Add instant task (runs once immediately)
app.addTask(Task.instant(
id: 'startup-init',
));
await app.listen(port: 3000);
}
Task Types:
- Periodic Tasks - Run at regular intervals (e.g., every hour)
- Scheduled Tasks - Run at specific times (e.g., daily at 9 AM)
- Instant Tasks - Run once immediately on startup
CLI Integration: Generate tasks using your project CLI:
dart run bin/myproject.dart task add cleanup
dart run bin/myproject.dart task list
See the Scheduler Documentation for more details.
Configuration #
Harpy supports flexible configuration management:
// From environment variables
final app = Harpy(); // Uses Configuration.fromEnvironment()
// From JSON file
final config = Configuration.fromJsonFile('config.json');
final app = Harpy(config: config);
// From map
final config = Configuration.fromMap({
'port': 8080,
'database': {'url': 'postgresql://localhost/mydb'},
});
final app = Harpy(config: config);
// Access configuration
final port = app.config.get<int>('port', 3000);
final dbUrl = app.config.get<String>('database.url');
final debug = app.config.get<bool>('debug', false);
// Required values (throws if missing)
final secret = app.config.getRequired<String>('jwt.secret');
Sub-routers #
Organize your routes with sub-routers:
// Create API router
final apiRouter = Router();
apiRouter.get('/users', getUsersHandler);
apiRouter.post('/users', createUserHandler);
apiRouter.get('/posts', getPostsHandler);
// Create admin router
final adminRouter = Router();
adminRouter.get('/stats', getStatsHandler);
adminRouter.delete('/users/:id', deleteUserHandler);
// Mount routers
app.mount('/api/v1', apiRouter);
app.mount('/admin', adminRouter);
// Routes will be available at:
// GET /api/v1/users
// POST /api/v1/users
// GET /api/v1/posts
// GET /admin/stats
// DELETE /admin/users/:id
Error Handling #
app.get('/error-example', (req, res) {
throw Exception('Something went wrong!');
// Automatically returns 500 with error details
});
// Custom error handling in middleware
shelf.Middleware errorHandler() {
return (shelf.Handler innerHandler) {
return (shelf.Request request) async {
try {
return await innerHandler(request);
} catch (error, stackTrace) {
print('Error: $error');
return shelf.Response.internalServerError(
body: jsonEncode({'error': error.toString()}),
headers: {'content-type': 'application/json'},
);
}
};
};
}
app.use(errorHandler());
Testing #
import 'package:test/test.dart';
import 'package:harpy/harpy.dart';
void main() {
group('API Tests', () {
late Harpy app;
setUp(() {
app = Harpy();
app.get('/test', (req, res) => res.json({'test': true}));
});
test('should register routes', () {
final routes = app.router.routes;
expect(routes.length, equals(1));
expect(routes.first.method, equals('GET'));
});
});
}
π CLI Tools #
Harpy includes a powerful CLI for project management:
# Create new project
harpy create my_api
# Show version
harpy version
# Help
harpy help
π Project Structure #
my_harpy_project/
βββ bin/
β βββ main.dart # Application entry point
βββ lib/
β βββ handlers/ # Route handlers
β βββ middleware/ # Custom middleware
β βββ models/ # Data models
β βββ services/ # Business logic
βββ test/ # Tests
βββ config.json # Configuration file
βββ pubspec.yaml
βββ README.md
π§ Advanced Usage #
Custom Server Configuration #
import 'dart:io';
final app = Harpy();
// Custom host and port
await app.listen(host: '0.0.0.0', port: 8080);
// HTTPS support
final context = SecurityContext()
..useCertificateChain('server.crt')
..usePrivateKey('server.key');
await app.listen(
host: 'localhost',
port: 443,
securityContext: context,
);
Environment-based Configuration #
Create a config.json file:
{
"port": 3000,
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"jwt": {
"secret": "your-secret-key",
"expiresIn": "24h"
}
}
Use environment variables (takes precedence over config files):
export PORT=8080
export DATABASE_HOST=prod-db.example.com
export JWT_SECRET=super-secret-key
π TODO #
High Priority #
- β Complete Redis Adapter Implementation
- Replace stub implementation with full Redis client integration
- Add support for all Redis data types (Strings, Lists, Sets, Sorted Sets, Hashes)
- Implement Redis-specific features (Pub/Sub, Lua scripts, Streams)
- Add connection pooling and cluster support
- Comprehensive testing suite
Medium Priority #
-
Enhanced Query Builder
- Add support for complex JOIN operations across all adapters
- Implement subquery support
- Add query optimization hints
-
Advanced ORM Features
- Model relationships (One-to-Many, Many-to-Many)
- Lazy loading and eager loading
- Model validation and serialization
- Schema synchronization
Low Priority #
-
Additional Database Adapters
- CouchDB support
- InfluxDB for time-series data
- Neo4j for graph databases
-
Performance Optimizations
- Query result caching
- Connection pool optimization
- Benchmark suite and performance monitoring
π€ Contributing #
We welcome contributions! Please see our Contributing Guide for details.
Development Setup #
git clone https://github.com/moses-team-ru/harpy.git
cd harpy
dart pub get
dart test
π License #
MIT License - see LICENSE file for details.
π Acknowledgments #
- Built on top of the excellent Shelf package
- Inspired by Express.js and other modern web frameworks
- Thanks to the Dart community for their support