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:
- 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() !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 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):
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