Getting Started with Varel

Varel is a modern, fast, and thoughtfully-designed web framework for the V programming language. Inspired by Ruby's Roda framework and optimized for V's strengths, Varel provides a clean, modular architecture for building web applications with simplicity, flexibility, and performance.

Table of Contents


Prerequisites

Before you begin, ensure you have the following installed:

  • V Language: Version 0.4.12 or higher

    v version
    
  • PostgreSQL: Version 12 or higher (optional for database features)

    psql --version
    
  • Git: For version control

    git --version
    
  • Linux Environment: Ubuntu 22.04+ recommended (Varel is Linux-first)

Installing V

If you don't have V installed:

# Clone V repository
git clone https://github.com/vlang/v
cd v
make

# Create symlink (adds to PATH)
sudo ./v symlink

# Verify installation
v version
# Output: V 0.4.12 dev

Installation

Step 1: Clone Varel Repository

# Clone Varel from GitHub
git clone https://github.com/leafscale/varel.git
cd varel

Step 2: Compile Varel CLI

# Compile Varel CLI tool
v varel.v

# Or in production mode (optimized)
v -prod varel.v

# This creates the 'varel' executable
ls -lh varel
# Output: -rwxr-xr-x 1 user user 3.9M Oct 17 varel

Step 3: Install Varel Globally

# Create symlink to make 'varel' command available globally
./varel symlink

# Output: ✓ Symlink created: /usr/local/bin/varel

# Verify installation
varel version
# Output: Varel version 0.4.0

# View help
varel --help

The varel command is now available globally!


Your First Application

Let's create a new Varel application using the CLI.

Step 1: Create New Project

# Create a new Varel project
varel new myapp

# Output:
# Creating new Varel project: myapp
# App name: myapp
#
# Initializing Varel project in /home/user/myapp...
#   Created directory structure
#   Created main.v
#   Created config.v
#   Created middleware.v
#   Created routes.v
#   Created v.mod
#   Created config/config.toml
#   Created controllers/home_controller.v
#   Created controllers/api_controller.v
#   Created README.md
#   Created PROJECT_STRUCTURE.md
#
# ✓ Project myapp created successfully!
#
# Get started:
#   cd myapp
#   v run main.v
#
# Visit: http://localhost:8080

Step 2: Explore Your Application

cd myapp
ls -la

# Directory structure:
# myapp/
# ├── .gitignore               # Git ignore file
# ├── main.v                   # Application entry point (bootstrap)
# ├── config.v                 # Configuration structs and loading
# ├── middleware.v             # Middleware configuration
# ├── routes.v                 # All route definitions
# ├── v.mod                    # V module definition
# ├── README.md                # Project README
# ├── PROJECT_STRUCTURE.md     # Structure documentation
# ├── config/
# │   └── config.toml          # Configuration file
# ├── controllers/
# │   ├── home_controller.v    # Home page controller
# │   └── api_controller.v     # API controller
# ├── models/                  # Database models (empty initially)
# ├── middleware/              # Custom middleware (empty initially)
# ├── views/                   # VeeMarker templates
# │   ├── layouts/
# │   │   └── base.html.vtpl   # Default layout
# │   ├── home/
# │   │   ├── index.html.vtpl  # Home page
# │   │   └── hello.html.vtpl  # Hello page
# │   └── shared/
# │       ├── header.html.vtpl # Header partial
# │       └── footer.html.vtpl # Footer partial
# ├── db/
# │   ├── migrations/          # Database migrations
# │   └── seeds/               # Seed data
# ├── public/
# │   ├── css/                 # Stylesheets
# │   ├── js/                  # JavaScript
# │   └── images/              # Images
# ├── storage/
# │   ├── logs/                # Application logs
# │   └── uploads/             # User uploads
# ├── scaffold/                # Generated scaffold templates
# │   ├── controllers/         # Controller templates
# │   ├── models/              # Model templates
# │   ├── views/               # View templates
# │   ├── migrations/          # Migration templates
# │   └── tests/               # Test templates
# └── tests/                   # Test files
#     ├── unit/
#     └── integration/

