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

CORS (Cross-Origin Resource Sharing)

Volga provides an easily configurable CORS middleware.

It is included in the full feature set. However, if you are not using it, you can enable the middleware feature in your Cargo.toml to make CORS functionality available:

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

Basic Setup

The following example demonstrates a permissive CORS configuration:

use volga::App;

#[tokio::main]
async fn main() -> std::io::Result<()> {   
    let mut app = App::new()
        .with_cors(|cors| cors
            .with_any_origin()
            .with_any_header()
            .with_any_method());

    // Enable CORS middleware
    app.use_cors();

    app.map_post("/", || async {});

    app.run().await
}

By default, CORS is disabled. To avoid a runtime panic, you must call with_cors() before use_cors().

If you need a stricter configuration, you can specify allowed origins, headers, and methods:

use volga::{App, http::Method};

#[tokio::main]
async fn main() -> std::io::Result<()> {   
    let mut app = App::new()
        .with_cors(|cors| cors
            .with_origins(["http://example.com", "http://example.net"])
            .with_headers(["Cache-Control", "Content-Language"])
            .with_methods([Method::GET, Method::POST]));

    // Enable CORS middleware
    app.use_cors();

    app.map_post("/", || async {});

    app.run().await
}

Warning

If you need to enable credentials using with_credentials(), note that it cannot be used with wildcard origins, headers, or methods for security reasons. These constraints are validated in use_cors(), which will panic if misconfigured. The method takes no arguments — use without_credentials() to explicitly disable. Similarly, the Vary response header is controlled via with_vary_header() / without_vary_header().

CORS Policies and Scoping

Volga separates CORS configuration from where it is applied:

  • with_cors() defines one or more CORS policies.

  • use_cors() enables the CORS middleware (required).

  • CORS is applied based on:

    • a default policy (applies to all routes unless overridden), or
    • a named policy (applies only where explicitly specified).

Named Policy for a Route Group

If you configure only a named policy, routes will not get CORS unless you explicitly attach that policy via cors_with(...).

use volga::App;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new()
        .with_cors(|cors| cors
            .with_name("policy")
            .with_origins(["https://example.com"])
            .with_any_header()
            .with_any_method());

    // Enable the middleware (required)
    app.use_cors();

    app.group("/api", |api| {
        // Apply CORS only for `/api/*`
        api.cors_with("policy");

        api.map_get("/users", || async {});
        api.map_post("/posts", || async {});
    });

    // No CORS here (policy not applied)
    app.map_get("/internal", || async {});

    app.run().await
}

Named Policy for an Individual Route

You can attach a policy to a single endpoint:

use volga::App;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new()
        .with_cors(|cors| cors
            .with_name("permissive")
            .with_any_origin()
            .with_any_method()
            .with_any_header());

    app.use_cors();

    app.map_get("/public", || async {})
        .cors_with("permissive");

    app.map_get("/private", || async {})
        .disable_cors();

    app.run().await
}

Warning

When using only named policies, endpoints without an explicitly attached policy will not emit CORS headers.

Overriding and Disabling

CORS can be explicitly disabled on a route (or group) using disable_cors(). This is especially useful when you have a default policy enabled globally, but need to opt out for specific endpoints.

CORS on Unmatched Requests

A 404 and a 405 carry the CORS headers the configured policy would have put on a 200: global middleware runs for requests that matched no route, so a browser gets a usable CORS error instead of an opaque one.

A preflight is still answered 204 only for a route that exists — answering one for a path or a method the router did not match would advertise an endpoint that is not there. Those requests fall through to the 404 / 405 and pick the headers up on the way out. The CORS middleware tells the two apart with matched_route().

Static files

A route group's CORS policy does not reach the files that group serves. A policy is resolved from the route that matched, and a static file is served without one, so the policy that applies to files is the application's — the one configured with with_cors(). For the same reason a preflight aimed at a file's path is answered as one for an unmatched path: a 404 carrying the policy's headers rather than a 204. Cross-origin GETs of static content never send one anyway.

A complete example is available here.

Last Updated: 9/14/26, 6:45 AM
Prev
Request Decompression
Next
Static Files