Volga
Home
Agent Skill
API Docs
GitHub
  • English
  • Русский
Home
Agent Skill
API Docs
GitHub
  • English
  • Русский
  • Home
  • Getting Started

    • Quick Start
    • Handlers
    • Route Parameters
    • Query Parameters
    • Route Groups
  • Requests & Responses

    • Headers
    • Handling JSON
    • Handling Form Data
    • Working with Files
    • Multipart Responses
    • Raw Request Body
    • Cookies
    • Validating Requests
  • Middleware & Infrastructure

    • Basic Middleware
    • Custom Middleware
    • Parameterized Middleware
    • Response Compression
    • Request Decompression
    • CORS (Cross-Origin Resource Sharing)
    • Static Files
    • Rate Limiting
    • Configuration Files
    • OpenAPI
  • Security & Access

    • Authentication and Authorization
    • OAuth 2.1 & OpenID Connect
    • OAuth 2.1 Client
    • Machine-to-Machine Grants
    • DPoP — Sender-Constrained Tokens
  • Reliability & Observability

    • Global Error Handling
    • Tracing & Logging
    • Request cancellation
    • Graceful Shutdown
  • Protocols & Realtime

    • HTTP Versions and Methods
    • HTTPS
    • WebSockets
    • Server-Sent Events (SSE)
  • Advanced Patterns

    • Dependency Injection
    • Custom Handling of HEAD, OPTIONS, and TRACE Methods
  • Agent Skill

Quick Start

Build a basic "Hello, World" HTTP API using Volga.

Prerequisites

Install Rust

If you haven't installed Rust yet, it is recommended to use the rustup. Here is the official guide where you can find how to do it.

Volga currently has a minimum supported Rust version (MSRV) of 1.90. Running rustup update will ensure you have the latest Rust version available.

Create an app

Create a new binary-based app:

cargo new hello-world
cd hello-world

Add the following dependencies in your Cargo.toml:

[dependencies]
volga = { version = "..." }
tokio = { version = "...", features = ["full"] }

Setup

Create your main application in main.rs:

use volga::{App, ok};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    // Configure the server
    let mut app = App::new();

    // Example of a simple GET request handler
    app.map_get("/hello", || async {
        ok!("Hello, World!")
    });
    
    // Run the server
    app.run().await
}

Detailed Walkthrough

When the App struct is instantiated, it represents your API. Unless bind() says otherwise it listens on port 7878 on every interface (0.0.0.0:7878), and on 127.0.0.1:7878 on Windows:

let mut app = App::new();

Or if you need to bind it to another socket, you can use the bind() method like this:

// Binds the server to http://localhost:5000
let mut app = App::new().bind("localhost:5000");

Since v0.9.7, bind() accepts the full address grammar of tokio::net::TcpListener::bind:

let app = App::new().bind("127.0.0.1:7878");      // IPv4 literal
let app = App::new().bind("[::1]:7878");          // IPv6 literal
let app = App::new().bind("::1:7878");            // unbracketed IPv6 literal
let app = App::new().bind("[fe80::1%eth0]:7878"); // zone-scoped IPv6 literal
let app = App::new().bind("localhost:7878");      // host name
let app = App::new().bind(([127, 0, 0, 1], 7878)); // anything that converts into a SocketAddr

Host names are resolved when the server starts — asynchronously, so the runtime is never blocked. If a name resolves to several addresses, they are tried in resolution order and the first one that can be bound wins.

Warning

An address that cannot be resolved is reported rather than guessed at: run() returns an io::Error, and run_blocking() logs it and starts no server. Nothing falls back to 0.0.0.0:7878, so a loopback bind stays on loopback.

Next, map a specific handler to a route. For instance, mapping our handler to GET /hello:

app.map_get("/hello", || {
    ok!("Hello, World!")
});

A handler with nothing to await can return its response directly, as this one does, while one that awaits something is an async closure or an async fn. Both shapes are covered in Handlers.

Ensure routes are mapped before you start the server with:

app.run().await

Testing the API

You can test your API using the curl command:

> curl -v "http://localhost:7878/hello"

Response expected:

* Host localhost:7878 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:7878...
* Connected to localhost (::1) port 7878
> GET /hello HTTP/1.1
> Host: localhost:7878
> User-Agent: curl/8.9.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< date: Sun, 6 Oct 2024 08:22:17 +0000
< server: Volga
< content-length: 12
< content-type: text/plain
<
* Connection #0 to host localhost left intact
Hello, World!

Blocking Example

Volga also supports building an HTTP API without explicitly depending on tokio, using the run_blocking() method.

This allows you to simplify your Cargo.toml dependencies:

[dependencies]
volga = { version = "..." }

Your main.rs might then look like this:

use volga::{App, ok};

fn main() {
    // Create the application
    let mut app = App::new();

    // Define a simple GET handler
    app.map_get("/hello", || async {
        ok!("Hello, World!")
    });

    // Run the server in blocking mode
    app.run_blocking()
}

Although the main function appears synchronous, the server still runs asynchronously under the hood using the tokio runtime.

Info

The run_blocking() approach is suitable for quick prototyping, simple tools, or learning scenarios where you want to avoid dealing with async setup. However, using #[tokio::main] is generally recommended for production applications, as it gives you full control over the async runtime, allows more advanced configuration, and better supports integration with other async libraries and services.

You can also check out the full example here

Last Updated: 9/19/26, 6:56 AM
Next
Handlers