Step 3: Run Your Application

# Run the application
v run main.v

# Output:
# 🚀 Starting myapp...
#    Environment: development
#    Visit: http://localhost:8080
#    Health: http://localhost:8080/health

Step 4: Test Your Application

Open your browser and visit:

  • http://localhost:8080/ - Welcome page with links to examples
  • http://localhost:8080/hello/world - Personalized greeting: "Hello, world!"
  • http://localhost:8080/api/v1/status - JSON API endpoint
  • http://localhost:8080/health - Health check endpoint

Congratulations! You've just created your first Varel web application! 🎉


Understanding the Generated Application

Let's explore what the varel new command generated for you.

Important: Varel organizes the main module across four files for better code organization:

  • main.v - Application entry point and setup
  • config.v - Configuration structs and loading
  • middleware.v - Middleware configuration
  • routes.v - All route definitions

These files work together as part of the main module - no imports needed between them!

main.v - Application Entry Point

The generated main.v contains the bootstrap code:

module main

import leafscale.varel.app as varel_app
import leafscale.varel.vareldb
import leafscale.varel.templates
import os

fn main() {
    config_path := parse_args()
    cfg := load_config(config_path) or {
        eprintln('Error loading config: ${err}')
        exit(1)
    }

    mut app := setup_app(cfg) or {
        eprintln('Error setting up app: ${err}')
        exit(1)
    }
    start_server(mut app, cfg)
}

fn setup_app(cfg Config) !&varel_app.App {
    mut app := varel_app.new(cfg.app.name)

    // Configure database
    app.database(vareldb.Config{ ... })

    // Configure templates
    app.templates(templates.RendererConfig{ ... })

    // Configure middleware (defined in middleware.v)
    configure_middleware(mut app)

    // Configure static files
    app.static('/static', 'public')!

    // Register routes (defined in routes.v)
    register_routes(mut app)!

    return app
}

Key Features:

  • Clean entry point - focuses on application bootstrap
  • Delegates to other files for configuration, middleware, and routes
  • Command-line argument parsing (--config, --help, --version)
  • Database and template configuration
  • Calls functions defined in other main module files

config.v - Configuration Management

Configuration structs and TOML loading logic live here:

module main

import os
import toml

struct Config {
pub mut:
    app      AppConfig
    server   ServerConfig
    database DatabaseConfig
}

fn load_config(path string) !Config {
    // TOML file parsing
    // Environment variable overrides
    // Returns populated Config struct
}

Benefits:

  • All configuration code in one place
  • Easy to add new config sections
  • Separates config logic from application logic

middleware.v - Middleware Stack

Middleware configuration is defined here:

module main

import leafscale.varel.app as varel_app
import leafscale.varel.middleware

fn configure_middleware(mut app &varel_app.App) {
    app.use(middleware.logger_default())
    app.use(middleware.recovery_default())
    app.use(middleware.method_override())
    app.use(middleware.request_id())
}

Benefits:

  • Clear middleware ordering
  • Easy to add/remove middleware
  • Keeps main.v focused on setup

routes.v - Application Routes

All route definitions live in this file:

module main

import leafscale.varel.app as varel_app
import leafscale.varel.http
import controllers

fn register_routes(mut app &varel_app.App) ! {
    mut home_ctrl := controllers.HomeController{}
    mut api_ctrl := controllers.ApiController{}

    // Home routes
    app.get('/', fn [mut home_ctrl] (mut ctx http.Context) http.Response {
        return home_ctrl.index(mut ctx)
    })!

    app.get('/hello/:name', fn [mut home_ctrl] (mut ctx http.Context) http.Response {
        return home_ctrl.hello(mut ctx)
    })!

    // API routes
    mut api := app.group('/api/v1')
    api.use(middleware.cors_default())
    api.get('/status', fn [mut api_ctrl] (mut ctx http.Context) http.Response {
        return api_ctrl.status(mut ctx)
    })!
}

