A web server integration for the Bevy game engine that allows you to easily append a webserver to Bevy. For either creating standalone webapps or appending a webserver to an existing bevy app/game.
- 🚀 Seamless integration with Bevy ECS
- 🌐 Built on top of Axum for all your webserver needs
- ⚡ Async-first design with full ECS access thanks to bevy_defer
- 🔧 Multi-port support - Run multiple servers on different ports
- 🏠 IP binding control - Bind servers to specific IP addresses
Add this to your Cargo.toml:
[dependencies]
bevy_webgate = "0.2.0"
bevy = "0.17"
axum = "0.8.1"Here's a minimal example that sets up a simple "Hello World" web server:
use bevy::prelude::*;
use bevy_webgate::RouterAppExt;
fn main() {
App::new()
.add_plugins(MinimalPlugins)
.route("/hello_world", axum::routing::get(hello_world))
.run();
}
async fn hello_world() -> axum::response::Html<String> {
axum::response::Html("<p>Hello world!</p>".to_string())
}- Use the
RouterAppExttrait to add routes - Define your handler functions
- That's it! Your web server is ready to go
use bevy::prelude::*;
use bevy_webserver::RouterAppExt;
fn main() {
App::new()
.add_plugins(MinimalPlugins)
// Add as many routes as you need
.route("/", axum::routing::get(index))
.route("/about", axum::routing::get(about))
.route("/api/data", axum::routing::post(handle_data))
.run();
}Run multiple web servers on different ports within a single Bevy application:
use bevy::prelude::*;
use bevy_webserver::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Port-specific routes
.port_route(8080, "/api/users", axum::routing::get(get_users))
.port_route(8081, "/admin", axum::routing::get(admin_panel))
.port_route(8082, "/health", axum::routing::get(health_check))
.run();
}For more details on multi-port functionality, see docs/multi_port.md.
The plugin uses bevy_defer::AsyncWorld for accessing Bevy's ECS from your web handlers:
use bevy_defer::AsyncWorld;
async fn get_player_score(Path(player_id): Path<String>) -> impl IntoResponse {
let scores = AsyncWorld
.query::<&Score>()
.get_mut(|query| {
let mut scores = vec![];
for score in query.iter() {
scores.push(score);
}
serde_json::serialize(&scores).unwrap()
});
Json(scores)
}Create dynamic HTML templates using my recommendation, Maud:
use maud::{html, Markup};
fn base_template(content: Markup) -> Markup {
html! {
html {
head {
title { "My Bevy Web App" }
}
body {
(content)
}
}
}
}
async fn index() -> axum::response::Html<String> {
let markup = base_template(html! {
h1 { "Welcome!" }
p { "This is a Bevy web application." }
});
axum::response::Html(markup.into_string())
}The plugin works great with HTMX for dynamic content:
async fn player_list() -> axum::response::Html<String> {
let players = AsyncWorld
.query::<(&Player, &Score)>()
.get_mut(|query| {
let mut players = vec![];
for (player, score) in query.iter() {
players.push((player.clone(), score.clone()));
}
players
})
.unwrap();
let markup = html! {
div class="player-list" {
@for (player, score) in players {
div hx-target="this" hx-swap="outerHTML" {
(player.name) " - " (score.value)
}
}
}
};
axum::response::Html(markup.into_string())
}| bevy | bevy_webgate |
|---|---|
| 0.16 | 0.1 |
| 0.17 | 0.2 |
There is a complete example of a web-based game score tracker in examples/crud_app.rs
This also uses another one of my crates bevy_easy_database which makes it easy to persist data!
Contributions are welcome! Please feel free to submit a Pull Request.
MIT OR Apache-2.0
Built with ❤️ for the Bevy community. Built off the back of Axum and bevy_defer