SystemForge Documentation
SystemForge is a platform for learning system design by building real systems. You wire components together in a visual graph editor, design your database schemas, and write handler logic in SysLang — a simple scripting language.
Components
| Component | Purpose | You write code? |
|---|---|---|
| Client | Sends test requests to your system | No — driven by test cases |
| HTTP Server | Handles requests, runs your handler functions | Yes |
| Database | Relational storage with typed schema | You design the schema |
| Cache | Fast key-value store | No — use via API |
| Load Balancer | Distributes requests across servers | No — automatic round-robin |
Quick Start
- Go to Problems and select a problem
- Review the problem description on the left
- Design your schema — right-click the Database node and select "Edit Schema"
- Write your handler — click the HTTP Server node to open the code editor
- Click Run (or Ctrl+Enter) to test your solution
Your handler receives:
// req object passed to your handler:
{
method: "POST", // HTTP method
path: "/register", // request path
body: { // request body (map)
plate: "ABC123",
owner: "Alice"
},
params: {} // URL parameters
}
Your handler returns:
// Return a map with status and body:
return {
status: 201,
body: { registered: true }
};
Graph Editor
The graph editor is the center of the workspace. Use it to build your system architecture.
| Action | How |
|---|---|
| Add a component | Click a palette button (top-right) or right-click empty space |
| Move a component | Click and drag |
| Connect components | Drag from one port (circle) to another |
| Edit code | Right-click HTTP Server → "Edit Code", or click the node |
| Edit database schema | Right-click Database → "Edit Schema" |
| Delete | Right-click → "Delete Component" or select + Backspace |
| Pan | Click and drag on empty space |
| Zoom | Mouse wheel |
db becomes available in your handler code. The alias is the variable name.
Schema Design
When you place a Database component, you need to define its schema — the tables and columns it contains. This is part of the design challenge.
How to define a schema
- Right-click the Database node → Edit Schema
- Click Add Table to create a new table
- Name the table and add columns with types
- Click Save Schema
Column types
| Type | Description | Example values |
|---|---|---|
string | Text data | "Alice", "ABC123" |
int | Whole numbers | 42, 0, -1 |
float | Decimal numbers | 3.14, 99.9 |
bool | True or false | true, false |
any | No type checking | Any value |
Schema validation
Operations are validated against your schema at runtime:
- Unknown columns — inserting or filtering on a column that doesn't exist returns an error
- Type mismatch — inserting a string into an
intcolumn returns an error - Missing table — operating on a table that doesn't exist returns an error
// If your "cars" table has columns: plate (string), owner (string)
db.Insert("cars", { plate: "ABC", owner: "Alice" }); // OK
db.Insert("cars", { plate: "ABC", color: "red" }); // Error: unknown column 'color'
db.Insert("nonexistent", { a: 1 }); // Error: Table not found
Variables
Use let to declare variables. No type annotations — types are inferred.
let name = "car_reg";
let count = 0;
let active = true;
let pi = 3.14;
let nothing = null;
let plates = ["ABC123", "XYZ789"]; // list
let car = { plate: "ABC123", owner: "Alice", year: 2024 }; // map
Functions
Use fn to declare functions. Functions are first-class values and support closures.
fn validate_plate(plate) {
if (plate.length() != 6) {
return { ok: false, error: "invalid length" };
}
return { ok: true };
}
fn add(a, b) {
return a + b;
}
// Recursion works
fn factorial(n) {
if (n <= 1) { return 1; }
return n * factorial(n - 1);
}
Lambdas
Anonymous functions with C++-style capture syntax.
// No capture
let add = [](a, b) { return a + b; };
let result = add(3, 4); // 7
// With capture
let threshold = 100;
let check = [threshold](val) { return val > threshold; };
check(150); // true
// Closures
fn make_adder(x) {
return [x](y) { return x + y; };
}
let add5 = make_adder(5);
add5(3); // 8
Control Flow
// if / else
if (count > 10) {
log("too many");
} else if (count == 0) {
log("none");
} else {
log("ok");
}
// for — iterates over lists
for (let plate : plates) {
log(plate);
}
// while
let i = 0;
while (i < 10) {
log(i);
i = i + 1;
}
Pattern Matching
Match expressions compare a value against patterns. Use _ as a wildcard.
fn handle_request(req) {
return match (req.method) {
"GET" => get_data(req),
"POST" => create_data(req),
_ => { status: 405, body: "Method not allowed" }
};
}
Data Structures
Lists
let items = [1, 2, 3];
items.push(4); // [1, 2, 3, 4]
let first = items[0]; // 1
let count = items.size(); // 4
Maps
let user = { name: "Alice", age: 30 };
user.email = "alice@example.com"; // add field
let name = user.name; // access
let has = user.has("email"); // true
let keys = user.keys(); // ["age", "email", "name"]
String Operations
let s = "hello world";
s.length(); // 11
s.contains("world"); // true
s.split(" "); // ["hello", "world"]
s.upper(); // "HELLO WORLD"
s.substr(0, 5); // "hello"
s + " !!!"; // "hello world !!!"
Built-in Functions
| Function | Description | Example |
|---|---|---|
log(value) | Print to console | log("hello") |
print(value) | Alias for log | print(42) |
len(x) | Length of string/list/map | len("abc") → 3 |
str(x) | Convert to string | str(42) → "42" |
int(x) | Convert to integer | int("42") → 42 |
float(x) | Convert to float | float(3) → 3.0 |
Client Component
Represents users sending requests to your system. In tests, the client sends predefined requests and checks responses.
HTTP Server Component
Handles incoming requests. You write handler functions that receive a request and return a response.
Request object
fn my_handler(req) {
req.method // "GET", "POST", "PUT", "DELETE"
req.path // "/register", "/users"
req.body // map with request data
req.params // URL parameters
}
Response format
return { status: 200, body: { data: "value" } };
return { status: 404, body: "Not found" };
return { status: 201, body: { created: true } };
Connected component access
When you connect a Database or Cache to your server, their APIs become available as variables (the alias from the connection):
// Connected to Database with alias "db":
let car = db.FindOne("cars", { plate: "ABC123" });
db.Insert("cars", { plate: "ABC123", owner: "Alice" });
// Connected to Cache with alias "cache":
let val = cache.get("key");
cache.set("key", "value");
Database Component
In-memory relational database with a functional API and schema validation. No SQL — just function calls with maps.
Right-click the Database node → Edit Schema to define tables and columns before using it.
| Method | Description | Returns |
|---|---|---|
db.FindOne(table, filter) | Find first matching row | Map or null |
db.Find(table, filter) | Find all matching rows | List of maps |
db.Insert(table, row) | Insert a new row | { inserted: true } |
db.Update(table, filter, updates) | Update matching rows | { updated: N } |
db.Delete(table, filter) | Delete matching rows | { deleted: N } |
db.All(table) | Get all rows | List of maps |
db.Count(table, filter) | Count matching rows | Number |
The filter argument is a map of column-value pairs. All must match (AND logic). Empty filter {} matches all rows.
Cache Component
Fast key-value store. Use it for caching frequently accessed data.
| Method | Description | Returns |
|---|---|---|
cache.get(key) | Get value by key | Value or null |
cache.set(key, value) | Store a value | null |
cache.del(key) | Delete a key | true/false |
cache.has(key) | Check if key exists | true/false |
Load Balancer Component
Distributes incoming requests across multiple downstream servers using round-robin. No code needed — just connect it.
How to use
- Place a Load Balancer between your Client and HTTP Servers
- Connect Client → Load Balancer
- Connect Load Balancer → Server 1, Load Balancer → Server 2, etc.
- Requests are automatically distributed in round-robin order
Find / FindOne
// Find first matching row (or null if not found)
let car = db.FindOne("cars", { plate: "ABC123" });
// Returns: { plate: "ABC123", owner: "Alice" } or null
// Find all matching rows
let alice_cars = db.Find("cars", { owner: "Alice" });
// Returns: [ { plate: "ABC123", owner: "Alice" }, ... ]
// Empty filter matches all rows
let all = db.Find("cars", {});
Insert
db.Insert("cars", { plate: "ABC123", owner: "Alice" });
// Returns: { inserted: true }
Update
// Update matching rows: (table, filter, updates)
db.Update("cars", { plate: "ABC123" }, { owner: "Bob" });
// Returns: { updated: 1 }
Delete
db.Delete("cars", { plate: "ABC123" });
// Returns: { deleted: 1 }
All / Count
// Get all rows in a table
let cars = db.All("cars");
// Count rows matching a filter
let n = db.Count("cars", { owner: "Alice" });
// Count all rows
let total = db.Count("cars", {});