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
- Installation
- Your First Application
- Understanding the Generated Application
- Understanding the Basics
- Next Steps
Prerequisites
Before you begin, ensure you have the following installed:
V Language: Version 0.4.12 or higher
v versionPostgreSQL: Version 12 or higher (optional for database features)
psql --versionGit: For version control
git --versionLinux 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 setupconfig.v- Configuration structs and loadingmiddleware.v- Middleware configurationroutes.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
mainmodule 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.developmentreloads templates on each request,productioncaches 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 = truein production to require HTTPS http_only = trueprevents JavaScript from accessing the session cookiesame_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:
- Routing Guide - Deep dive into routing, parameters, and groups
- Middleware Guide - Learn about middleware and custom middleware
- Controllers & MVC Guide - Organize applications with MVC
- Database Guide - Work with PostgreSQL databases
- Templates Guide - Render HTML with VeeMarker templates
- Sessions & Auth Guide - User sessions and authentication
- Production Features Guide - Rate limiting, CSRF, compression, uploads
- 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!