Skip to content

Tutorial

Lee Yunjin edited this page Oct 7, 2026 · 1 revision

CWIST Web Development Tutorial (A to Z)

한국어: korean/cwist_tutorial.md

Welcome, especially if you already know Spring Boot or ReactJS! CWIST is a high-performance, ultra-lightweight web framework written in C. This tutorial helps you quickly grasp CWIST's philosophy and structure so you can put it to work on real web applications right away.

Philosophy: "Explicit, lightweight, and safe." CWIST keeps magic to a minimum and uses 100% of C's performance while still offering a modern web development experience (routing, JSON, DB, JWT, WebSocket).


Table of Contents

  1. Hello World: Starting Your First Server
  2. Routing and Handlers (Mux Router)
  3. JSON Parsing and Zod Schema Validation
  4. Middleware (CORS, Logging)
  5. Databases and Migrations (SQLite)
  6. Authentication (JWT and DB Encryption)
  7. PQC TLS (Post-Quantum Hybrid Key Exchange)
  8. WebSocket Integration (Real-Time Bidirectional Communication)
  9. Template Engine and Static File Serving
  10. Dynamic CSS Composer (WASM and SSR)

1. Hello World: Starting Your First Server

The heart of CWIST is the cwist_app object. Much like Spring's ApplicationContext, it manages the application's lifecycle.

#include <cwist/sys/app/app.h>
#include <cwist/core/sstring/sstring.h>

// Request handler (Spring's @GetMapping)
void hello_handler(cwist_http_request *req, cwist_http_response *res) {
    cwist_sstring_assign(res->body, "Hello, CWIST World!");
    cwist_http_header_add(&res->headers, "Content-Type", "text/plain");
}

int main() {
    // 1. Create the app
    cwist_app *app = cwist_app_create();
    
    // 2. Configure the router
    cwist_app_get(app, "/hello", hello_handler);
    
    // 3. Run the server on port 8080 (blocking)
    cwist_app_listen(app, 8080);
    
    cwist_app_destroy(app);
    return 0;
}

2. Routing and Handlers (Mux Router)

This section shows how to handle dynamic paths (path parameters) and query strings (query parameters). CWIST offers an intuitive pattern very similar to React Router or Express.

#include <cwist/app.h>
#include <stdio.h>

// Example: /users/:id?role=admin
void user_profile_handler(cwist_http_request *req, cwist_http_response *res) {
    // Read the path parameter (:id)
    const char *user_id = cwist_query_map_get(req->path_params, "id");
    
    // Read the query parameter (?role=admin)
    const char *role = cwist_query_map_get(req->query_params, "role");

    char buf[256];
    snprintf(buf, sizeof(buf), "User ID: %s, Role: %s", user_id, role ? role : "user");
    
    cwist_sstring_assign(res->body, buf);
}

int main() {
    cwist_app *app = cwist_app_create();
    
    // Register a dynamic route
    cwist_app_get(app, "/users/:id", user_profile_handler);
    // ...
}

3. JSON Parsing and Zod Schema Validation

A powerful runtime schema validator inspired by zod from the TypeScript world. It safely parses the JSON sent by the client and validates its types.

#include <cwist/app.h>
#include <cwist/core/utils/zod.h>
#include <cjson/cJSON.h>

// 1. Define the schema (name is a required string, age is a required integer)
static const cwist_schema_field_t user_fields[] = {
    {"name", {NULL}, CWIST_FIELD_STRING, true},
    {"age", {NULL}, CWIST_FIELD_INT, true},
};
static const cwist_schema_t user_schema = {user_fields, 2};

void create_user_handler(cwist_http_request *req, cwist_http_response *res) {
    // 2. Validate (Body -> JSON)
    cJSON *parsed_json = NULL;
    const char *raw = (req->body && req->body->data) ? req->body->data : "";
    cwist_zod_result_t z_res = cwist_zod_parse(raw, &user_schema, &parsed_json);

    if (!z_res.valid || !parsed_json) {
        res->status_code = CWIST_HTTP_BAD_REQUEST;
        cwist_sstring_assign(res->body,
                             z_res.error_count > 0 ? z_res.errors[0].message : "invalid JSON");
        return;
    }

    res->status_code = CWIST_HTTP_CREATED;

    // 3. Build the JSON response (cJSON builder pattern)
    cJSON *reply = cJSON_CreateObject();
    cJSON_AddStringToObject(reply, "status", "User created");
    cJSON_AddStringToObject(reply, "name", cJSON_GetObjectItem(parsed_json, "name")->valuestring);

    char *json_str = cJSON_PrintUnformatted(reply);
    cwist_http_header_add(&res->headers, "Content-Type", "application/json");
    cwist_sstring_assign(res->body, json_str);

    cJSON_free(json_str);
    cJSON_Delete(reply);
    cJSON_Delete(parsed_json);
}

