Vespera
FastAPI-like developer experience for Rust. Zero-config OpenAPI 3.1 generation for Axum.
// That's it. Swagger UI at /docs, OpenAPI at openapi.json
let app = vespera!;
Why Vespera?
| Feature | Vespera | Manual Approach |
|---|---|---|
| Route registration | Automatic (file-based) | Manual Router::new().route(...) |
| OpenAPI spec | Generated at compile time | Hand-written or runtime generation |
| Schema extraction | From Rust types | Manual JSON Schema |
| Swagger UI | Built-in | Separate setup |
| Type safety | Compile-time verified | Runtime errors |
Quick Start
1. Add Dependencies
[]
= "0.1"
= "0.8"
= { = "1", = ["full"] }
= { = "1", = ["derive"] }
2. Create Route Handler
src/
├── main.rs
└── routes/
└── users.rs
src/routes/users.rs:
use ;
use ;
use Schema;
/// Get user by ID
pub async
/// Create a new user
pub async
3. Setup Main
src/main.rs:
use vespera;
async
4. Run
# Open http://localhost:3000/docs
Core Concepts
File-Based Routing
File structure maps to URL paths automatically:
src/routes/
├── mod.rs → /
├── users.rs → /users
├── posts.rs → /posts
└── admin/
├── mod.rs → /admin
└── stats.rs → /admin/stats
Route Handlers
Handlers must be pub async fn with the #[vespera::route] attribute:
// GET /users (default method)
pub async
// POST /users
pub async
// GET /users/{id}
pub async
// Full options
pub async ...
Schema Derivation
Derive Schema on types used in request/response bodies:
// Serde attributes are respected
Supported Extractors
| Extractor | OpenAPI Mapping |
|---|---|
Path<T> |
Path parameters |
Query<T> |
Query parameters |
Json<T> |
Request body (application/json) |
Form<T> |
Request body (form-urlencoded) |
TypedHeader<T> |
Header parameters |
State<T> |
Ignored (internal) |
Error Handling
pub async
vespera! Macro Reference
let app = vespera!;
export_app! Macro Reference
Export a vespera app for merging into other apps:
// Basic usage (scans "routes" folder by default)
export_app!;
// Custom directory
export_app!;
Generates a struct with:
MyApp::OPENAPI_SPEC: &'static str- The OpenAPI JSON specMyApp::router() -> Router- Function returning the Axum router
Environment Variable Fallbacks
All parameters support environment variable fallbacks:
| Parameter | Environment Variable |
|---|---|
dir |
VESPERA_DIR |
openapi |
VESPERA_OPENAPI |
title |
VESPERA_TITLE |
version |
VESPERA_VERSION |
docs_url |
VESPERA_DOCS_URL |
redoc_url |
VESPERA_REDOC_URL |
servers |
VESPERA_SERVER_URL + VESPERA_SERVER_DESCRIPTION |
Priority: Macro parameter > Environment variable > Default
schema_type! Macro
Generate request/response types from existing structs. Perfect for creating API types from database models.
Basic Usage
use schema_type;
// Pick specific fields only
schema_type!;
// Omit specific fields
schema_type!;
// Add new fields
schema_type!;
Same-File Model Reference
When the model is in the same file, you can use a simple name with name parameter:
// In src/models/user.rs
// Simple `Model` path works when using `name` parameter
schema_type!;
The macro infers the module path from the file location, so relation types like HasOne<super::user::Entity> are resolved correctly.
Cross-File References
Reference structs from other files using full module paths:
// In src/routes/users.rs - references src/models/user.rs
schema_type!;
Auto-Generated From Impl
When add is NOT used, a From impl is automatically generated:
schema_type!;
// Now you can do:
let model: Model = db.find_user.await?;
Json // Automatic conversion!
Partial Updates (PATCH)
Use partial to make fields optional for PATCH-style updates:
// All fields become Option<T>
schema_type!;
// Only specific fields become Option<T>
schema_type!;
Serde Rename All
Apply serde rename_all strategy:
// Convert field names to camelCase in JSON
schema_type!;
// Available: "camelCase", "snake_case", "PascalCase", "SCREAMING_SNAKE_CASE", etc.
SeaORM Integration
schema_type! has first-class support for SeaORM models with relations:
use *;
// Generates Schema with proper relation types
schema_type!;
Relation Type Conversions:
| SeaORM Type | Generated Schema Type |
|---|---|
HasOne<Entity> |
Box<Schema> or Option<Box<Schema>> |
BelongsTo<Entity> |
Option<Box<Schema>> |
HasMany<Entity> |
Vec<Schema> |
DateTimeWithTimeZone |
chrono::DateTime<FixedOffset> |
Circular Reference Handling: When schemas reference each other (e.g., User ↔ Memo), the macro automatically detects and handles circular references by inlining fields to prevent infinite recursion.
Parameters
| Parameter | Description |
|---|---|
pick |
Include only specified fields |
omit |
Exclude specified fields |
rename |
Rename fields: rename = [("old", "new")] |
add |
Add new fields (disables auto From impl) |
clone |
Control Clone derive (default: true) |
partial |
Make fields optional: partial or partial = ["field1"] |
name |
Custom OpenAPI schema name: name = "UserSchema" |
rename_all |
Serde rename strategy: rename_all = "camelCase" |
ignore |
Skip Schema derive (bare keyword, no value) |
schema! Macro
Get a Schema value at runtime with optional field filtering. Useful for programmatic schema access.
use ;
// Full schema
let full: Schema = schema!;
// With fields omitted
let safe: Schema = schema!;
// With only specified fields
let summary: Schema = schema!;
Note: For creating request/response types, use
schema_type!instead - it generates actual struct types withFromimpl.
Advanced Usage
Adding State
let app = vespera!
.with_state;
Adding Middleware
let app = vespera!
.layer
.layer;
Multiple OpenAPI Files
let app = vespera!;
Custom Route Folder
// Scans src/api/ instead of src/routes/
let app = vespera!;
// Or explicitly
let app = vespera!;
Merging Multiple Vespera Apps
Combine routes and OpenAPI specs from multiple vespera apps at compile time:
Child app (e.g., third crate):
// src/lib.rs
// Export app for merging (dir defaults to "routes")
export_app!;
// Or with custom directory
// vespera::export_app!(ThirdApp, dir = "api");
Parent app:
// src/main.rs
use vespera;
let app = vespera!
.with_state;
This automatically:
- Merges all routes from child apps into the parent router
- Combines OpenAPI specs (paths, schemas, tags) into a single spec
- Makes Swagger UI show all routes from all apps
Type Mapping
| Rust Type | OpenAPI Schema |
|---|---|
String, &str |
string |
i32, u64, etc. |
integer |
f32, f64 |
number |
bool |
boolean |
Vec<T> |
array with items |
Option<T> |
nullable T |
HashMap<K, V> |
object with additionalProperties |
| Custom struct | $ref to components/schemas |
Project Structure
vespera/
├── crates/
│ ├── vespera/ # Main crate - re-exports everything
│ ├── vespera_core/ # OpenAPI types and abstractions
│ └── vespera_macro/ # Proc-macros (compile-time magic)
└── examples/
└── axum-example/ # Complete example application
Contributing
# Build & test
# Run example
# → http://localhost:3000/docs
See SKILL.md for development guidelines and architecture details.
Comparison
vs. utoipa
- Vespera: Zero-config, file-based routing, compile-time generation
- utoipa: Manual annotations, more control, works with any router
vs. aide
- Vespera: Automatic discovery, built-in Swagger UI
- aide: More flexible, supports multiple doc formats
vs. paperclip
- Vespera: Axum-first, modern OpenAPI 3.1
- paperclip: Actix-focused, OpenAPI 2.0/3.0
License
Apache-2.0
Acknowledgments
Inspired by FastAPI's developer experience and Next.js's file-based routing.