Skip to main content
Detailed technical architecture and design decisions behind the Zingat pastebin application.

System Overview

Zingat follows a modular architecture with clear separation of concerns:
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Web Server    │    │  Business Logic │    │   Database      │
│   (Rocket)      │◄──►│   (Rust Modules)│◄──►│   (SQLx)        │
└─────────────────┘    └─────────────────┘    └─────────────────┘
        │                       │                       │
        │                       │                       │
        ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Async Workers │    │   CLI Client    │    │   Migrations    │
│   (Tokio)       │    │   (Standalone)  │    │   (SQLx CLI)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘
The modular split allows independent iteration of web, business logic, and data layers.

Database Schema

Clips Table

CREATE TABLE IF NOT EXISTS clips (
    clip_id   TEXT PRIMARY KEY NOT NULL,
    shortcode TEXT UNIQUE NOT NULL,
    content   TEXT NOT NULL,
    title     TEXT,
    posted    DATETIME NOT NULL,
    expires   DATETIME,
    password  TEXT,
    hits      BIGINT NOT NULL
);

API Keys Table

CREATE TABLE IF NOT EXISTS api_keys (
    api_key BLOB PRIMARY KEY
);
The clips table stores all paste content with metadata. The hits field tracks view counts and is updated asynchronously via batched operations for performance.

Key Design Decisions

1. Batch Database Operations

Instead of logging each view directly into the database, Zingat defers these operations to separate threads. This design:
  • Reduces Database Contention: Multiple hits are batched into single database commits
  • Improves Performance: Minimizes database write operations
  • Scales Better: Handles high traffic more efficiently
Implementation:
// Views are collected in memory
let mut view_buffer: Vec<ViewRecord> = Vec::new();

// Periodically flushed to database
async fn flush_views_to_db(views: Vec<ViewRecord>) {
    // Batch insert operation
    sqlx::query!("INSERT INTO paste_views (paste_id, viewed_at, ip_address) VALUES (...)")
        .execute(&pool)
        .await;
}
Batching is most effective under burst traffic; tune flush intervals to balance latency and write load.

2. Async Architecture

Zingat uses Tokio for async processing, enabling:
  • Non-blocking I/O: Database operations don’t block the web server
  • High Concurrency: Handles thousands of simultaneous connections
  • Efficient Resource Usage: Minimal overhead for I/O-bound operations

3. Password Security

Passwords are hashed using Argon2 before storage:
fn hash_password(password: &str) -> Result<Strisng> {
    let salt = SaltString::generate(&mut OsRng);
    let argon2 = Argon2::default();
    let password_hash = argon2.hash_password(password.as_bytes(), &salt)?;
    Ok(password_hash.to_string())
}
Use Argon2 with strong parameters and unique salts; avoid fast hashes like MD5/SHA1 for passwords.

4. Shortcode Generation

Clip shortcodes are generated as unique identifiers for URL sharing. The system ensures uniqueness through database constraints and collision handling. s

5. Project Structure

Zingat follows a layered architecture with clear separation: Data Layer (src/lib/data/)
  • mod.rs: Database connection and configuration
  • model.rs: Database models and entities
  • query.rs: Database queries and operations
Domain Layer (src/lib/domain/)
  • clip/: Core business logic for clips
    • field/: Newtype wrappers for type safety (ClipId, Content, Expires, Hits, Password, Posted, Shortcode, Title)
    • mod.rs: Main Clip struct and ClipError enum
  • maintenance.rs: Background maintenance tasks
  • time.rs: Time-related utilities
Service Layer (src/lib/service/)
  • action.rs: Business logic actions
  • ask.rs: Request/response DTOs for API operations
Web Layer (src/lib/web/)
  • api.rs: REST API endpoints and authentication
  • ctx.rs: Template context structures
  • form.rs: Form handling and validation
  • hitcounter.rs: Asynchronous hit counting system
  • http.rs: Web UI routes and handlers
  • renderer.rs: Template rendering engine
The newtype pattern is used extensively for type safety, preventing accidental mixing of different string types (e.g., ClipId vs Shortcode).

Performance Optimizations

Database Connection Pooling

SQLx connection pooling ensures efficient database resource usage:
let pool = SqlitePoolOptions::new()
    .max_connections(10)
    .connect(&database_url)
    .await?;
Right-size max_connections for your database; too high can degrade performance via context switching.

Memory Management

Rust’s ownership model prevents common issues:
  • No memory leaks
  • No buffer overflows
  • Thread safety guaranteed at compile time

Hit Counter Batching

Hit counting uses an asynchronous batching system:
  • Hits are deferred to separate threads
  • Multiple hits are batched into single database commits
  • Significantly improves performance and reduces database contention
  • Implemented via hitcounter.rs module with crossbeam channels

Security Architecture

Input Validation

All inputs are rigorously validated:
fn validate_clip_content(content: &str) -> Result<()> {
    if content.is_empty() {
        return Err(ClipError::EmptyContent);
    }
    // Additional validation logic
    Ok(())
}

Error Handling

Zingat uses comprehensive error types: ClipError Variants:
  • NotFound: Clip doesn’t exist
  • AlreadyExists: Shortcode collision
  • Expired: Clip has passed expiration date
  • InvalidPassword: Wrong password provided
  • InvalidTitle: Title validation failed
  • EmptyContent: Content cannot be empty
  • InvalidDate: Date parsing error
API Errors:
  • NotFound: Resource not found (404)
  • Server: Internal server error (500)
  • User: Client error (401)
  • KeyError: API key issues (400)
  • Abuse: brute force and high-frequency scraping
  • Leakage: unintended access to protected pastes
  • Integrity: tampering with stored content
  • Rate limiting for abuse
  • Hashed secrets for password protection
  • Prepared statements for injection prevention

Rate Limiting

IP-based rate limiting prevents abuse:
struct RateLimiter {
    requests: Mutex<HashMap<String, Vec<Instant>>>,
}

SQL Injection Prevention

SQLx uses prepared statements, preventing SQL injection:
sqlx::query!("SELECT * FROM clips WHERE shortcode = ?", shortcode)
    .fetch_one(&pool)
    .await;

Deployment Architecture

Containerization

Docker support for easy deployment:
FROM rust:1.56 as builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bullseye-slim
COPY --from=builder /app/target/release/httpd /usr/local/bin/
CMD ["httpd"]

Cloud Deployment

Support for multiple platforms:
  • Render: render.yaml configuration
  • Railway: railway.toml configuration
  • Docker: Dockerfile included

Monitoring and Logging

Structured Logging

JSON-formatted logs for easy parsing:
#[derive(Serialize)]
struct AccessLog {
    timestamp: DateTime<Utc>,
    method: String,
    path: String,
    status: u16,
    duration_ms: u64,
}

Health Checks

Endpoint for monitoring application health:
#[get("/health")]
async fn health_check() -> &'static str {
    "OK"
}

Future Enhancements

Planned architectural improvements:
  • Redis Integration: For better caching and session management
  • CDN Support: For static asset delivery
  • Microservices: Split into smaller, focused services
  • GraphQL API: Alternative to REST API
This architecture provides a solid foundation for a secure, high-performance pastebin service that can scale to handle significant traffic while maintaining security and reliability.