A simple framework for writing web services in zig.
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
flopetautschnig 4708c03a48
Update README.md
11 months ago
src fix accept GET query string in URI 11 months ago
.gitignore update .gitignore 11 months ago
LICENSE Create LICENSE 1 year ago
README.md Update README.md 11 months ago
build.zig make lib and zig.mod 1 year ago
gyro.zzz update gyro.zzzz 11 months ago
zig.mod enable for lower than zig version 0.11.0 1 year ago

README.md

zerve

A simple framework for writing web services in zig.

Create a simple web app

const zrv = @import("zerve"); // Or set the path to zerve.zig e.g. @import("zerve-main/src/zerve.zig");
const Request = zrv.Request;
const Response = zrv.Response;
const Server = zrv.Server;
const Route = zrv.Route;
const allocator = std.heap.page_allocator; // Choose any allocator you want!

fn index(req: *Request) Response {
    _=req;
    return Response.write("hello!");
}

fn about(req: *Request) Response {
    _=req;
    return Response.write("about site");
}

fn writeJson(req: *Request) Response {
    _=req;
    return Response.json("[1, 2, 3, 4]");
}

pub fn main() !void {
     const rt = [_]Route{.{"/", index}, .{"/about", about}, .{"/json", writeJson}};

     try Server.listen("0.0.0.0", 8080, &rt, allocator); // listens to http://localhost:8080
                                                         // http://localhost:8080/  "hello!"
                                                         // http://localhost:8080/about "about site"
                                                         // http://localhost:8080/json  "[1, 2, 3, 4]" (JSON-Response)
}

Types

Route

To write a web service with zerve you have to configure one or more Routes. They are being set by creating an Array of Route.

Example:

const rt = [_]Route{.{"/hello", helloFunction}, "/about", aboutFunction};

You can also set only one path and link it to a handler function, but since Server.listen() takes an Array of Route as one of it's arguments, you have do declare it as an Array as well:

const rt = [_]Route{.{"/hello", helloFunction}};

Handler Functions

Every Request is handled by a handler function. It has to be of this type: fn(req: *Request) Response

Example:

fn hello(req: *Request) Response {
    _ = req;
    return Response.write("hello"); // `Server` will return a Reponse with body "hello". You will see "hello" on your browser.
}

Request

This represents the Request sent by the client.

pub const Request = struct {
    /// The Request Method, e.g. "GET"
    method: Method,
    /// HTTP-Version of the Request sent by the client
    httpVersion: HTTP_Version,
    /// Represents the request headers sent by the client
    headers: []const Header,
    /// The Request URI
    uri: []const u8,
    /// Represents the request body sent by the client
    body: []const u8,
};

Get Query Params

zerve lets you easily extract query params no matter if Requestmethod is GETor POST.

This can be done by using the getQuery method of Request.

Example:

fn index(req: Request) Response {

    // Assuming that a query string has been sent by the client containing the requested param,
    // e.g. `?user=james`

    const user = req.getQuery("user"); // This will return an optional
    
    if (user == null) return Response.write("") else return Response.write(user.?);
    
}

Get value of Request header by key

You can get the header value of any sent header by the client with the headermethod of Request.

Example:

fn index(req: *Request) Response {
    
    // Get value of the 'Content-Type' header

    const h = req.header("Content-Type"); // This will return an optional

    if (h == null) return Response.write("Header not found!") else return Response.write(h.?);

}

Response

A Response that is sent ny the server. Every handler function has to return a Response.

pub const Response = struct {
    httpVersion: HTTP_Version = HTTP_Version.HTTP1_1,
    /// Response status, default is "200 OK"
    status: stat.Status = stat.Status.OK,
    /// Response eaders sent by the server
    headers: []const Header = &[_]Header{.{ .key = "Content-Type", .value = "text/html; charset=utf-8" }},
    /// Response body sent by the server
    body: []const u8 = "",

    /// Write a simple response.
    pub fn write(s: []const u8) Response

    /// Send a response with json content.
    pub fn json(j: []const u8) Response

    /// Send a response with status not found.
    pub fn notfound(s: []const u8) Response

    /// Send a response with status forbidden.
    pub fn forbidden(s: []u8) Response
};

Header

Every Request or Response has Headers represented by an Array of Headers. Every Header has a key and a value.

pub const Header = struct {
    key: []const u8,
    value: []const u8,
};

Cookies

To read the Cookie of a request by key, Request has a cookie-method. It returns an optional and fetches the value of a Request.Cookie.

Get Request Cookie value by key:

fn index(req: *Request) Response {
    
    // Fetches the cookie value by cookie name.
    // The `cookie` method will return an optional and will be `null`
    // in case that the cookie does not exist.

    const cookie = if (req.cookie("password")) |password| password else "";

    return Response.write("cookie-test");
}

To send a cookie in your Response just add a Response.Cookie to the cookies field. The cookies field is a slice of Response.Cookie.

fn index(_: *Request) Response {

    // Define a cookie with name and value.
    // It will live for 24 hours, since `maxAge` represents
    // lifetime in seconds.
    // See all field of the `Response.Cookie` struct below.

    const cookie = Response.Cookie{.name="User", .value="James", .maxAge=60*60*24};

    var res = Response.write("Set Cookie!");
    // add cookie to the `cookies` field which is a slice of `Response.Cookie`
    res.cookies = &[_]Response.Cookie{.{cookie}};
    
    return res;
}

This are the fields of Response.Cookie:

    name: []const u8,
    value: []const u8,
    path: []const u8 = "/",
    domain: []const u8 = "",
    /// Indicates the number of seconds until the cookie expires.
    maxAge: i64 = 0,
    secure: bool = true,
    httpOnly: bool = true,
    sameSite: SameSite = .lax,

Method

Represents the http method of a Request or a Response.

pub const Method = enum {
    GET,
    POST,
    PUT,
    HEAD,
    DELETE,
    CONNECT,
    OPTIONS,
    TRACE,
    PATCH,
    UNKNOWN,

    /// Turns the HTTP_method into a u8-Slice.
    pub fn stringify(m: Method) []const u8 {...}
};

HTTP-Version

The HTTP-Version of a Request or a Response.

pub const HTTP_Version = enum {
    HTTP1_1,
    HTTP2,

    /// Parses from `[]u8`
    pub fn parse(s: []const u8) HTTP_Version {...}

    /// Stringifies `HTTP_Version`
    pub fn stringify(version: HTTP_Version) []const u8 {...}

};

Namespaces

Server

Server is a namespace to configure IP and Port the app will listen to by calling Server.listen(), as well as the routing paths ([]Route) it shall handle. You can also choose an allocator that the app will use for dynamic memory allocation.

pub fn listen(ip: []const u8, port: u16, rt: []const Route, allocator: std.mem.Allocator) !void {...}