diff --git a/src/main.zig b/src/main.zig
index ad07c31..de99fbb 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -4,158 +4,197 @@ const net = std.net;
 const http = std.http;
 const heap = std.heap;
 
+const App = Listener(Router);
+
 pub fn main() !void {
     var gpa = std.heap.GeneralPurposeAllocator(.{}){};
     defer _ = gpa.deinit();
     const allocator = gpa.allocator();
 
-    const router = BasicRouter.init(allocator);
+    var router = Router.init(allocator, &handle, &handleError);
     defer router.deinit();
 
+    try router.addRoute("/", &handle);
+
     const address = try net.Address.parseIp("0.0.0.0", 8080);
-    var listener = Listener.init(address, allocator, &handle);
+    var listener = App.init(.{
+        .address = address,
+        .allocator = allocator,
+        .context = router,
+        .handler = router.handle,
+    });
     defer listener.deinit();
     try listener.listen();
 }
 
-pub fn handle(event: *Listener.Event) anyerror!void {
+pub fn handle(_: *Router, event: *App.Event) anyerror!void {
     try event.res.body.appendSlice("hello there");
 }
 
-pub const Listener = struct {
-    address: net.Address,
-    arena: heap.ArenaAllocator,
-    handlerFn: *const fn (event: *Event) anyerror!void,
+pub fn handleError(_: Router, event: *App.Event) anyerror!void {
+    try event.res.body.appendSlice("ahoy, an error occurred");
+}
 
-    pub fn init(address: net.Address, allocator: mem.Allocator, handler: *const fn (event: *Event) anyerror!void) Listener {
-        return .{
-            .address = address,
-            .arena = heap.ArenaAllocator.init(allocator),
-            .handlerFn = handler,
-        };
-    }
-
-    pub fn deinit(self: *Listener) void {
-        self.arena.deinit();
-    }
-
-    pub fn listen(self: *Listener) !void {
-        var tcp = try self.address.listen(.{});
-        defer tcp.deinit();
-        std.debug.print("listening at: {any}\n", .{self.address});
-
-        while (true) {
-            const read_buf = try self.arena.allocator().alloc(u8, 1024 * 32);
-            const connection = try tcp.accept();
-            var server = http.Server.init(connection, read_buf);
-            try self.handle(&server);
-            _ = self.arena.reset(.retain_capacity);
-        }
-    }
-
-    fn handle(self: *Listener, server: *http.Server) !void {
-        handler: while (true) {
-            var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
-
-            var event = Event{
-                .req = .{
-                    .uri = try std.Uri.parseWithoutScheme(req.head.target),
-                    .headers = std.ArrayList(*const http.Header).init(self.arena.allocator()),
-                },
-                .res = .{
-                    .status = .ok,
-                    .headers = std.StringHashMap(*http.Header).init(self.arena.allocator()),
-                    .body = std.ArrayList(u8).init(self.arena.allocator()),
-                },
-            };
-
-            var header_it = req.iterateHeaders();
-            while (header_it.next()) |header| try event.req.headers.append(&header);
-
-            try self.handlerFn(&event);
-
-            try self.respondFromEvent(&event, &req);
-        }
-    }
-
-    fn respondFromEvent(self: *Listener, event: *Event, req: *http.Server.Request) !void {
-        const res_body = try event.res.body.toOwnedSlice();
-        const res_headers = try self.arena.allocator().alloc(http.Header, event.res.headers.count());
-        var i: usize = 0;
-        var header_it = event.res.headers.iterator();
-        while (header_it.next()) |header_ptr| : (i += 1) res_headers[i] = header_ptr.value_ptr.*.*;
-        try req.respond(res_body, .{
-            .status = event.res.status,
-            .extra_headers = res_headers,
-        });
-    }
-
-    pub const Event = struct {
-        req: Request,
-        res: Response,
-
-        pub const Request = struct {
-            uri: std.Uri,
-            // head: http.Server.Request.Head,
-            headers: std.ArrayList(*const http.Header),
-        };
-        pub const Response = struct {
-            status: http.Status,
-            headers: std.StringHashMap(*http.Header),
-            body: std.ArrayList(u8),
-        };
-    };
-};
-
-pub const BasicRouter = Router(Listener.Event);
-
-pub fn Router(comptime Context: type) type {
+pub fn Listener(comptime Context: type) type {
     return struct {
-        ctx: Context,
-        allocator: mem.Allocator,
+        address: net.Address,
+        arena: heap.ArenaAllocator,
+        ctx: *Context,
+        handlerFn: HandlerFn,
 
-        static_routes: std.StringHashMap(*Node),
+        pub const Options = struct {
+            address: net.Address,
+            allocator: mem.Allocator,
+            context: *Context,
+            handler: HandlerFn,
+        };
 
-        pub fn init(allocator: mem.Allocator, ctx: Context) Self {
+        pub fn init(options: Options) Listener {
             return .{
-                .ctx = ctx,
-                .allocator = allocator,
-                .static_routes = std.StringHashMap(*Node).init(allocator),
+                .address = options.address,
+                .arena = heap.ArenaAllocator.init(options.allocator),
+                .ctx = options.context,
+                .handlerFn = options.handler,
             };
         }
 
-        pub fn deinit(self: *Self) void {
-            self.static_routes.deinit();
+        pub fn deinit(self: *Listener) void {
+            self.arena.deinit();
         }
 
-        pub fn addRoute(self: *Self, path: []const u8, handler: HandlerFn) !void {
-            try self.addStaticRoute(path, handler);
+        pub fn listen(self: *Listener) !void {
+            var tcp = try self.address.listen(.{});
+            defer tcp.deinit();
+            std.debug.print("listening at: {any}\n", .{self.address});
+
+            while (true) {
+                const read_buf = try self.arena.allocator().alloc(u8, 1024 * 32);
+                const connection = try tcp.accept();
+                var server = http.Server.init(connection, read_buf);
+                try self.handle(&server);
+                _ = self.arena.reset(.retain_capacity);
+            }
         }
 
-        fn addStaticRoute(self: *Self, path: []const u8, handler: HandlerFn) !void {
-            try self.static_routes.put(path, .{
-                .type = .normal,
-                .max_depth = 1,
-                .children = std.StringHashMap(*Node).init(self.allocator),
-                .handler = handler,
-                .placeholder_children = std.ArrayList(*Node).init(self.allocator),
+        fn handle(self: *Listener, server: *http.Server) !void {
+            handler: while (true) {
+                var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
+
+                var event = Event{
+                    .req = .{
+                        .uri = try std.Uri.parseWithoutScheme(req.head.target),
+                        .headers = std.ArrayList(*const http.Header).init(self.arena.allocator()),
+                    },
+                    .res = .{
+                        .status = .ok,
+                        .headers = std.StringHashMap(*http.Header).init(self.arena.allocator()),
+                        .body = std.ArrayList(u8).init(self.arena.allocator()),
+                    },
+                };
+
+                var header_it = req.iterateHeaders();
+                while (header_it.next()) |header| try event.req.headers.append(&header);
+
+                try self.handlerFn(self.ctx, &event);
+
+                try self.respondFromEvent(&event, &req);
+            }
+        }
+
+        fn respondFromEvent(self: *Listener, event: *Event, req: *http.Server.Request) !void {
+            const res_body = try event.res.body.toOwnedSlice();
+            const res_headers = try self.arena.allocator().alloc(http.Header, event.res.headers.count());
+            var i: usize = 0;
+            var header_it = event.res.headers.iterator();
+            while (header_it.next()) |header_ptr| : (i += 1) res_headers[i] = header_ptr.value_ptr.*.*;
+            try req.respond(res_body, .{
+                .status = event.res.status,
+                .extra_headers = res_headers,
             });
         }
 
-        pub const Node = struct {
-            type: Type,
-            max_depth: usize, // TODO: what is best here
-            parent: ?*Node = null,
-            children: std.StringHashMap(*Node),
-            handler: HandlerFn,
-            wildcard_child_node: ?*Node = null,
-            placeholder_children: std.ArrayList(*Node),
+        pub const Event = struct {
+            req: Request,
+            res: Response,
 
-            pub const Type = enum { normal, wildcard, placeholder };
+            pub const Request = struct {
+                uri: std.Uri,
+                // head: http.Server.Request.Head,
+                headers: std.ArrayList(*const http.Header),
+            };
+            pub const Response = struct {
+                status: http.Status,
+                headers: std.StringHashMap(*http.Header),
+                body: std.ArrayList(u8),
+            };
         };
 
-        pub const HandlerFn = *const fn (ctx: *Context) anyerror!void;
-
-        const Self = @This();
+        pub const HandlerFn = *const fn (ctx: *Context, event: *Event) anyerror!void;
     };
 }
+
+pub const Router = struct {
+    allocator: mem.Allocator,
+
+    root_node: Node,
+    static_routes: std.StringHashMap(*Node),
+
+    error_handler: HandlerFn,
+
+    pub fn init(allocator: mem.Allocator, root_handler: HandlerFn, error_handler: HandlerFn) Router {
+        return .{
+            .allocator = allocator,
+            .root_node = .{
+                .type = .normal,
+                .max_depth = 64,
+                .children = std.StringHashMap(*Node).init(allocator),
+                .handler = root_handler,
+                .placeholder_children = std.ArrayList(*Node).init(allocator),
+            },
+            .static_routes = std.StringHashMap(*Node).init(allocator),
+            .error_handler = error_handler,
+        };
+    }
+
+    pub fn deinit(self: *Router) void {
+        self.static_routes.deinit();
+    }
+
+    pub fn handle(self: *Router, event: *Listener.Event) anyerror!void {
+        try self.route(event.req.uri.path)(self, event);
+    }
+
+    pub fn addRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
+        try self.addStaticRoute(path, handler);
+    }
+
+    fn addStaticRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
+        try self.static_routes.put(path, .{
+            .type = .normal,
+            .max_depth = 1,
+            .children = std.StringHashMap(*Node).init(self.allocator),
+            .handler = handler,
+            .placeholder_children = std.ArrayList(*Node).init(self.allocator),
+        });
+    }
+
+    pub fn route(self: *Router, path: []const u8) HandlerFn {
+        if (self.static_routes.get(path)) |rt| return rt.handler;
+        if (self.root_node.wildcard_child_node) |node| return node.handler;
+        return self.error_handler;
+    }
+
+    pub const Node = struct {
+        type: Type,
+        max_depth: usize, // TODO: what is best here
+        parent: ?*Node = null,
+        children: std.StringHashMap(*Node),
+        handler: HandlerFn,
+        wildcard_child_node: ?*Node = null,
+        placeholder_children: std.ArrayList(*Node),
+
+        pub const Type = enum { normal, wildcard, placeholder };
+    };
+
+    pub const HandlerFn = *const fn (self: *Router, event: *App.Event) anyerror!void;
+};