Getting Started
This guide will help set up a simple Cobble application (both server and client) that synchronizes a counter between multiple clients.
⚠️ NOTE: Cobble is still in very early development: things are not easy to do, APIs and features are subject to change, and things will break!
Cobble is a full-stack framework, helping you build both server and client code. This guide guides you through setting up a project with both server and client code, so it is recommended to use the following file structure:
project-root/
|-- server/ # Your server code
|-- ... # ... other server files (with your code)
|-- client/ # Your client code
|-- ... # ... other client files (with your code)
Sometimes, you may want to use a different file structure, such as having pre-made server code and you want to add client code to it, or vice versa. Decide what works best for you, but keep in mind that the guide will assume the above structure.
Cobble CLI is a command-line tool that helps you run, test, and deploy Cobble servers.
You will need to have Git and a Rust toolchain installed to build Cobble CLI from Scratch. See https://www.rust-lang.org/tools/install for Rust installation instructions.
To install Cobble CLI:
$ cargo install --git https://github.com/giilbert/cobble.git cobble_cli
If the installation is successful, you should be able to run the cobble_cli command:
$ cobble_cli -V
cobble_cli 0.1.0
Add the following to your shell configuration file (e.g., ~/.bashrc, ~/.zshrc):
alias cobble="cobble_cli"
This will allow you to run the cobble command instead of cobble_cli. The rest of the documentation will refer to the cobble command.
1. Initialize a new Cobble project:
$ cobble create --template rust
? Server path (Defaults to './server'):
? Name (Lowercase alphanumeric characters and hyphens): enter-project-name
INFO: Setting up project 'enter-project-name' using template 'rust' in ...
2. src/lib.rs should include the following code:
use cobble::prelude::*;
struct CounterStore {
count: i32,
}
impl StoreData for CounterStore {
type Data<'this> = i32;
// ^ This lifetime is used for borrowing data from the store
// when selecting data to send to the client. (Read more below!)
fn init() -> Self {
Self { count: 0 }
}
// Determines what data to send to the client when the store is serialized
// v Used when borrowing data
fn select(&self, _user: &User) -> Self::Select<'_> {
self.count
}
// This name will be used to identify the store
fn name() -> impl AsRef<str> {
"counter"
}
}
// RPC functions can be used to perform actions on the server
async fn increment_counter(
// Special types for extracting parameters, data, and context
Params(counter): Params<i32>,
test: Store<CounterStore>
) -> i32 {
let mut data = test.write().await;
*data += counter;
println!("incremented counter by {counter}. new value: {}", &*data);
*data
}
async fn on_connect(user: User) {
println!("user connected! id: {}", user.meta.id());
}
// Declare what the Cobble application should do
fn build() -> App {
App::builder()
.on_connect(on_connect)
.rpc("increment_counter", increment_counter)
.build()
}
cobble::register!(build);
3. Build and run the server with:
$ cobble run
If all goes well, you should see the following:
INFO: running build command `cargo build --target wasm32-wasip2` in ...
----------
[cargo build output]
-----------
INFO: build completed in 12.34s
INFO: loaded room from <output path>
INFO: dev server listening on 1147
❗ The development server will listen on port 1147 by default. You can change this by passing the --port flag to the cobble run command.
The client library allows you to connect to a Cobble server and interact with it.
1. Install the @usecobble/client package.
2. Install additional dependencies to build/run your code. If you're building a website, you will need a bundler like Vite, Parcel, or Webpack.
3. Write the following code:
import { CobbleClient } from "@usecobble/client";
const client = new CobbleClient({
// NOTE: Change this when you deploy your server
server: "dev",
});
// Use the same name as in `StoreData`
const store = client.store("counter");
store.on("change", (data) => {
console.log("Counter value changed:", data);
});
async function run() {
await client.connect();
console.log("Connected to the server!");
// Call the RPC function to increment the counter every second
while (true) {
const _result = await client.rpc<number>("increment_counter", 1);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
run();