Tutorial: Build a URL Shortener with Varel
Difficulty: Beginner Time: 45-60 minutes Prerequisites: V language basics, basic web development knowledge
📝 Tutorial Updated: November 2025 - Now teaches layout-based templates for cleaner, more maintainable code. All URL views use Varel's layout system instead of full HTML templates.
What You'll Learn
In this hands-on tutorial, you'll build a complete URL shortener application with Varel. Along the way, you'll learn:
- How to create a new Varel application
- Using the scaffold generator to create resources quickly
- Working with PostgreSQL databases and migrations
- Building interactive UIs with Alpine.js (no build step!)
- Using Varel's Alpine.js components (@copyButton, @toastContainer)
- Server-side rendering with VeeMarker templates
- Form validation and error handling
What You'll Build:
A URL shortener that lets users:
- Submit long URLs and get short codes (like "abc123")
- Copy shortened URLs to clipboard with one click
- See success/error messages with toast notifications
- View all shortened URLs in a list
- Track click counts for each URL
Prerequisites
Before starting, ensure you have:
V compiler installed (v0.4.12+)
v versionPostgreSQL installed and running (12+)
psql --versionVarel CLI installed (see Getting Started)
varel version
Step 1: Create the Project
Let's create a new Varel application for our URL shortener.
# Navigate to your apps directory (or wherever you want to store your projects)
mkdir ~/varel-apps && cd ~/varel-apps
# Create new Varel project
varel new urlshortener
# Navigate into the project
cd urlshortener
What just happened?
The varel new command created a complete project structure:
main.v- Application entry pointconfig/config.toml- Database and server configurationcontrollers/- Request handlers (we'll add our logic here)views/- VeeMarker templates (HTML with Alpine.js)models/- Database modelsdb/migrations/- Database schema changesstatic/- CSS, JavaScript, images
Explore the structure:
ls -la
You should see all these directories. Take a moment to peek inside main.v to see how a Varel app starts.
Step 2: Configure the Database
Important: For best practices, you should create a database user with access only to this database. for example:
sudo su - postgres
createuser -dSRP varelapp
Open config/config.toml in your editor and update the database settings for your postgresql server:
[database]
host = "localhost"
port = 5432
database = "urlshortener_dev"
user = "varelapp"
password = "" # Add your PostgreSQL password if needed
sslmode = "prefer"
connect_timeout = 30
Create the database:
varel db create
You should see:
✓ Database urlshortener_dev created successfully!
What's happening?
Varel connects to PostgreSQL using the config and creates a new database for your app. Each Varel app should gets its own database to keep data isolated and protected.
Step 3: Generate the URL Resource
Now for the magic! We'll use Varel's scaffold generator to create everything we need for managing URLs.
varel generate scaffold Url \
original_url:text \
short_code:string \
clicks:int
Watch the output:
Generating scaffold for Url...
Created controllers/url.v
Created models/url.v
Created db/migrations/20251104_create_url.up.sql
Created db/migrations/20251104_create_url.down.sql
Created views/url/index.vtpl
Created views/url/show.vtpl
Created views/url/new.vtpl
Created views/url/edit.vtpl
Created tests/controllers/url_test.v
✓ Scaffold generated successfully!
What did we just create?
- Controller (
controllers/url.v) - 7 RESTful actions (index, show, new, create, edit, update, destroy) - Model (
models/url.v) - Database operations (create, find, update, delete) - Views (4 templates) - HTML pages for listing, viewing, creating, editing URLs
- Migrations (2 SQL files) - Database schema (up = create table, down = drop table)
- Tests - Controller tests for all 7 actions
Peek at the migration:
cat db/migrations/*create_url.up.sql
You'll see a PostgreSQL table definition with your fields plus automatic id, created_at, updated_at columns!
Step 4: Run the Migration
Apply the database schema we just generated:
varel db migrate
You should see something similar to the following output:
Running 1 migration(s)...
Migrating: 20251105171507_create_url
Migrated: 20251105171507_create_url
All migrations completed successfully
(Optional) Verify the table was created:
PGPASSWORD='' psql -U postgres -d urlshortener_dev -c '\d url'
(Replace '' with your password if needed)
You should see the table structure with columns: id, original_url, short_code, clicks, created_at, updated_at.
Table "public.url"
Column | Type | Collation | Nullable | Default
--------------+-----------------------------+-----------+----------+---------------------------------
id | integer | | not null | nextval('url_id_seq'::regclass)
original_url | text | | not null |
short_code | character varying(255) | | not null |
clicks | integer | | not null |
created_at | timestamp without time zone | | not null | CURRENT_TIMESTAMP
updated_at | timestamp without time zone | | not null | CURRENT_TIMESTAMP
Indexes:
"url_pkey" PRIMARY KEY, btree (id)
Step 5: Register Routes
Open routes.v in your editor. This file centralizes all route definitions. The import controllers is already included at the top of the file from the template.
First, add route wrapper functions (add these after the existing route wrapper functions, around line 24):
// URL shortener route wrappers
fn route_url_index(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.index(mut ctx)
}
fn route_url_new(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.new(mut ctx)
}
fn route_url_create(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.create(mut ctx)
}
fn route_url_show(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.show(mut ctx)
}
fn route_url_edit(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.edit(mut ctx)
}
fn route_url_update(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.update(mut ctx)
}
fn route_url_destroy(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.destroy(mut ctx)
}
Then, inside the register_routes() function, add the URL routes in the "Add Your Routes Below" section (after the health check, before the commented examples around line 63):
// ========================================
// URL Shortener Routes
// ========================================
// IMPORTANT: More specific routes must come before parameterized routes
// /url/new must be registered before /url/:id
app.get('/url/new', route_url_new)!
app.get('/url', route_url_index)!
app.post('/url', route_url_create)!
app.get('/url/:id', route_url_show)!
app.get('/url/:id/edit', route_url_edit)!
app.put('/url/:id', route_url_update)!
app.delete('/url/:id', route_url_destroy)!
⚠️ Important: Route Order Matters!
Notice that /url/new is registered before /url/:id. This is critical because:
- Routes are matched in the order they're registered
- If
/url/:idcame first, the router would match/url/newthinking "new" is an ID - Always register static paths (like
/new) before parameterized paths (like/:id)
Understanding the routes:
| HTTP Method | Path | Purpose | Controller Action |
|---|---|---|---|
| GET | /url |
List all URLs | index() |
| GET | /url/new |
Show create form | new() |
| POST | /url |
Submit create form | create() |
| GET | /url/:id |
View single URL | show() |
| GET | /url/:id/edit |
Show edit form | edit() |
| PUT | /url/:id |
Submit edit form | update() |
| DELETE | /url/:id |
Delete URL | destroy() |
This is the standard RESTful CRUD pattern used by Rails, Laravel, and Django. Each route maps to a controller action that handles that specific HTTP request.
Step 5.5: Add URLs to Navigation
Let's add a navigation link to access our URL shortener from anywhere in the app.
Open views/shared/header.vtpl and find the <nav> section (around line 16). Add the URLs link after the Home link:
<nav>
<a href="/" style="color: white; margin-left: 20px; padding: 8px 16px; background: rgba(77, 208, 225, 0.15); border: 1px solid rgba(77, 208, 225, 0.3); border-radius: 5px; transition: all 0.3s; text-decoration: none;">Home</a>
<a href="/url" style="color: white; margin-left: 10px; padding: 8px 16px; background: rgba(77, 208, 225, 0.15); border: 1px solid rgba(77, 208, 225, 0.3); border-radius: 5px; transition: all 0.3s; text-decoration: none;">URLs</a>
<a href="/hello/world" style="color: white; margin-left: 10px; padding: 8px 16px; background: rgba(77, 208, 225, 0.15); border: 1px solid rgba(77, 208, 225, 0.3); border-radius: 5px; transition: all 0.3s; text-decoration: none;">Hello</a>
<a href="/api/v1/status" style="color: white; margin-left: 10px; padding: 8px 16px; background: rgba(77, 208, 225, 0.15); border: 1px solid rgba(77, 208, 225, 0.3); border-radius: 5px; transition: all 0.3s; text-decoration: none;">API</a>
</nav>
What did we do?
Added a "URLs" link to the header navigation. Because header.vtpl is included in the layout (layouts/base.vtpl), this navigation will appear on every page that uses the layout!
This is one of the key benefits of layouts - make one change, see it everywhere.
Step 6: Test the Basic App
Let's see what we've built so far!
varel serve
You should see output like:
Starting Varel development server...
🚀 Starting urlshortener...
Environment: development
Listen: :8080
Visit: http://localhost:8080
Health: http://localhost:8080/health
Open your browser: http://localhost:8080/url/new
You should see a form with three fields:
- Original URL
- Short Code
- Clicks
Try creating a URL:
- Original URL:
https://www.google.com - Short Code:
google - Clicks:
0 - Click "Create Url"
You should be redirected to the detail page showing your URL!
Stop the server: Press Ctrl+C in your terminal.
Step 6.5: Understanding Layouts
Before we continue, let's understand how Varel's layout system works. This will help you write cleaner, more maintainable templates.
What are Layouts?
Layouts are template wrappers that provide consistent structure across all pages. Instead of repeating the <!DOCTYPE html>, <head>, and <body> tags in every view, you write them once in a layout file.
Your app already has a layout file: views/layouts/base.vtpl
This layout includes:
- HTML document structure (DOCTYPE, head, body)
- CSS and JavaScript includes
- Shared header and footer
- A
${content}placeholder where your view content goes
How ctx.render_data() Works
When you call ctx.render_data('url/show.vtpl', data) in your controller:
- Varel renders your view template (
url/show.vtpl) - It wraps that content in the layout (
layouts/base.vtpl) - The
${content}variable in the layout is replaced with your view's HTML
This means your view templates should contain ONLY the page-specific content - no DOCTYPE, no <head>, no <body> tags!
Example: Layout + View
Layout (views/layouts/base.vtpl):
<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
<#include "shared/header.vtpl">
<main>
${content} <!-- Your view goes here -->
</main>
<#include "shared/footer.vtpl">
</body>
</html>
View (views/url/show.vtpl):
<div class="container">
<h1>URL Details</h1>
<p>Short code: ${url.short_code}</p>
</div>
Result: A complete HTML page with header, footer, and your content!
Benefits
✅ DRY (Don't Repeat Yourself) - Write DOCTYPE/head/body once ✅ Consistency - All pages have the same structure ✅ Easy Updates - Change header/footer in one place ✅ Clean Views - Focus on page content, not boilerplate
Step 7: Add Alpine.js Interactivity
Now let's make it more user-friendly with Alpine.js! We'll add:
- A copy button for the shortened URL
- Toast notifications for feedback
- Auto-generated short codes
Open views/url/show.vtpl and replace the entire content with:
<#include "shared/alpine_components.vtpl">
<div class="container">
<header>
<h1>Shortened URL</h1>
<div class="actions">
<a href="/url" class="btn">Back to List</a>
<a href="/url/${url.id}/edit" class="btn btn-primary">Edit</a>
</div>
</header>
<main>
<div class="card">
<dl class="details">
<dt>Short Code</dt>
<dd><strong>${url.short_code}</strong></dd>
<dt>Short URL</dt>
<dd>
<#assign shortUrl = base_url + "/" + url.short_code>
<code>${shortUrl}</code>
<@copyButton text=shortUrl label="Copy URL" />
</dd>
<dt>Original URL</dt>
<dd>
<a href="${url.original_url}" target="_blank">${url.original_url}</a>
</dd>
<dt>Clicks</dt>
<dd>${url.clicks}</dd>
<dt>Created</dt>
<dd>${url.created_at}</dd>
</dl>
<div class="danger-zone">
<form method="POST" action="/url/${url.id}">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this URL?')">
Delete URL
</button>
</form>
</div>
</div>
</main>
</div>
<@toastContainer />
What changed?
- No DOCTYPE/head/body tags - The layout handles these! (Remember Step 6.5?)
- Line 1:
<#include "shared/alpine_components.vtpl">- Loads Alpine.js component macros - Line 20:
<#assign shortUrl = ...>- Build the URL as a variable (VeeMarker doesn't interpolate ${} inside macro attribute strings) - Line 22:
<@copyButton ... />- Adds a copy-to-clipboard button using the shortUrl variable - Line 49:
<@toastContainer />- Adds toast notification area
VeeMarker Note: When passing dynamic values to macros, use <#assign> to build the value first, then pass the variable name without quotes: text=shortUrl (not text="${shortUrl}"). VeeMarker doesn't interpolate ${} expressions inside quoted macro attribute strings.
Test it:
varel serve
Visit http://localhost:8080/url (your previously created URL should be listed)
Click on it, then click the "Copy URL" button. You should see:
- Button text changes to "Copied!" briefly
- A toast notification appears at the top
- The URL is in your clipboard (try pasting it)
This is Alpine.js magic! All of this interactivity works with zero JavaScript build step, no npm, no webpack. The alpine.min.js file is included in your project at public/js/alpine.min.js, and it's all server-side rendered and enhanced with Alpine.js directives.
Step 8: Improve the Create Form
Let's make the create form smarter by auto-generating short codes.
Open views/url/new.vtpl and replace with:
<#include "shared/alpine_components.vtpl">
<div class="container">
<header>
<h1>Shorten a URL</h1>
<a href="/url" class="btn">Back to List</a>
</header>
<main>
<div x-data="{
originalUrl: '',
shortCode: '',
autoGenerate: true,
generateCode() {
if (!this.autoGenerate || !this.originalUrl) return;
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
let code = '';
for (let i = 0; i < 6; i++) {
code += chars[Math.floor(Math.random() * chars.length)];
}
this.shortCode = code;
}
}">
<form method="POST" action="/url" class="form">
<div class="form-group">
<label for="original_url">Long URL *</label>
<input type="text"
id="original_url"
name="original_url"
x-model="originalUrl"
@blur="generateCode()"
placeholder="https://example.com/very/long/url"
required>
<small class="form-text">The URL you want to shorten</small>
</div>
<div class="form-group">
<label>
<input type="checkbox" x-model="autoGenerate" checked>
Auto-generate short code
</label>
</div>
<div class="form-group" x-show="!autoGenerate">
<label for="short_code">Custom Short Code</label>
<input type="text"
id="short_code"
name="short_code"
x-model="shortCode"
placeholder="my-custom-code">
<small class="form-text">Letters, numbers, and hyphens only</small>
</div>
<input type="hidden" name="short_code" x-bind:value="shortCode">
<input type="hidden" name="clicks" value="0">
<div class="form-group" x-show="shortCode">
<label>Preview</label>
<div class="preview">
<code>${base_url}/<span x-text="shortCode"></span></code>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Shorten URL</button>
<a href="/url" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</main>
</div>
<@toastContainer />
What's new?
Line 18-30: Alpine.js component with reactive data
originalUrl- Bound to the input fieldshortCode- Auto-generated or customautoGenerate- Toggle between auto/customgenerateCode()- Generates random 6-character code
Line 38:
x-model="originalUrl"- Two-way data bindingLine 39:
@blur="generateCode()"- Generate code when user leaves fieldLine 54:
x-show="!autoGenerate"- Show/hide custom code fieldLine 71: Live preview of shortened URL
Test it:
varel serve
Visit http://localhost:8080/url/new
- Type a URL in the "Long URL" field
- Click outside the field (or press Tab)
- Watch the "Preview" appear with a random short code!
- Try unchecking "Auto-generate" to enter a custom code
Create a few URLs to test:
https://github.com/vlang/v→ random codehttps://docs.varel.dev→ custom:docshttps://www.youtube.com/watch?v=dQw4w9WgXcQ→ random code
Step 9: Improve the Index Page
Let's make the URL list more useful with click counts and copy buttons.
Open views/url/index.vtpl and replace with:
<#include "shared/alpine_components.vtpl">
<div class="container">
<header>
<h1>URL Shortener</h1>
<a href="/url/new" class="btn btn-primary">Shorten New URL</a>
</header>
<main>
<#if url??>
<#if url?size > 0>
<table class="table">
<thead>
<tr>
<th>Short Code</th>
<th>Short URL</th>
<th>Original URL</th>
<th>Clicks</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<#list url as item>
<#assign itemShortUrl = base_url + "/" + item.short_code>
<tr>
<td><code>${item.short_code}</code></td>
<td>
<code>${itemShortUrl}</code>
<@copyButton text=itemShortUrl />
</td>
<td>
<a href="${item.original_url}" target="_blank" class="truncate">
${item.original_url}
</a>
</td>
<td>
<strong>${item.clicks}</strong>
</td>
<td class="actions">
<a href="/url/${item.id}" class="btn btn-sm">View</a>
<a href="/url/${item.id}/edit" class="btn btn-sm">Edit</a>
<form method="POST" action="/url/${item.id}" style="display:inline;">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Delete this URL?')">
Delete
</button>
</form>
</td>
</tr>
</#list>
</tbody>
</table>
<#else>
<div class="empty-state">
<p>No URLs yet!</p>
<a href="/url/new" class="btn btn-primary">Create Your First Short URL</a>
</div>
</#if>
</#if>
</main>
</div>
<@toastContainer />
What's improved?
- Copy buttons for each URL (click to copy!)
- Click count display
- Better table layout
- Empty state message if no URLs exist
- Toast notifications when copying
Test it:
varel serve
Visit http://localhost:8080/url
You should see all your URLs in a nice table. Try:
- Clicking the copy buttons (you'll get a toast notification!)
- Creating more URLs
- Deleting URLs
Step 10: Add URL Redirection (Bonus!)
Let's make the short URLs actually work! When someone visits http://localhost:8080/abc123, they should be redirected to the original URL.
Open controllers/url.v and add this function to the end of the file:
// redirect handles short code lookups and redirects
// GET /:short_code
pub fn (c UrlController) redirect(mut ctx http.Context) http.Response {
short_code := ctx.param('short_code')
// Find URL by short code
mut urls := models.all_url(mut ctx.db) or { return ctx.not_found('Database error') }
mut found_url := ?models.Url(none)
for url in urls {
if url.short_code == short_code {
found_url = url
break
}
}
url := found_url or { return ctx.not_found('Short URL not found') }
// Increment click count
mut updated_url := url
updated_url.clicks += 1
models.update_url(mut ctx.db, updated_url) or {
eprintln('Failed to update click count: ${err}')
}
// Redirect to original URL
return ctx.redirect_permanent(url.original_url)
}
Open routes.v and add this route wrapper function after the other URL route wrappers (around line 60):
fn route_url_redirect(mut ctx http.Context) http.Response {
mut ctrl := controllers.UrlController{}
return ctrl.redirect(mut ctx)
}
Then, in the register_routes() function, add this route at the END (after all the /url routes, before the closing brace):
// Catch-all route for short code redirects (must be LAST!)
app.get('/:short_code', route_url_redirect)!
Important: This route MUST be last because it catches all paths. Any routes below it won't work!
Test it:
varel serve
- Visit http://localhost:8080/url and note one of your short codes (e.g.,
google) - Visit http://localhost:8080/google (or whatever your code was)
- You should be redirected to the original URL!
- Go back to http://localhost:8080/url
- Notice the click count increased by 1!
Congratulations! You now have a fully functional URL shortener with click tracking! 🎉
Step 11: Add Custom Styling (Optional)
Let's make it look nicer with some custom CSS.
Open public/css/main.css and add at the end:
/* URL Shortener specific styles */
.preview {
padding: 12px;
background: #f0f4f8;
border-radius: 6px;
margin-top: 8px;
}
.preview code {
color: #2563eb;
font-size: 16px;
}
.truncate {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #6b7280;
}
.empty-state p {
font-size: 18px;
margin-bottom: 20px;
}
table code {
background: #f3f4f6;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
}
.details dt {
font-weight: 600;
color: #374151;
margin-bottom: 8px;
}
.details dd {
margin-bottom: 20px;
color: #6b7280;
}
.details code {
background: #f3f4f6;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
margin-right: 12px;
}
Refresh your browser - everything should look more polished now!
What You Built
Let's recap what you accomplished:
✅ Full CRUD Application
- Create, Read, Update, Delete URLs
- RESTful routing pattern
- Database persistence with PostgreSQL
✅ Interactive UI with Alpine.js
- Copy-to-clipboard buttons (no build step!)
- Toast notifications for feedback
- Auto-generated short codes
- Live preview of shortened URLs
- Reactive forms with show/hide logic
✅ Real Functionality
- URL redirection with
:short_coderouting - Click tracking
- Database migrations
- Form validation
✅ Best Practices
- MVC architecture (Models, Views, Controllers)
- Database migrations (up/down SQL)
- RESTful routes
- Server-side rendering + client-side enhancement
Key Takeaways
Varel's Strengths:
- Scaffold Generator - Created 9 files (controller, model, 4 views, 2 migrations, tests) with one command
- Alpine.js Integration - Interactive UI with zero build step, no npm packages
- VeeMarker Components - Reusable
@copyButtonand@toastContainermacros - Database Helpers - Simple migration system, type-safe queries
- Convention over Configuration - Everything just works with sensible defaults
Alpine.js Components Used:
@copyButton- One-line copy-to-clipboard with visual feedback@toastContainer- Global notification systemx-data- Component state managementx-model- Two-way data bindingx-show- Conditional rendering@blur- Event handling
Next Steps
Want to expand your URL shortener? Try these challenges:
Easy
- Add a "Visit" link in the table (opens original URL in new tab)
- Sort URLs by click count (most popular first)
- Add a "Created" date column
- Change the short code generator to use uppercase letters too
Medium
- Add URL validation (check if original_url is a valid URL)
- Prevent duplicate short codes (check before creating)
- Add search/filter to the URL list (Alpine.js
x-show+includes()) - Add QR code generation for each short URL
Hard
- Add user authentication (only logged-in users can create URLs)
- Add analytics dashboard (chart showing clicks over time)
- Add custom domains (e.g.,
short.io/abc123vslocalhost:8080/abc123) - Add expiration dates for URLs (auto-delete after 30 days)
Troubleshooting
Problem: "Database connection failed"
- Check PostgreSQL is running:
systemctl status postgresql - Verify credentials in
config/config.toml - Try:
varel db createagain
Problem: "Table 'url' does not exist"
- Run migrations:
varel db migrate - Check migration status:
varel db status
Problem: "Alpine.js copy button doesn't work"
- Make sure you included:
<#include "shared/alpine_components.vtpl"> - Check browser console for errors (F12)
- Verify Alpine.js is loaded:
static/js/alpine.min.jsshould exist
Problem: "Route not found" for short codes
- Make sure the
/:short_coderoute is LAST inmain.v - Restart the server after adding routes
Problem: "Clicks aren't incrementing"
- Check the
redirect()method is correct - Look for errors in terminal output
- Verify the URL exists before testing
Learn More
- Routing: docs/guides/02_routing.md
- Controllers: docs/guides/04_controllers.md
- Database: docs/guides/05_database.md
- Templates: docs/guides/06_templates.md
- Alpine.js Components: docs/guides/09_javascript_helpers.md
- CLI Reference: docs/guides/98_cli_reference.md
Congratulations on building your first Varel application! 🎉
You've learned the core concepts of Varel web development. The same patterns you used here (scaffold → migrate → customize) work for any CRUD application: blogs, todo lists, inventory systems, and more.
Questions or feedback? Open an issue at https://github.com/leafscale/varel/issues