4. Middleware (CORS, Logging)

You can easily build a pipeline that every request passes through (Spring's Interceptor, Express's Middleware).

#include <cwist/app.h>

// Middleware for CORS handling: calling next proceeds to the next stage (the next middleware or the handler)
void cors_middleware(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next) {
    cwist_http_header_add(&res->headers, "Access-Control-Allow-Origin", "*");
    cwist_http_header_add(&res->headers, "Access-Control-Allow-Methods", "GET, POST, OPTIONS");

    // Respond immediately to OPTIONS requests (not calling next stops the chain)
    if (req->method == CWIST_HTTP_OPTIONS) {
        res->status_code = CWIST_HTTP_NO_CONTENT;
        return;
    }

    if (next) next(req, res);
}

int main() {
    cwist_app *app = cwist_app_create();

    // Register the middleware (applied globally)
    cwist_app_use(app, cors_middleware);
    // ...
}

5. Databases and Migrations (SQLite)

CWIST fully supports embedded SQLite and provides a connection pool and migration tooling tied to the app lifecycle.

#include <cwist/app.h>
#include <cwist/core/db/sql.h>
#include <cwist/core/db/migrate.h>

void get_users_handler(cwist_http_request *req, cwist_http_response *res) {
    // req->db is the DB instance attached to the app with cwist_app_use_db()
    cwist_db *db = req->db;

    sqlite3_stmt *stmt = NULL;
    if (!db || sqlite3_prepare_v2(db->conn, "SELECT id, name FROM users", -1, &stmt, NULL) !=
                   SQLITE_OK) {
        res->status_code = CWIST_HTTP_INTERNAL_ERROR;
        return;
    }

    cJSON *users_array = cJSON_CreateArray();
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        cJSON *user = cJSON_CreateObject();
        cJSON_AddNumberToObject(user, "id", sqlite3_column_int(stmt, 0));
        cJSON_AddStringToObject(user, "name", (const char *)sqlite3_column_text(stmt, 1));
        cJSON_AddItemToArray(users_array, user);
    }
    sqlite3_finalize(stmt);

    char *json_str = cJSON_PrintUnformatted(users_array);
    cwist_http_header_add(&res->headers, "Content-Type", "application/json");
    cwist_sstring_assign(res->body, json_str);
    cJSON_free(json_str);
    cJSON_Delete(users_array);
}

// Schema migrations (each applied exactly once, in version order)
static const cwist_migration_t migrations[] = {
    {.version = 1,
     .name = "create_users",
     .up_sql = "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);",
     .down_sql = "DROP TABLE users;"},
    {.version = 2,
     .name = "insert_seed",
     .up_sql = "INSERT INTO users (name) VALUES ('Alice'), ('Bob');",
     .down_sql = "DELETE FROM users;"},
};

int main() {
    cwist_app *app = cwist_app_create();

    // Connect the DB ("file.db" or ":memory:")
    cwist_error_t err = cwist_app_use_db(app, "app_data.db");
    if (!cwist_error_is_ok(&err)) {
        cwist_error_dispose(&err);
        cwist_app_destroy(app);
        return 1;
    }

    // Apply the schema migrations automatically
    if (cwist_migrate_up(cwist_app_get_db(app)->conn, migrations, 2) != CWIST_MIGRATE_OK) {
        cwist_app_destroy(app);
        return 1;
    }

    cwist_app_get(app, "/users", get_users_handler);
    cwist_app_listen(app, 8080);
    cwist_app_destroy(app);
    return 0;
}

6. Authentication (JWT and DB Encryption)

Stateless JWT authentication, a must for the modern web, is supported through built-in functions.

#include <cwist/app.h>
#include <cwist/core/mem/alloc.h>
#include <cwist/security/jwt/jwt.h>
#include <string.h>

#define SECRET_KEY "my_super_secret"

// Issue a JWT on successful login
void login_handler(cwist_http_request *req, cwist_http_response *res) {
    cJSON *payload = cJSON_CreateObject();
    cJSON_AddStringToObject(payload, "user_id", "12345");
    cJSON_AddStringToObject(payload, "role", "admin");
    char *payload_json = cJSON_PrintUnformatted(payload);
    cJSON_Delete(payload);

    // Create a token valid for 3600 seconds (1 hour); the exp claim is added automatically
    char *token = cwist_jwt_sign(payload_json, SECRET_KEY, 3600);
    cJSON_free(payload_json);

    if (!token) {
        res->status_code = CWIST_HTTP_INTERNAL_ERROR;
        return;
    }
    cwist_sstring_assign(res->body, token);
    cwist_free(token);
}

