Jetzig
Description
Jetzig is a very complete and opinionated web framework. It offers all the basics of a web framework, such as routing, middleware and cookies. It also offers a templating language written in Zig. The project aims to be 100% Zig and not use any C code. It is also relatively fast because it uses http.zig as its HTTP server.
CLI
Jetzig project structure are quite complex and opinionated, that is why it comes with a CLI to generate a new project.
To install the CLI I recommend directly cloning the repository instead of downloading the zip file so that you avoid downloading an outdated version of the CLI, indeed updates are frequent as they hve to keep up with Zig and http.zig.
git clone https://github.com/jetzig-framework/jetzig
cd jetzig/cli
zig build install
mv zig-out/bin/jetzig /usr/local/bin // Assuming /usr/local/bin is in your PATH
And after that you should be able to use the jetzig
CLI anywhere in your terminal.
You can use this command to see all the available commands:
jetzig --help
The CLI can do far more than simply generate a new project, it can generate all the different components of a Jetzig project, such as views, layouts and middlewares.
Project structure
The framework being very opinionated, it forces the user to comply to a strict project structure that looks like this.
- favicon.ico
- styles.css
- jetzig.png
- index.zmpl
- index.zmpl
- get.zmpl
- put.zmpl
- post.zmpl
- patch.zmpl
- delete.zmpl
- root.zig
- viewo.zig
- main.zig
For each view you create, you get a zig file for handling all the different available HTTP methods and for each you get a zmpl
file in a folder with the same name as the view.
Basic example
Let's take back the structure we just mentionned and look at the viewo.zig
endpoint.
Your entry file looks like this.
const std = @import("std");
const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}
pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.created);
}
pub fn put(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn patch(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn delete(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
In this endpoint you can pass data to your zmpl
file. Like here where we pass the name
parameter of the request or the default "Jeremie" string.
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
var object = try data.root(.object);
const value = try request.params();
if (value.get("name")) |name| {
try object.put("name", name);
} else {
try object.put("name", data.string("Jeremie"));
}
return request.render(.ok);
}
If you visit http://localhost:8080/viewo
you will get a WEB page containing the following.
Your name is Jeremie
And if you visit http://localhost:8080/viewo?name=John
you will get the personalised input.
Your name is John
Cookies
You can do exactly the same but with cookies.
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
var object = try data.root(.object);
var cookies = try request.cookies();
try cookies.put(.{ .name = "username", .value = "Spongebob" });
if (cookies.get("username")) |name| {
try object.put("name", name.value);
} else {
try object.put("name", data.string("unknown"));
}
return request.render(.ok);
}
Tailwind Middleware
One of the upcoming features of Jetzig is a Tailwind middleware that allow to only include the CSS rules used by your project to keep the file size as small as possible, however before this feature comes out you have two options.
The first is to simply use the CDN of Tailwind in the .zmpl
file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tailwind CSS</title>
<link href="../../../output.css" rel="stylesheet">
</head>
<body>
<div class="h1 bg-red-500 text-white p-4 rounded">
Hello with my CDN Tailwind!
</div>
</body>
</html>
The problem with this solution is that you are going to serve the entire Tailwind CSS file at runtime for each request.
The second which makes most of the work at build time is according to the author of the framework himself it is almost equivalent to what he imagined for the Tailwind middleware. We are simply going to install Tailwind in our project following the official documentation and modifying some parts to fit the structure of our project.
# At the root of our project folder
npm install -D tailwindcss
npx tailwindcss init
The modify the tailwind.config.js
file to fit the structure of our project.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/app/views/**/*.zmpl"],
theme: {
extend: {},
},
plugins: [],
}
Then in our /public
directory create an input.css
file.
@tailwind base;
@tailwind components;
@tailwind utilities;
Then simply start the Tailwind build process.
npx tailwindcss -i ./public/input.css -o ./public/output.css --watch
To conclude with this second solution you do not really need the future Tailwind middleware.
HTMX Middleware
The HTMX middleware is going to allow only partial pages reloads, without calling the endpoint agains and do a full page refresh when calling hx-get and hx-target together. More informations can be found on this discord answer from the author itself.
To leverage the middleware just uncomment this line in the main.zig
file.
jetzig.middleware.HtmxMiddleware
Other than that you can just use HTMX as you would normally do.
Conclusion
Jetzig is a very complete and begginer friendly web framework, the CLI guides by creating whatever you need and puting it in the right place, making all the Jetzig projects follow the same structure. The framework is currently under heavy development and plan to add even more features like database integration into the project. The templating language allows you to develop your frontend and backend in the same language, which is a great advantage for small projects. The only downside is that there are a lot of features and not all of them are deeply documented yet, note that all the the generated code is heavily commented so you can try to understand what is happening by reading the source code. Having such an opiniated framework can also be annyoing sometimes, for example you have to have 1 file per endpoint plus a whole folder containing all the different possible views for each endpoint, this can be problematic if you write a lot of small endpoints, having to have at least 2 files and 1 folder for each of them can be a bit annoying.