A single-command dev tool for fullstack Rust web applications using Actix-web and Yew. Scaffolds a three-crate workspace, then stays alive as the persistent driver of your entire development loop.
Most fullstack Rust setups require you to manage two separate processes: one for the frontend WASM build and one for the backend server. wasm-drydock replaces both with a single command that owns the whole loop:
wasm-drydock dev
This starts a dev server on port 8080 that:
- Builds the frontend with
wasm-packon startup - Spawns your Actix-web backend and waits for it to be ready
- Proxies
/api/*requests to the backend - Watches
frontend/src/and rebuilds WASM on changes - Watches
backend/src/and restarts the backend on changes - Watches
frontend/styles/and recompiles SCSS on changes - Watches
frontend/public/and triggers a browser reload on changes - Watches
frontend/for.htmlchanges and triggers a browser reload - Signals the browser to reload after successful builds
The generated project contains only user code — API handlers, Yew components, shared types, and styles. The tool handles everything else.
- Rust (edition 2024)
- wasm-pack >= 0.13.0 — install guide
- wasm32-unknown-unknown target:
rustup target add wasm32-unknown-unknown
The init command validates these before scaffolding.
git clone https://github.com/your-username/wasm-drydock.git
cd wasm-drydock
cargo install --path .Reinstall after making changes to the tool.
wasm-drydock init my-app
cd my-appThis creates a three-crate Cargo workspace with deployment files for Fly.io:
my-app/
├── .cargo/config.toml # cargo alias: backend = "run -p my-app-backend"
├── .dockerignore # Docker build exclusions
├── .gitignore
├── Cargo.toml # workspace root
├── Dockerfile # Multi-stage build with cargo-chef
├── drydock.toml # wasm-drydock configuration
├── fly.toml # Fly.io deployment configuration
├── backend/ # Actix-web API server
│ ├── Cargo.toml
│ ├── build.rs
│ ├── configuration/ # YAML-based config (base, local, production)
│ ├── src/
│ │ ├── bin/main.rs
│ │ ├── lib.rs
│ │ ├── api/
│ │ ├── configuration.rs
│ │ ├── error.rs
│ │ ├── response.rs
│ │ ├── startup.rs
│ │ ├── static_assets.rs
│ │ └── telemetry.rs
│ └── tests/
│ └── api/ # integration test scaffolding
├── frontend/ # Yew WASM application
│ ├── Cargo.toml
│ ├── index.html
│ ├── public/ # static assets (served as-is)
│ ├── src/
│ └── styles/
│ └── screen.scss # Josh Comeau CSS reset included
└── shared/ # Serde-compatible API types
├── Cargo.toml
└── src/
To skip deployment file generation:
wasm-drydock init my-app --no-deploywasm-drydock devOpen http://localhost:8080. Edits to frontend, backend, styles, or public assets are picked up automatically.
To open the browser automatically when the server starts:
wasm-drydock dev --open
# or
wasm-drydock dev -owasm-drydock releaseThis runs wasm-pack build --release on the frontend, then cargo build --release --features embed-assets on the backend. The result is a single self-contained binary — all frontend assets and configuration/base.yaml are compiled in, so the binary runs with zero filesystem dependencies.
Deploy the binary alone. To customise settings without a recompile, place an environment-specific YAML file (e.g. configuration/production.yaml) next to the binary or anywhere in its walk-up path. It is loaded on top of the embedded base config when present. APP_* environment variables are always applied last and override everything.
drydock.toml lives at the workspace root. All [dev] fields are optional and fall back to the defaults shown:
[project]
name = "my-app"
[dev]
public_port = 8080 # browser-facing port
backend_port = 3001 # internal backend port
watch_debounce_ms = 300 # file change debounce intervalThe backend reads its port from the DRYDOCK_BACKEND_PORT environment variable, which wasm-drydock dev sets automatically from config.dev.backend_port.
You can add custom watch paths for additional file monitoring using [[watch]] entries:
# Watch a content folder for markdown files
[[watch]]
path = "frontend/content"
extensions = ["md", "mdx"]
action = "reload"
# Watch an assets folder (all file types)
[[watch]]
path = "frontend/assets"
extensions = []
action = "reload"
# Watch shared crate for type changes
[[watch]]
path = "shared/src"
extensions = ["rs"]
action = "rebuild"Available actions:
| Action | Effect |
|---|---|
reload |
Trigger browser page reload |
rebuild |
Trigger WASM rebuild (then reload) |
restart |
Trigger backend restart |
Notes:
- Paths are relative to the project root
- Empty
extensionsarray watches all files - Non-existent paths are logged as warnings and skipped
- Core watchers (
frontend/src,backend/src,frontend/public,frontend/styles,frontend/) are always active
Browser :8080
│
├── /api/* → proxy → Backend :3001
├── /pkg/* → wasm-pack build output (filesystem)
├── /styles/screen.css → compiled from frontend/styles/screen.scss
├── /ws/reload → WebSocket live reload
└── /* → index.html (SPA fallback)
The tool runs four core file watchers simultaneously (always active):
| Watcher | Path | Filters | On change |
|---|---|---|---|
| Frontend | frontend/src/ |
.rs |
wasm-pack build, then browser reload |
| Backend | backend/src/ |
.rs |
Kill backend, respawn, health check, then browser reload |
| Styles | frontend/styles/ |
.scss |
Recompile SCSS in memory, browser reload |
| Public assets | frontend/public/ |
any file | Browser reload |
| HTML | frontend/ |
.html |
Browser reload |
Additional custom watchers can be configured via [[watch]] entries in drydock.toml (see Custom Watch Paths).
The scaffolded backend is an opinionated Actix-web starter. Out of the box backend/src/ contains:
configuration.rs— YAML-based configuration loading via theconfigcrate. In development builds, readsconfiguration/base.yamland an environment-specific file from the filesystem. In release builds (embed-assetsfeature),base.yamlis compiled directly into the binary viainclude_str!— no config files required at runtime. An optional environment-specific file (e.g.configuration/production.yaml) placed next to the binary is still loaded when present.APP_*environment variables override everything. TheDRYDOCK_BACKEND_PORTenv var overrides the configured port at runtime.startup.rs—Applicationstruct that wires up the Actix-web server, routes, and middleware.error.rs—ApiErrorenum implementing Actix-web'sResponseErrortrait, mapping variants (BadRequest,NotFound,Internal) to HTTP status codes.response.rs—ApiResponse<T>generic wrapper implementing theRespondertrait, withsuccess()anderror()constructors for consistent JSON responses.telemetry.rs— Tracing subscriber setup with environment-based log filtering viatracingandtracing-subscriber.static_assets.rs— Serves embedded frontend assets in release builds. Gated on theembed-assetsfeature flag — not compiled during development.api/— Route configuration with starter endpoints:/api/health_check,/api/hello, and/api/status.
Integration test scaffolding lives in tests/api/ with a TestApp helper that spawns the server on a random port, plus a sample health-check test to build on.
shared/ contains the API boundary types used by both backend and frontend. Both crates depend on it, so serialization mismatches are compile errors rather than runtime surprises:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HelloResponse {
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusResponse {
pub version: String,
pub uptime_seconds: u64,
}| Flag | Effect |
|---|---|
embed-assets |
Embeds frontend/pkg/, frontend/index.html, frontend/public/, compiled CSS, and configuration/base.yaml into the binary at compile time. The resulting binary has zero runtime filesystem dependencies. |
Used automatically by wasm-drydock release.
cargo testTests that invoke wasm-pack are gated behind an environment variable:
RUN_WASM_TESTS=1 cargo test- Multi-framework support (Yew only at the moment, would be nice to add anything
trunksupports) - CSS-in-Rust or Tailwind integration
- Multi-target or SSR builds
MIT — see LICENSE.txt