This document consolidates the architecture, syntax, and requirements for css-server.
css-server is a transpiler that converts CSS syntax into a running Express.js server. The system parses CSS files into an AST, compiles them into Express route handlers, and runs them with a runtime that also manages SQLite access.
CSS File -> Parser -> AST -> Compiler -> Express Routes -> Runtime
- Parses command-line arguments using Commander
- Reads the CSS file from disk
- Invokes the parser and runtime
Uses PostCSS to parse CSS into a ParsedCSS object:
interface ParsedCSS {
config: ServerConfig
routes: RouteRule[]
}Parsing steps:
- Server config extraction from
@server - Route extraction for
[path="..."]:METHOD - Declaration parsing for variables, status, and
@return - Expression parsing: sql, param, query, body, header, var, if
Executes expressions during request handling.
Key functions:
evaluateExpression()evaluateSql()evaluateIf()evaluateCondition()
Request context:
interface RequestContext {
params: Record<string, string>
query: Record<string, string>
body: Record<string, any>
headers: Record<string, string>
variables: Record<string, any>
}Transforms parsed routes into Express handlers.
Handler logic:
- Build RequestContext from req
- Evaluate variable assignments in order
- Evaluate status code (if present)
- Evaluate return value
- Send response (json or html)
- Initializes Express middleware (JSON, URL-encoded)
- Initializes SQLite connection
- Registers compiled routes
- Handles 404 fallback
- Starts HTTP server
src/
types.ts
parser.ts
evaluator.ts
compiler.ts
runtime.ts
index.ts
tests/
parser.test.ts
evaluator.test.ts
integration.test.ts
| Package | Purpose |
|---|---|
| postcss | CSS parsing |
| express | HTTP server framework |
| better-sqlite3 | SQLite database |
| commander | CLI argument parsing |
Adding new functions:
- Add type to
Expressioninsrc/types.ts - Add parsing logic in
src/parser.ts:parseExpression() - Add evaluation logic in
src/evaluator.ts:evaluateExpression()
Adding new conditions:
- Add type to
Conditioninsrc/types.ts - Add parsing logic in
src/parser.ts:parseCondition() - Add evaluation logic in
src/evaluator.ts:evaluateCondition()
Adding new HTTP methods:
- Add to
HttpMethodtype insrc/types.ts - Add case in
src/runtime.ts:registerRoute()
Define HTTP routes using CSS selector syntax.
[path="<route-path>"]:http_method {
}Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
Examples:
[path="/users"]:get {}
[path="/users/:id"]:get {}
[path="/users"]:post {}
[path="*"]:get {}Support @server at-rule.
| Property | Type | Description |
|---|---|---|
| port | number | Server port (default: 3000) |
| database | string | SQLite database path |
| host | string | Server host binding (default: localhost) |
Environment variables:
@server {
port: env(PORT, 3000);
database: env(DATABASE_PATH, ./app.db);
}| Function | Description | Example |
|---|---|---|
| param(:name) | Route parameter | --id: param(:id) |
| query(name) | Query string parameter | --q: query(q) |
| body(name) | Request body field | --name: body(name) |
| header(name) | HTTP header | --role: header(x-user-role) |
--variable-name: expression;--id: param(:id);
--user: sql("SELECT * FROM users WHERE id = ?", var(--id));
@return json(var(--user));sql("query", arg1, arg2, ...)Return values:
| Query Type | Return |
|---|---|
| SELECT | Array of rows or single row |
| INSERT | { id: lastInsertRowid, changes: number } |
| UPDATE | { changes: number } |
| DELETE | { changes: number } |
if(
condition: value;
else: default_value;
)Condition types: truthy, equals, not equals, greater than, less than, greater or equal, less or equal, AND, OR, NOT.
JSON:
@return json({ "key": "value" })
@return json(var(--data))HTML:
@return html("<h1>Hello</h1>")
@return html(var(--htmlContent))status: 404;
status: if(--authorized: 200; else: 403);css-server <file> [options]Options:
| Option | Description |
|---|---|
| -p, --port | Override server port |
| -h, --host | Override server host |
| -v, --version | Display version |
| --help | Display help |
- Parse CSS files in under 100ms for typical API definitions
- Execute SQL queries using prepared statements
- Reuse database connection across requests
- Report parse errors with line numbers
- Return 404 for unmatched routes
- Handle missing database gracefully
- Return JSON error objects for SQL failures
- Use parameterized queries to prevent SQL injection
- Do not expose internal error details to clients
- Validate route paths
Current limitations:
- No middleware support
- No authentication
- SQLite only
- No file uploads
- No CORS
- No rate limiting
Future considerations:
- Middleware hooks
- Multiple database support
- WebSocket support
- Server-side events
- Request validation
- Response caching
@server {
port: <number>;
database: <string>;
host: <string>;
}Environment variables:
@server {
port: env(PORT, 3000);
database: env(DATABASE_PATH, ./app.db);
host: env(HOST, localhost);
}@database {
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT
);
CREATE INDEX users_email ON users(email);
}The @database block is optional. If omitted, database features are unavailable unless a database is configured another way.
[path="<route-path>"]:http_method {
}HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
Path patterns:
| Pattern | Description | Example |
|---|---|---|
| / | Root path | [path="/"]:GET |
| /users | Static path | [path="/users"]:GET |
| /users/:id | Path with parameter | [path="/users/:id"]:GET |
| /posts/:postId/comments/:id | Multiple parameters | [path="/posts/:postId/comments/:id"]:GET |
| * | Catch-all / fallback | [path="*"]:GET |
Examples:
[path="/"]:GET {
@return html("<h1>Home</h1>");
}
[path="/users"]:GET {
@return json(sql("SELECT * FROM users"));
}
[path="/users"]:POST {
--name: body(name);
@return json(sql("INSERT INTO users (name) VALUES (?)", var(--name)));
}
[path="/users/:id"]:GET {
--id: param(:id);
@return json(sql("SELECT * FROM users WHERE id = ?", var(--id)));
}
[path="*"]:GET {
status: 404;
@return json({ "error": "Not found" });
}--variable-name: <expression>;--id: param(:id);
--user: sql("SELECT * FROM users WHERE id = ?", var(--id));
@return json(var(--user));param():
param(:parameter-name)query():
query(parameter-name)body():
body(field-name)header():
header(header-name)var():
var(--variable-name)sql():
sql("query", arg1, arg2, ...)Return values:
| Query Type | Return Value |
|---|---|
| SELECT (no args) | Array of row objects |
| SELECT (with args) | Single row object (.get()) |
| INSERT | { id: number, changes: number } |
| UPDATE | { changes: number } |
| DELETE | { changes: number } |
| Error | { error: string } |
if(
condition: value;
condition: value;
else: default-value;
)Condition types:
| Type | Syntax | Description |
|---|---|---|
| Truthy | --var | Variable is truthy (not null, not empty, not 0) |
| Equals | --var = value | Variable equals value |
| Not Equals | --var != value | Variable does not equal value |
| Greater Than | --var > number | Variable is greater than number |
| Less Than | --var < number | Variable is less than number |
| Greater or Equal | --var >= number | Variable is greater or equal |
| Less or Equal | --var <= number | Variable is less or equal |
| AND | --a and --b | Both conditions must be true |
| OR | --a or --b | Either condition must be true |
| NOT | not --var | Negate a condition |
@return json(<expression>);
@return html(<expression>);status: <number>;
status: if(<condition>: <number>; else: <number>);@server {
port: env(PORT, 3000);
database: env(DATABASE, ./app.db);
}
@database {
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT
);
}
[path="/"]:GET {
@return html("<h1>Welcome to CSS Server</h1>");
}
[path="/users"]:GET {
@return json(sql("SELECT * FROM users"));
}
[path="/users/:id"]:GET {
--id: param(:id);
--user: sql("SELECT * FROM users WHERE id = ?", var(--id));
@return json(if(--user: var(--user); else: { "error": "User not found" }));
}
[path="/users"]:POST {
--name: body(name);
--email: body(email);
@return json(sql("INSERT INTO users (name, email) VALUES (?, ?)", var(--name), var(--email)));
}
[path="/users/:id"]:PUT {
--id: param(:id);
--name: body(name);
--email: body(email);
@return json(sql("UPDATE users SET name = ?, email = ? WHERE id = ?", var(--name), var(--email), var(--id)));
}
[path="/users/:id"]:DELETE {
--id: param(:id);
@return json(sql("DELETE FROM users WHERE id = ?", var(--id)));
}
[path="/search"]:GET {
--q: query(q);
--results: sql("SELECT * FROM users WHERE name LIKE ?", var(--q));
@return json(if(--q: var(--results); else: []));
}
[path="/admin"]:GET {
--role: header(x-user-role);
status: if(--role = admin: 200; else: 403);
@return json(if(
--role = admin: { "message": "Welcome, admin!" };
else: { "error": "Access denied" };
));
}
[path="/check-age"]:GET {
--age: query(age);
@return json(if(
--age >= 18: { "status": "adult" };
--age >= 13: { "status": "teen" };
else: { "status": "child" };
));
}
[path="*"]:GET {
status: 404;
@return json({ "error": "Not found" });
}| Feature | Syntax |
|---|---|
| Server config | @server { ... } |
| Route | [path="/path"]:GET { ... } |
| Variable | --name: value; |
| Param | param(:name) |
| Query | query(name) |
| Body | body(name) |
| Header | header(name) |
| Variable ref | var(--name) |
| SQL | sql("query", args...) |
| If | if(cond: val; else: val) |
| Return JSON | @return json(...) |
| Return HTML | @return html(...) |
| Status | status: 404; |
| Equals | --var = value |
| Not equals | --var != value |
| Greater than | --var > number |
| Less than | --var < number |
| AND | --a and --b |
| OR | --a or --b |
| NOT | not --var |