// Middleware that verifies the JWT on API requests
void auth_middleware(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next) {
    const char *auth_header = cwist_http_header_get(req->headers, "Authorization");

    if (auth_header && strncmp(auth_header, "Bearer ", 7) == 0) {
        // Returns a claims object if the signature and exp check out, NULL otherwise
        cwist_jwt_claims *claims = cwist_jwt_verify(auth_header + 7, SECRET_KEY);
        if (claims) {
            // Token is valid, let it through! (e.g. cwist_jwt_claims_get(claims, "role"))
            cwist_jwt_claims_destroy(claims);
            if (next) next(req, res);
            return;
        }
    }

    // Respond 401 on failure (not calling next stops the chain)
    res->status_code = CWIST_HTTP_UNAUTHORIZED;
    cwist_sstring_assign(res->body, "{\"error\": \"Unauthorized\"}");
}

7. PQC TLS (Post-Quantum Hybrid Key Exchange)

CWIST can enable post-quantum hybrid TLS with a single line of code. It is a hybrid KEM that combines classic X25519 ECDH with the NIST-standard ML-KEM-768 (Kyber family), so the key exchange stays secure even against quantum computers.

#include <cwist/sys/app/app.h>

int main() {
    cwist_app *app = cwist_app_create();

    // Enable HTTPS
    cwist_app_use_https(app, "server.crt", "server.key");

    // Enable the PQC hybrid layer: one line is all it takes
    cwist_app_use_pqc_layer(app, true);

    // Every TLS 1.3 connection now uses the X25519MLKEM768:X25519:P-256 groups.
    // TLS 1.2 and below are disabled automatically.
    cwist_app_listen(app, 8443);
    cwist_app_destroy(app);
    return 0;
}

Security Policy Summary

Item Setting
Key Exchange Group X25519MLKEM768:X25519:P-256
Minimum TLS version 1.3
Legacy TLS Disabled (1.0, 1.1, 1.2 removed)
Downgrade protection Enabled

Note: This setting applies only to key exchange at the transport layer. Switching certificate signatures to PQC as well would require a separate feature such as cwist_app_use_pqc_cert(), which is still considered a step too far for the current ecosystem.


8. WebSocket Integration (Real-Time Bidirectional Communication)

CWIST can easily upgrade HTTP traffic to WebSocket on the same port. Once you register a path with cwist_app_ws(), the framework handles the upgrade handshake and your handler only deals with the connected cwist_websocket.

#include <cwist/app.h>
#include <cwist/net/websocket/websocket.h>
#include <stdio.h>

// Called once per connection. The framework handles the upgrade (101 response),
// and the connection is cleaned up when this function returns.
void chat_handler(cwist_websocket *ws) {
    cwist_ws_frame *frame;
    // NULL when the connection closes or errors (PONG replies to PING are automatic)
    while ((frame = cwist_websocket_receive(ws)) != NULL) {
        if (frame->opcode == CWIST_WS_FRAME_CLOSE) {
            cwist_websocket_frame_destroy(frame);
            break;
        }
        if (frame->opcode == CWIST_WS_FRAME_TEXT) {
            printf("Received: %.*s\n", (int)frame->payload_len, (const char *)frame->payload);
            // Echo reply (send it back to the client)
            cwist_websocket_send(ws, CWIST_WS_FRAME_TEXT, frame->payload, frame->payload_len);
        }
        cwist_websocket_frame_destroy(frame);
    }
    printf("Client disconnected.\n");
}

int main() {
    cwist_app *app = cwist_app_create();
    cwist_app_ws(app, "/chat", chat_handler);
    cwist_app_listen(app, 8080);
    cwist_app_destroy(app);
    return 0;
}

9. Template Engine and Static File Serving

Useful when building an HTML-based SSR (Server-Side Rendering) project or serving a React build output (static files).

#include <cwist/app.h>
#include <cwist/core/template/template.h>
#include <cjson/cJSON.h>

void render_home_handler(cwist_http_request *req, cwist_http_response *res) {
    (void)req;

    // Inject data (e.g. substitute the {{ title }} variable)
    cJSON *context = cJSON_CreateObject();
    cJSON_AddStringToObject(context, "title", "CWIST Homepage");
    cJSON_AddStringToObject(context, "user", "Developer");

    // Read the template file + render it
    cwist_sstring *output = cwist_template_render_file("views/index.html", context);
    cJSON_Delete(context);

    // Respond after rendering
    if (output) {
        cwist_sstring_assign(res->body, output->data);
        cwist_sstring_destroy(output);
    } else {
        res->status_code = CWIST_HTTP_INTERNAL_ERROR;
        cwist_sstring_assign(res->body, "Failed to render template");
    }
    cwist_http_header_add(&res->headers, "Content-Type", "text/html");
}

