webview_app

A desktop application combining a web-based UI with native functionality.

Source Code

Path: examples/webview_app/

const builtin = @import("builtin");
const std = @import("std");
const tk = @import("tokamak");

const App = struct {
    server: tk.Server,
    server_opts: tk.ServerOptions = .{},
    routes: []const tk.Route = &.{
        .get("/*", tk.static.dir("public", .{})),
        .get("/api/hello", hello),
    },

    fn hello() ![]const u8 {
        return "Hello, world!";
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const ct = try tk.Container.init(gpa.allocator(), &.{App});
    defer ct.deinit();

    const server = try ct.injector.get(*tk.Server);
    const port = server.http.config.port.?;

    const thread = try server.http.listenInNewThread();
    defer thread.join();

    const c = @cImport({
        @cInclude("stddef.h");
        @cInclude("webview.h");
    });

    const w = c.webview_create(if (builtin.mode == .Debug) 1 else 0, null);
    defer _ = c.webview_destroy(w);

    _ = c.webview_set_title(w, "Example");
    _ = c.webview_set_size(w, 800, 500, c.WEBVIEW_HINT_NONE);

    const url = try std.fmt.allocPrintSentinel(gpa.allocator(), "http://127.0.0.1:{}", .{port}, 0);
    defer gpa.allocator().free(url);

    _ = c.webview_navigate(w, url);
    _ = c.webview_run(w);
    server.stop();
}

Features Demonstrated

  • Webview integration for desktop apps
  • Static file serving
  • API endpoints for backend logic
  • Server running in background thread
  • C library integration (@cImport)
  • Cross-platform desktop app development

Prerequisites

This example requires the webview library to be installed on your system.

Architecture

The application combines:

  1. Backend Server: Runs in a separate thread, serves static files and API endpoints
  2. Webview Window: Embeds a browser that loads the local server
const App = struct {
    server: tk.Server,
    server_opts: tk.ServerOptions = .{},
    routes: []const tk.Route = &.{
        .get("/*", tk.static.dir("public", .{})),
        .get("/api/hello", hello),
    },

    fn hello() ![]const u8 {
        return "Hello, world!";
    }
};

How It Works

The main function shows the complete flow:

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const ct = try tk.Container.init(gpa.allocator(), &.{App});
    defer ct.deinit();

    const server = try ct.injector.get(*tk.Server);
    const port = server.http.config.port.?;

    const thread = try server.http.listenInNewThread();
    defer thread.join();

    const c = @cImport({
        @cInclude("stddef.h");
        @cInclude("webview.h");
    });

    const w = c.webview_create(if (builtin.mode == .Debug) 1 else 0, null);
    defer _ = c.webview_destroy(w);

    _ = c.webview_set_title(w, "Example");
    _ = c.webview_set_size(w, 800, 500, c.WEBVIEW_HINT_NONE);

    const url = try std.fmt.allocPrintSentinel(gpa.allocator(), "http://127.0.0.1:{}", .{port}, 0);
    defer gpa.allocator().free(url);

    _ = c.webview_navigate(w, url);
    _ = c.webview_run(w);
    server.stop();
}

Routes

Routes are defined in the App struct:

    routes: []const tk.Route = &.{
        .get("/*", tk.static.dir("public", .{})),
        .get("/api/hello", hello),
    },
  • .get("/*", tk.static.dir("public", .{})) serves all files from the public/ directory
  • .get("/api/hello", hello) is a backend API that the frontend can call

Frontend Integration

Your frontend JavaScript can call the backend API:

fetch('/api/hello')
  .then(response => response.text())
  .then(data => console.log(data));

Development vs Production

The webview can show dev tools in debug mode (see line 36 in the source):

    const w = c.webview_create(if (builtin.mode == .Debug) 1 else 0, null);
    defer _ = c.webview_destroy(w);

The first argument to webview_create is 1 for dev tools enabled, 0 for disabled.

Running

cd examples/webview_app
zig build run

A desktop window will open showing your web UI.

Use Cases

This pattern is great for:

  • Desktop applications with web UI
  • Tools that need native OS integration
  • Applications requiring file system access
  • Cross-platform GUI apps without heavy frameworks

Architecture Benefits

  • Familiar Technologies: Build UI with HTML/CSS/JavaScript
  • Backend Power: Full Zig capabilities for system operations
  • Small Binary: No Electron overhead
  • Native Performance: Direct system access from Zig backend

Next Steps

  • Add more API endpoints for your application logic
  • Implement file system operations in the backend
  • Use WebSockets for real-time communication
  • Explore the webview library for more platform-specific features