| Crates.io | bevy_webgate |
| lib.rs | bevy_webgate |
| version | 0.1.1 |
| created_at | 2025-06-30 18:00:00.011221+00 |
| updated_at | 2025-06-30 18:04:40.123368+00 |
| description | A web server integration for the Bevy game engine that allows you to easily append a webserver to Bevy. |
| homepage | |
| repository | https://github.com/MalekiRe/bevy_webserver |
| max_upload_size | |
| id | 1732143 |
| size | 350,867 |
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.
Add this to your Cargo.toml:
[dependencies]
bevy_webgate = "0.1.0"
bevy = "0.16.1"
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())
}
RouterAppExt trait to add routesuse 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())
}
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