Benefits:

  • Routes serve as a map of your HTTP interface
  • All routes in one place - easy to understand URL structure
  • Scales well as your application grows
  • Clear separation from business logic

Why Multiple Files?

Organization: As your application grows, routes.v will become your routing map, making it easy to see the entire HTTP interface at a glance.

No Imports Needed: All files are part of the main module, so they can call each other's functions without imports.

V Language Feature: When you run v . or v run ., V automatically compiles all .v files in the directory together

config/config.toml - Configuration

[app]
name = "myapp"
env = "development"  # Affects template caching (dev = reload, prod = cache)

[server]
host = "0.0.0.0"          # 0.0.0.0 = all interfaces, 127.0.0.1 = localhost only
port = "8080"
read_timeout_secs = 30    # Request read timeout in seconds
write_timeout_secs = 30   # Response write timeout in seconds
max_read = 5000000        # 5MB for file uploads
max_write = 16384         # 16KB for responses

[database]
host = "localhost"
port = 5432
database = "myapp_dev"
user = "postgres"
password = ""
sslmode = "prefer"
connect_timeout = 30

[session]
cookie_name = "myapp_session"
max_age = 86400           # Session lifetime in seconds (24 hours)
secure = false            # Set to true in production (HTTPS only)
http_only = true          # Prevent JavaScript access to session cookie
same_site = "strict"      # CSRF protection

Configuration Notes:

  • env: Controls template caching. development reloads templates on each request, production caches them for performance.
  • max_read: Maximum request body size (default 5MB). Increase for large file uploads.
  • max_write: Maximum response size (default 16KB). Increase for large responses.
  • session: Sessions are PostgreSQL-backed. The cookie stores only the session ID (64-char hex). All session data is stored in PostgreSQL as JSONB. Database connection is required for sessions to work.

Session Security:

  • Cookie stores session ID only (not encrypted data)
  • All session data stored in PostgreSQL with JSONB column
  • Set secure = true in production to require HTTPS
  • http_only = true prevents JavaScript from accessing the session cookie
  • same_site = "strict" provides CSRF protection

controllers/home_controller.v - Home Controller

module controllers

import leafscale.varel.http
import leafscale.varel.controller

pub struct HomeController {
    controller.Controller
}

pub fn (mut c HomeController) index(mut ctx http.Context) http.Response {
    // Render home page template
    return ctx.render('home/index.html', {
        'app_name': 'myapp'
        'title': 'myapp - Home'
    })
}

pub fn (mut c HomeController) hello(mut ctx http.Context) http.Response {
    name := ctx.param('name')
    // Render hello template with name parameter
    return ctx.render('home/hello.html', {
        'app_name': 'myapp'
        'name': name
        'title': 'Hello ${name} - myapp'
    })
}

Note: The generated controller uses VeeMarker templates instead of inline HTML. Templates are located in the views/ directory and provide a cleaner separation of concerns.

controllers/api_controller.v - API Controller

module controllers

import leafscale.varel.http
import leafscale.varel.controller

pub struct ApiController {
    controller.Controller
}

pub fn (mut c ApiController) status(mut ctx http.Context) http.Response {
    return ctx.json_response(200, {
        'app': 'myapp'
        'status': 'running'
        'version': '1.0.0'
    })
}

Understanding the Basics

Let's break down the core concepts you'll use daily in Varel.

1. Application Instance

import leafscale.varel.app as varel_app

mut app := varel_app.new('My App')

Creates a new Varel application. The name is used for logging and identification.

2. Routing

// GET request
app.get('/path', handler)!

// POST request
app.post('/path', handler)!

// PUT request
app.put('/path', handler)!

// DELETE request
app.delete('/path', handler)!

// Route with parameters
app.get('/users/:id', user_handler)!

// Route with multiple parameters
app.get('/posts/:id/comments/:comment_id', comment_handler)!

3. URL Parameters

Extract dynamic values from URLs:

import leafscale.varel.http

fn user_handler(mut ctx http.Context) http.Response {
    user_id := ctx.param('id')
    // Convert to integer
    id := user_id.int()

    return ctx.text(200, 'User ID: ${id}')
}

