{"id":13611872,"url":"https://github.com/cztomsik/tokamak","last_synced_at":"2025-05-16T07:00:18.007Z","repository":{"id":220718938,"uuid":"752374498","full_name":"cztomsik/tokamak","owner":"cztomsik","description":"Server-side framework for Zig, relying heavily on dependency injection.","archived":false,"fork":false,"pushed_at":"2025-05-13T15:21:26.000Z","size":225,"stargazers_count":400,"open_issues_count":2,"forks_count":13,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-13T16:39:40.331Z","etag":null,"topics":["api","express","framework","http","rest","server","spring","zig-package"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cztomsik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-03T18:40:05.000Z","updated_at":"2025-05-13T15:21:29.000Z","dependencies_parsed_at":"2024-03-18T00:20:59.657Z","dependency_job_id":"313f7d42-de4e-43da-9c11-8d4a4cd94fed","html_url":"https://github.com/cztomsik/tokamak","commit_stats":null,"previous_names":["cztomsik/tokamak"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cztomsik%2Ftokamak","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cztomsik%2Ftokamak/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cztomsik%2Ftokamak/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cztomsik%2Ftokamak/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cztomsik","download_url":"https://codeload.github.com/cztomsik/tokamak/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485025,"owners_count":22078764,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","express","framework","http","rest","server","spring","zig-package"],"created_at":"2024-08-01T19:02:16.681Z","updated_at":"2025-05-16T07:00:17.895Z","avatar_url":"https://github.com/cztomsik.png","language":"Zig","funding_links":[],"categories":["Zig","Libraries","\u003ca name=\"Zig\"\u003e\u003c/a\u003eZig","Network \u0026 Web"],"sub_categories":["Web Framework"],"readme":"# Tokamak\n\nTokamak is a server-side framework for Zig, built around\n[http.zig](https://github.com/karlseguin/http.zig) and a simple dependency\ninjection container.\n\nNote that it is **not designed to be used alone**, but with a reverse proxy in\nfront of it, like Nginx or Cloudfront, which will handle SSL, caching,\nsanitization, etc.\n\n\u003e ### Recent changes\n\u003e - injecting `tk.Injector` is deprecated, use `*tk.Injector`\n\u003e - multi-module support (cross-module initializers, providers, overrides)\n\u003e - Switched to [http.zig](https://github.com/karlseguin/http.zig) for improved\n\u003e   performance over `std.http`.\n\u003e - Implemented hierarchical and introspectable routes.\n\u003e - Added basic Swagger support.\n\u003e - Added `tk.static.dir()` for serving entire directories.\n\n## Getting Started\n\nSimple things should be easy to do.\n\n```zig\nconst std = @import(\"std\");\nconst tk = @import(\"tokamak\");\n\nconst routes: []const tk.Route = \u0026.{\n    .get(\"/\", hello),\n};\n\nfn hello() ![]const u8 {\n    return \"Hello\";\n}\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    const allocator = gpa.allocator();\n    defer _ = gpa.deinit();\n    \n    var server = try tk.Server.init(allocator, routes, .{ .listen = .{ .port = 8080 } });\n    try server.start();\n}\n```\n\n## Dependency Injection\n\nThe framework is built around the concept of dependency injection.\nThis means that your handler function can take any number of parameters, and the\nframework will try to provide them for you.\n\nNotable types you can inject are:\n\n- `std.mem.Allocator` (request-scoped arena allocator)\n- `*tk.Request` (current request, including headers, body reader, etc.)\n- `*tk.Response` (current response, with methods to send data, set headers, etc.)\n- `*tk.Injector` (the injector itself, see below)\n- and everything you provide yourself\n\nFor example, you can easily write a handler function which will create a\nstring on the fly and return it to the client without any tight coupling to the\nserver or the request/response types.\n\n```zig\nfn hello(allocator: std.mem.Allocator) ![]const u8 {\n    return std.fmt.allocPrint(allocator, \"Hello {}\", .{std.time.timestamp()});\n}\n```\n\nIf you return any other type than `[]const u8`, the framework will try to\nserialize it to JSON.\n\n```zig\nfn hello() !HelloRes {\n    return .{ .message = \"Hello\" };\n}\n```\n\nIf you need a more fine-grained control over the response, you can inject a\n`*tk.Response` and use its methods directly.\n\n\u003e But this will of course make your code tightly coupled to respective types\n\u003e and it should be avoided if possible.\n\n```zig\nfn hello(res: *tk.Response) !void {\n    try res.json(.{ .message = \"Hello\" }, .{});\n}\n```\n\n## Custom Dependencies\n\nYou can also provide your own (global) dependencies by passing your own\n`*tk.Injector` to the server.\n\n```zig\npub fn main() !void {\n    var db = try sqlite.open(\"my.db\");\n    var inj = tk.Injector.init(\u0026.{ .ref(\u0026db) }, null)\n\n    var server = try tk.Server.init(allocator, routes, .{\n        .injector = \u0026inj,\n        .port = 8080\n    });\n\n    try server.start();\n}\n```\n\n## Middleware\n\nWhile Tokamak doesn't have Express-style middleware, it achieves the same\nfunctionality through nested routes. Since routes can be nested and the\n`prefix`, `path`, and `method` fields are optional, you can create powerful\nmiddleware patterns.\n\nHere's how to create a simple logging middleware:\n\n```zig\nfn logger(children: []const Route) tk.Route {\n    const H = struct {\n        fn handleLogger(ctx: *Context) anyerror!void {\n            log.debug(\"{s} {s}\", .{ @tagName(ctx.req.method), ctx.req.url });\n\n            return ctx.next();\n        }\n    };\n\n    return .{ .handler = \u0026H.handleLogger, .children = children };\n}\n\nconst routes = []const tk.Route = \u0026.{\n    logger(\u0026.{\n        .get(\"/\", hello),\n    }),\n};\n```\n\nMiddleware handlers receive a `*Context` and return `anyerror!void`. They can\nperform pre-processing, logging, authentication, etc., and then call\n`ctx.next()` to continue to the next handler in the chain.\n\n\n## Request-Scoped Dependencies\n\nSince Zig doesn't have closures, you can't capture variables from the outer\nscope. Instead, Tokamak allows you to add request-scoped dependencies that will\nbe available to downstream handlers:\n\n```zig\nfn auth(ctx: *Context) anyerror!void {\n    const db = ctx.injector.get(*Db);\n    const token = try jwt.parse(ctx.req.getHeader(\"Authorization\"));\n    const user = db.find(User, token.id) catch null;\n\n    return ctx.nextScoped(\u0026.{ user });\n}\n```\n\n\u003e Note: Middleware handlers need to use `ctx.injector.get(T)` to access\n\u003e dependencies manually, as they don't support the automatic dependency\n\u003e injection syntax.\n\n## Routing\n\nTokamak includes an Express-inspired router that supports path parameters and\nwildcards. It can handle up to 16 path parameters and uses the `*` character for\nwildcards.\n\n```zig\nconst tk = @import(\"tokamak\");\n\nconst routes: []const tk.Route = \u0026.{\n    .get(\"/\", hello),                        // fn(...deps)\n    .get(\"/hello/:name\", helloName),         // fn(...deps, name)\n    .get(\"/hello/:name/:age\", helloNameAge), // fn(...deps, name, age)\n    .get(\"/hello/*\", helloWildcard),         // fn(...deps)\n    .post(\"/hello\", helloPost),              // fn(...deps, body)\n    .post0(\"/hello\", helloPost0),            // fn(...deps)\n    ...\n};\n```\n\nFor more organized routing, use the `Route.router(T)` method with a DSL-like\nstruct:\n\n```zig\nconst routes: []const tk.Route = \u0026.{\n    tk.logger(.{}),\n    .get(\"/\", tk.send(\"Hello\")),        // Classic Express-style routing\n    .group(\"/api\", \u0026.{ .router(api) }), // Structured routing with a module\n    .send(error.NotFound),\n};\n\nconst api = struct {\n    pub fn @\"GET /\"() []const u8 {\n        return \"Hello\";\n    }\n\n    pub fn @\"GET /:name\"(allocator: std.mem.Allocator, name: []const u8) ![]const u8 {\n        return std.fmt.allocPrint(allocator, \"Hello {s}\", .{name});\n    }\n};\n```\n\n## Error Handling\n\nTokamak handles errors gracefully by automatically serializing them to JSON:\n\n```zig\nfn hello() !void {\n    // This will send a 500 response with {\"error\": \"TODO\"}\n    return error.TODO;\n}\n```\n\n## Static Files\n\nServe static files easily with built-in helpers:\n\n```zig\nconst routes: []const tk.Route = \u0026.{\n    .get(\"/\", tk.static.file(\"public/index.html\")),\n};\n```\n\nServe entire directories:\n\n```zig\nconst routes: []const tk.Route = \u0026.{\n    tk.static.dir(\"public\", .{}),\n};\n```\n\nUse with wildcard routes for more flexibility:\n\n```zig\nconst routes: []const tk.Route = \u0026.{\n    tk.get(\"/assets/*\", tk.static.dir(\"assets\", .{ .index = null })),\n};\n```\n\nIf you want to embed some files into the binary, you can specify such paths to\nthe `tokamak.setup()` call in your `build.zig` file.\n\n```zig\nconst tokamak = @import(\"tokamak\");\n\n...\n\ntokamak.setup(exe, .{\n    .embed = \u0026.{\n        \"public/index.html\",\n    },\n});\n```\n\nIn this case, only the files listed in the `embed` array will be embedded into\nthe binary and any other files will be served from the filesystem.\n\n## MIME types\n\nThe framework will try to guess the MIME type based on the file extension, but\nyou can also provide your own in the root module.\n\n```zig\npub const mime_types = tk.mime_types ++ .{\n    .{ \".foo\", \"text/foo\" },\n};\n```\n\n## Configuration\n\nFor a simple configuration, you can use the `tk.config.read(T, opts)` function,\nwhich will read the configuration from a JSON file. The `opts` parameter is\noptional and can be used to specify the path to the config file and parsing\noptions.\n\n```zig\nconst Cfg = struct {\n    foo: u32,\n    bar: []const u8,\n};\n\nconst cfg = try tk.config.read(Cfg, .{ .path = \"config.json\" });\n```\n\nThere's also experimental `tk.config.write(T, opts)` function, which will write\nthe configuration back to the file.\n\n## Process Monitoring\n\nThe `tk.monitor(procs)` function runs multiple processes in parallel and\nautomatically restarts them if they crash. This creates a self-healing\napplication that stays running even after unexpected failures.\n\n```zig\nmonitor(.{\n    .{ \"server\", \u0026runServer, .{ 8080 } },\n    .{ \"worker\", \u0026runWorker, .{} },\n    ...\n});\n```\n\nIt takes a tuple of `{ name, fn_ptr, args_tuple }` triples as input.\n\n\u003e **Note:** This feature requires a system with `fork()` support. It takes over\n\u003e the main thread and forks processes, which may lead to unexpected behavior if\n\u003e used incorrectly. Use with caution.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcztomsik%2Ftokamak","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcztomsik%2Ftokamak","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcztomsik%2Ftokamak/lists"}