Vania Framework

repository·dev·Indexed 42 days ago

https://github.com/vania-dart/framework

A batteries-included backend framework for Dart providing routing, validation, ORM, and migrations. Designed with a modular architecture and a strict separation of logic from transport, Vania includes specialized packages for JWT authentication (vania_auth), database drivers (vania_mysql, vania_postgresql, vania_mongodb), Redis (vania_redis), Elasticsearch (vania_elasticsearch), GraphQL (vania_graphql), gRPC (vania_grpc), and WebSockets (vania_websocket).

Tokens
125.1K
Snippets
451
Records
525
Agent score
49%

What's inside Vania

  1. Overview of Vania core features

    dev

    Vania is a batteries-included backend framework providing the following capabilities out of the box:

    • Routing: Named routes, groups, prefixes, parameter constraints, and resource routes.
    • Requests & Responses: Typed input access, file uploads, and support for JSON, HTML, files, and SSE.
    • Middleware: A handle / process pipeline where throwing an error rejects the request.
    • Validation: String rules, fluent rules, form-request classes, and custom rules.
    • ORM & Query Builder: Models, relations, eager loading, and a fluent query builder.
    • Migrations & Seeders: Schema builder, versioned migrations, and factories.
    • Sessions: Encrypted file-based sessions and CSRF protection.
    • Caching: Driver-based caching (file-based by default; Redis via vania_redis).
    • Mail: Mailables with envelopes, content, and view templates.
    • IoC Container: Constructor-free service resolution used throughout the framework.
  2. Understand the vdart project directory structure

    dev

    When using vania create, a new project is initialized with a standard directory structure. Understanding this layout is essential for knowing where to place configuration, routes, controllers, models, and migrations so the framework can correctly discover and execute your code.

    Core Directory Layout

    • bin/server.dart: The application entry point. This file initializes the Application instance and passes configuration to it.
    • lib/config/: Application-wide settings (e.g., app.dart, auth.dart, cors.dart).
    • lib/route/: Route definitions organized by type (web.dart for HTML, api_route.dart for JSON, web_socket.dart for WebSockets).
    • lib/app/providers/: Service providers (e.g., RouteServiceProvider) that manage application services.
    • lib/app/http/controllers/: Request handling logic (classes extending Controller).
    • lib/app/http/middleware/: Request/Response interceptors (classes extending Middleware).
    • lib/app/models/: ORM models (classes extending Model) mapping to database tables.
    • lib/database/migrations/: Database schema evolution files (classes extending Migration).
    • public/: Static assets (CSS, JS, images) served directly.
    • storage/app/: Default location for file uploads and application-generated files.
    • .env: Environment-specific secrets and settings.
    my_app/
    ├── bin/
    │   └── server.dart                 # Application entry point
    ├── lib/
    │   ├── config/
    │   │   ├── app.dart                # Master configuration map
    │   │   ├── auth.dart               # Authentication guard config
    │   │   └── cors.dart               # CORS settings
    │   ├── route/
    │   │   ├── api_route.dart          # API routes (prefixed with /api)
    │   │   ├── web.dart                # Web routes (HTML responses)
    │   │   └── web_socket.dart         # WebSocket routes
    │   ├── app/
    │   │   ├── providers/
    │   │   │   └── route_service_provider.dart
    │   │   ├── http/
    │   │   │   ├── controllers/
    │   │   │   │   └── home_controller.dart
    │   │   │   └── middleware/
    │   │   │       ├── authenticate.dart
    │   │   │       └── error_response_middleware.dart
    │   │   └── models/
    │   │       └── user.dart
    │   └── database/
    │       └── migrations/
    │           ├── create_user_table.dart
    │           └── migrate.dart        # Migration registry
    ├── public/                         # Static files served directly
    ├── storage/
    │   └── app/                        # Application file storage
    ├── .env                            # Environment configuration
    ├── pubspec.yaml
    ├── Dockerfile
    └── docker-compose.yml
  3. Counter Example Project Structure

    dev

    The Counter Example demonstrates how to separate business logic from HTTP concerns using the Vania framework. The core logic is decoupled from the routing and controller layers.

    lib/
      app/counter.dart                          # the counter logic (no HTTP)
      app/http/controllers/counter_controller.dart
      route/api_route.dart
      app/providers/route_service_provider.dart
      config/app.dart
    bin/server.dart
    test/counter_test.dart                       # unit tests for the counter
  4. Core features of Vania WebSocket

    dev

    The vania_websocket package includes the following capabilities:

    • Rooms: Lightweight groupings for broadcasting; supports joining, leaving, and listing members.
    • Channels: High-level groupings for organizing subscriptions.
    • Presence: Use WebSocketPresence() to track online users, receive change events, and send per-user messaging.
    • Connection Introspection: Monitor and manage connections via connectionCount, connectionIds, hasConnection, getConnectionInfo, close, and closeAll.
    • Middleware: Implement authentication or rate-limiting for socket connections before they are accepted.
  5. Features included in vania_elasticsearch

    dev

    The vania_elasticsearch package provides several tools for search integration:

    • Searchable mixin: Provides .searchable(), .unsearchable(), and .search() methods.
    • Query builder: Supports multi-match, match-all, and custom queries with pagination using from and size parameters.
    • Direct client: Provides low-level access via Elasticsearch.search(...), Elasticsearch.index(...), and bulk helpers.
    • Bulk indexing: Optimized helpers for backfilling large datasets.
  6. Configure Table Names and Primary Keys

    dev

    By default, table names are derived from the class name using snake_case and pluralization (e.g., BlogPost becomes blog_posts).

    Custom Table Names

    You can set a specific table name in the constructor using super.table() or use a tablePrefix.

    Custom Primary Keys

    To change the primary key from the default id (auto-incrementing integer), override primaryKey, keyType, and incrementing.

    class User extends Model {
      User() {
        super.table('app_users');
      }
    
      @override
      String get tablePrefix => 'app_'; // Results in table: app_users
    
      @override
      String get primaryKey => 'user_id';
    
      @override
      String get keyType => 'string'; // Use for UUIDs
    
      @override
      bool get incrementing => false;
    }
    class User extends Model {
      User() {
        super.table('app_users');
      }
    
      @override
      String get tablePrefix => 'app_';
    
      @override
      String get primaryKey => 'user_id';
    
      @override
      String get keyType => 'string';
    
      @override
      bool get incrementing => false;
    }
  7. Best practices for Redis usage

    dev

    When using Redis within the framework, follow these patterns:

    • Use Atomic Operations: Leverage Redis atomic commands like INCR to handle concurrency. This avoids race conditions inherent in read-modify-write cycles.
    • Namespace Your Keys: Always use a prefix for your keys (e.g., views:home instead of just home) to prevent data collisions if multiple applications share the same Redis instance.
    • Test with Fakes: Because your logic depends on an interface rather than the Redis client directly, you can test your application logic using in-memory fakes without requiring a running Redis server.
  8. How Controllers work in Vania

    dev

    Controllers are classes used to organize request handling logic. Each public method in a controller class corresponds to a specific route and must return a Response object. To use a controller, you should instantiate it as a top-level singleton in its file and then reference its methods in your router definitions.

    // 1. Define the controller
    class PostController extends Controller {
      Future<Response> index() async {
        return Response.json({'data': 'example'});
      }
    }
    
    // 2. Instantiate as a singleton
    final PostController postController = PostController();
    
    // 3. Reference in routes
    Router.get('/posts', postController.index);
  9. Understand the DDD Layering Pattern in Vania

    dev

    The DDD Wallet example demonstrates how to structure a Vania application using Domain-Driven Design (DDD) principles. The architecture follows the dependency rule where outer layers depend on inner ones, but inner layers never depend on outer ones.

    Layer Responsibilities:

    • Domain Layer (lib/domain/): The core of the application. It contains pure Dart code with no framework dependencies. It includes Value Objects (e.g., Money), Aggregate Roots (e.g., Wallet), Repository Interfaces, and Domain Exceptions.
    • Application Layer (lib/application/): Orchestrates use cases (e.g., wallet_service.dart). It handles the flow of data (load $\rightarrow$ act $\rightarrow$ save) but contains no business logic or rules.
    • Infrastructure Layer (lib/infrastructure/): Contains concrete implementations of domain abstractions, such as in_memory_wallet_repository.dart. This allows you to swap storage (e.g., from in-memory to a database) without changing the domain.
    • Interface Layer (lib/interface/http/): The entry point for external requests. It includes HTTP controllers and routes. The controller's job is to map requests/responses and translate domain errors into HTTP status codes.
    • App/Config Layer (lib/app/providers/, lib/config/): Handles framework wiring and dependency injection.
  10. Understand the Vania pattern of separating logic from transport

    dev

    A core architectural pattern in Vania is the strict separation of logic from transport.

    In this pattern, the core business logic (e.g., a Counter class, a ChatHub, or a Wallet aggregate) remains independent of how it is accessed. The HTTP or WebSocket layer acts only as a thin adapter on top of this logic.

    Benefits of this pattern:

    • Testability: You can run tests on your business logic without needing to spin up a server, a socket, or a database.
    • Flexibility: You can easily swap or add new transport layers (e.g., moving from REST to gRPC) without changing the core application logic.
  11. Implement Guard-Based Multi-Auth

    dev

    You can support multiple authentication contexts (e.g., 'users' and 'admins') by configuring additional guards.

    Use Auth().configureGuard(name, ...) to set up a new context with its own tokenStore and userProvider. To interact with a specific guard, use Auth().guard(name) instead of the default Auth() methods.

    // Configure multiple guards
    Auth().configureGuard('admin', 
      tokenStore: ModelPersonalAccessTokenStore(model: PersonalAccessToken()),
      userProvider: ModelUserProvider(model: Admin()),
    );
    
    // Login as admin
    Auth().guard('admin').login(adminUser);
    var token = await Auth().guard('admin').createToken();
    
    // Check admin auth
    Auth().guard('admin').check(token);