4. Query Strings

Access URL query parameters:

import leafscale.varel.http

fn search_handler(mut ctx http.Context) http.Response {
    // ?q=search&page=2
    query := ctx.query('q') or { '' }
    page := ctx.query('page') or { '1' }

    return ctx.text(200, 'Search: ${query}, Page: ${page}')
}

5. Response Helpers

Varel provides many response helpers:

// Render template (VeeMarker - configured by default!)
return ctx.render('home/index.html', {
    'title': 'Welcome'
    'message': 'Hello, World!'
})

// HTML response
return ctx.html(200, '<h1>Hello</h1>')

// Plain text
return ctx.text(200, 'Hello, World!')

// JSON response
return ctx.json_response(200, {
    'message': 'Success'
    'data': [1, 2, 3]
})

// Redirect
return ctx.redirect('/new-path')

// Common status codes
return ctx.ok('Success')           // 200
return ctx.created('Created')      // 201
return ctx.bad_request('Invalid')  // 400
return ctx.unauthorized('Login')   // 401
return ctx.forbidden('No Access')  // 403
return ctx.not_found('Not Found')  // 404
return ctx.internal_error('Error') // 500

6. Request Context

The Context provides access to all request data:

import leafscale.varel.http

fn handler(mut ctx http.Context) http.Response {
    // HTTP method
    method := ctx.method()  // GET, POST, etc.

    // Path
    path := ctx.path()  // /hello/world

    // Headers
    user_agent := ctx.header('User-Agent')
    content_type := ctx.header('Content-Type')

    // Cookies
    session_id := ctx.cookie('session_id') or { '' }

    // Form data (POST requests)
    username := ctx.form('username')
    password := ctx.form('password')

    // JSON body
    struct User {
        username string
        email    string
    }
    user := ctx.json_body<User>() or { User{} }

    return ctx.ok('Processed')
}

7. Middleware

Add middleware to run before your handlers.

File: middleware.v - Configure global middleware here:

module main

import leafscale.varel.app as varel_app
import leafscale.varel.middleware

fn configure_middleware(mut app &varel_app.App) {
    // Global middleware (runs on all routes)
    app.use(middleware.logger_default())
    app.use(middleware.recovery_default())
    app.use(middleware.cors_default())

    // Add more middleware as needed:
    // app.use(middleware.rate_limit_default())
    // app.use(middleware.compression_default())
}

Note: This function is automatically called from setup_app() in main.v. Just add your middleware here!

8. Route Groups

Group routes with shared middleware or path prefix.

File: routes.v - Add route groups inside register_routes():

module main

import leafscale.varel.app as varel_app
import leafscale.varel.http
import leafscale.varel.middleware

fn register_routes(mut app &varel_app.App) ! {
    // Admin routes (require authentication)
    mut admin := app.group('/admin')
    admin.use(middleware.auth_required())
    admin.get('/dashboard', admin_dashboard)!
    admin.get('/users', admin_users)!

    // API routes (rate limited)
    mut api := app.group('/api/v1')
    api.use(middleware.rate_limit_default())
    api.get('/users', api_users)!
    api.post('/users', api_create_user)!
}

Note: This function is automatically called from setup_app() in main.v. All your routes go here!


CLI Commands

Create New Project

# Create new application
varel new myapp

# Initialize Varel in existing directory
cd existing_project
varel init

Generate Code

# Generate migration
varel generate migration create_users

# Generate scaffold (complete resource)
varel generate scaffold Product name:string price:decimal stock:int

Database Commands

# Create database
varel db create

# Run migrations
varel db migrate

# Rollback last migration
varel db rollback

# Check migration status
varel db status

# Drop database (careful!)
varel db drop

User Management

# Create user
varel user create admin password123

# List users
varel user list

# Delete user
varel user delete admin

Configuration

config.toml

The generated config/config.toml controls your application:

[app]
name = "myapp"           # Application name
env = "development"      # Environment: development, production, test

