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.

How it works: Select a problem, connect components (servers, databases, caches), design your database schema, write handlers, and run tests. Your architecture is validated against test cases — just like LeetCode, but for distributed systems.

Components

ComponentPurposeYou write code?
ClientSends test requests to your systemNo — driven by test cases
HTTP ServerHandles requests, runs your handler functionsYes
DatabaseRelational storage with typed schemaYou design the schema
CacheFast key-value storeNo — use via API
Load BalancerDistributes requests across serversNo — automatic round-robin

Quick Start

  1. Go to Problems and select a problem
  2. Review the problem description on the left
  3. Design your schema — right-click the Database node and select "Edit Schema"
  4. Write your handler — click the HTTP Server node to open the code editor
  5. 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.

ActionHow
Add a componentClick a palette button (top-right) or right-click empty space
Move a componentClick and drag
Connect componentsDrag from one port (circle) to another
Edit codeRight-click HTTP Server → "Edit Code", or click the node
Edit database schemaRight-click Database → "Edit Schema"
DeleteRight-click → "Delete Component" or select + Backspace
PanClick and drag on empty space
ZoomMouse wheel
Connections matter: When you connect a server to a database with alias "db", the variable 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

  1. Right-click the Database node → Edit Schema
  2. Click Add Table to create a new table
  3. Name the table and add columns with types
  4. Click Save Schema

Column types

TypeDescriptionExample values
stringText data"Alice", "ABC123"
intWhole numbers42, 0, -1
floatDecimal numbers3.14, 99.9
boolTrue or falsetrue, false
anyNo type checkingAny 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 int column 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

FunctionDescriptionExample
log(value)Print to consolelog("hello")
print(value)Alias for logprint(42)
len(x)Length of string/list/maplen("abc") → 3
str(x)Convert to stringstr(42) → "42"
int(x)Convert to integerint("42") → 42
float(x)Convert to floatfloat(3) → 3.0

Client Component

Represents users sending requests to your system. In tests, the client sends predefined requests and checks responses.

You don't write code for the Client. It's driven by test cases. Think of it as millions of users hitting your API.

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.

MethodDescriptionReturns
db.FindOne(table, filter)Find first matching rowMap or null
db.Find(table, filter)Find all matching rowsList 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 rowsList of maps
db.Count(table, filter)Count matching rowsNumber

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.

MethodDescriptionReturns
cache.get(key)Get value by keyValue or null
cache.set(key, value)Store a valuenull
cache.del(key)Delete a keytrue/false
cache.has(key)Check if key existstrue/false

Load Balancer Component

Distributes incoming requests across multiple downstream servers using round-robin. No code needed — just connect it.

How to use

  1. Place a Load Balancer between your Client and HTTP Servers
  2. Connect Client → Load Balancer
  3. Connect Load Balancer → Server 1, Load Balancer → Server 2, etc.
  4. Requests are automatically distributed in round-robin order
The load balancer automatically tracks in-flight requests and routes responses back to the original sender.

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", {});