int main() {
    cwist_app *app = cwist_app_create();

    cwist_app_get(app, "/", render_home_handler);

    // Mount a static directory (for serving React/Vue build output)
    // Requests to the "/public" URL are served files from the "./public" folder
    cwist_app_static(app, "/public", "./public");

    cwist_app_listen(app, 8080);
    cwist_app_destroy(app);
    return 0;
}

10. Dynamic CSS Composer (WASM and SSR)

Going beyond a plain backend, CWIST builds on C's strong numeric computation to ship a CSS Composer that synthesizes a design system at runtime. It mathematically derives Hover/Active states from a color's lightness and computes roundness and spacing dynamically.

Here are two typical ways to use it.

Approach A: 100% Server-Side Rendering (SSR)

The server generates theme CSS dynamically and injects it at render time. This is very useful for offering per-user custom themes.

#include <cwist/app.h>
#include <cwist/core/html/css_composer.h>

void theme_css_handler(cwist_http_request *req, cwist_http_response *res) {
    cwist_css_config cfg;
    cwist_css_config_init(&cfg);

    // Parse the hex code from the query parameter and set it as the primary color
    const char *color = cwist_query_map_get(req->query_params, "color");
    if (color) {
        cfg.primary_color = cwist_color_hex_to_rgb(color);
    }
    
    // Set whether dark mode is on
    const char *dark = cwist_query_map_get(req->query_params, "dark");
    cfg.is_dark_mode = (dark && strcmp(dark, "1") == 0);

    // Run the math needed for CSS composition (HSL conversion, etc.) and return the stylesheet string
    cwist_sstring *css_output = cwist_css_generate_stylesheet(&cfg);

    cwist_sstring_assign(res->body, css_output->data);
    cwist_http_header_add(&res->headers, "Content-Type", "text/css");
    
    cwist_sstring_destroy(css_output);
}

int main() {
    cwist_app *app = cwist_app_create();
    
    // Reachable via <link rel="stylesheet" href="/theme.css?color=ff5733&dark=1">
    cwist_app_get(app, "/theme.css", theme_css_handler);
    
    cwist_app_listen(app, 8080);
    cwist_app_destroy(app);
    return 0;
}

Approach B: Client-Side Composition with React + WebAssembly (WASM)

CWIST's css_composer.c is written independently of the framework, so you can build it to .wasm with Emscripten, import it into a React app, and run the computation instantly on the client.

  1. WASM build (Emscripten)
emcc src/core/html/css_composer.c -Iinclude \
    -s EXPORTED_FUNCTIONS="['_cwist_color_hex_to_rgb', '_cwist_css_generate_stylesheet', '_malloc', '_free']" \
    -o public/css_composer.js
  1. Using it from React (dynamic design system)
import React, { useEffect, useState } from 'react';

function DynamicThemeApp() {
  const [themeColor, setThemeColor] = useState("#3B82F6");

  useEffect(() => {
    // 1. Load the WASM module
    window.Module().then((module) => {
      // 2. Convert the input hex code into C's RGB struct
      const hexPtr = module.allocateUTF8(themeColor);
      const rgb = module._cwist_color_hex_to_rgb(hexPtr);
      module._free(hexPtr);

      // (Hypothetical example) Lay out C's config struct in memory, then compose the CSS
      // In practice it is easier to write a JS <-> C bridge function (wrapper) and call that.
      const cssStringPtr = module._cwist_css_generate_stylesheet(/* config_ptr */);
      const cssString = module.UTF8ToString(cssStringPtr);

      // 3. Inject it into the browser DOM immediately
      document.getElementById('dynamic-theme').innerText = cssString;
    });
  }, [themeColor]);

  return (
    <div className="bg-body text-main">
      <style id="dynamic-theme"></style>
      <input type="color" value={themeColor} onChange={e => setThemeColor(e.target.value)} />
      <button className="bg-primary radius-md">CWIST Themed Button</button>
    </div>
  );
}

Wrapping Up

We hope this tutorial showed how familiar and declarative web development can be, even in a C-based environment. Just remember that the cwist_app struct owns the whole lifecycle and registration functions such as cwist_app_get() handle routing, and you can build with the same architecture as today's modern frameworks.


Source: docs/tutorial/cwist_tutorial.md on dev. Edit the source file in the repository; this page is regenerated from it.

Clone this wiki locally