[server]
host = "0.0.0.0"         # Listen host (0.0.0.0 = all interfaces)
port = "8080"            # Listen port
read_timeout_secs = 30   # Request read timeout in seconds
write_timeout_secs = 30  # Response write timeout in seconds
max_read = 5000000       # 5MB for file uploads
max_write = 16384        # 16KB for responses

[database]
host = "localhost"
port = 5432
database = "myapp_dev"
user = "postgres"
password = ""
sslmode = "prefer"
connect_timeout = 30     # Connection timeout in seconds

# Note: v0.4.0 uses single-process architecture
# One database connection per application instance

Environment Variables

Override configuration with environment variables:

# Override port
PORT=3000 v run main.v

# Override environment
VAREL_ENV=production v run main.v

# Override database password
DB_PASSWORD=secret v run main.v

# Multiple environment variables
VAREL_ENV=production PORT=8080 DB_PASSWORD=secret v run main.v

Command-Line Arguments

# Use custom config file
v run main.v --config config/production.toml

# View help
v run main.v --help

# View version
v run main.v --version

Common Patterns

1. JSON API

import leafscale.varel.http

struct User {
    id       int
    username string
    email    string
}

fn api_users(mut ctx http.Context) http.Response {
    users := [
        User{id: 1, username: 'alice', email: 'alice@example.com'},
        User{id: 2, username: 'bob', email: 'bob@example.com'},
    ]

    return ctx.json_response(200, users)
}

fn api_create_user(mut ctx http.Context) http.Response {
    user := ctx.json_body<User>() or {
        return ctx.bad_request('Invalid JSON')
    }

    // Validate
    if user.username == '' {
        return ctx.bad_request('Username required')
    }

    // Save to database...

    return ctx.created(user)
}

2. Form Handling

import leafscale.varel.http

fn show_form(mut ctx http.Context) http.Response {
    html := '<form action="/submit" method="POST">
        <input type="text" name="username" placeholder="Username" required>
        <input type="email" name="email" placeholder="Email" required>
        <button type="submit">Submit</button>
    </form>'

    return ctx.html(200, html)
}

fn submit_form(mut ctx http.Context) http.Response {
    username := ctx.form('username')
    email := ctx.form('email')

    // Validate
    if username == '' || email == '' {
        return ctx.bad_request('All fields required')
    }

    // Process...

    return ctx.redirect('/success')
}

3. Error Handling

import leafscale.varel.http

fn handler(mut ctx http.Context) http.Response {
    // Try operation that might fail
    user := get_user(123) or {
        return ctx.not_found('User not found')
    }

    // Try another operation
    result := process_user(user) or {
        eprintln('Error: ${err}')
        return ctx.internal_error('Processing failed')
    }

    return ctx.json_response(200, result)
}

Next Steps

Now that you understand the basics, explore these guides:

  1. Routing Guide - Deep dive into routing, parameters, and groups
  2. Middleware Guide - Learn about middleware and custom middleware
  3. Controllers & MVC Guide - Organize applications with MVC
  4. Database Guide - Work with PostgreSQL databases
  5. Templates Guide - Render HTML with VeeMarker templates
  6. Sessions & Auth Guide - User sessions and authentication
  7. Production Features Guide - Rate limiting, CSRF, compression, uploads
  8. Deployment Guide - Deploy with Caddy and systemd

Getting Help

  • Documentation: Complete guides in docs/guides/
  • API Reference: See docs/api/ for detailed API docs
  • Examples: Check examples/ directory for full applications
  • GitHub Issues: Report bugs or request features at https://github.com/leafscale/varel
  • Discord: Join the V language Discord for community support

Summary

You've learned:

✅ How to install Varel CLI ✅ Creating new applications with varel new ✅ Generated project structure ✅ Basic routing with parameters ✅ Response helpers (HTML, JSON, text, redirect) ✅ Request context and accessing data ✅ Middleware and route groups ✅ Configuration with TOML and environment variables ✅ CLI commands for scaffolding and database ✅ Common patterns (JSON APIs, forms, error handling)

Continue to the Routing Guide to learn more about Varel's powerful routing system!