webview_app
A desktop application combining a web-based UI with native functionality.
Source Code
Path: examples/webview_app/
const builtin = @import("builtin");
const c = @import("c");
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(init: std.process.Init) !void {
try tk.app.run(init, webviewMain, &.{App});
}
pub fn webviewMain(server: *tk.Server, gpa: std.mem.Allocator) !void {
const address = server.http.config.address;
const thread = try server.http.listenInNewThread();
defer thread.join();
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, "http://{f}", .{address}, 0);
defer gpa.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:
- Backend Server: Runs in a separate thread, serves static files and API endpoints
- 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(init: std.process.Init) !void {
try tk.app.run(init, webviewMain, &.{App});
}
pub fn webviewMain(server: *tk.Server, gpa: std.mem.Allocator) !void {
const address = server.http.config.address;
const thread = try server.http.listenInNewThread();
defer thread.join();
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, "http://{f}", .{address}, 0);
defer gpa.free(url);
_ = c.webview_navigate(w, url);
_ = c.webview_run(w);
server.stop();
}
Routes
Routes are defined in the App struct:
server_opts: tk.ServerOptions = .{},
routes: []const tk.Route = &.{
.get("/*", tk.static.dir("public", .{})),
.get("/api/hello", hello),
.get("/*", tk.static.dir("public", .{}))serves all files from thepublic/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):
defer gpa.free(url);
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