Skip to content
Harry Rabin edited this page Aug 26, 2022 · 22 revisions

Welcome to the PatchBay wiki!

Documentation will be provided here until I get a dedicated docs site up and running.

Roadmap

Short-term/in progress

  • Middleware/plugins
    • This feature is coming soon, it will be called "modifiers". A Modifier is run for every Request or Response to/from a router (including the main router).
    • It will have a similar API to Patch, but returning the same type it takes in – instead of (Request) => Response, it would be (Request) => Request for an entry modifier or (Response) => Response for an exit modifier. However, you will be able to throw a Response from an entry modifier.
  • Session store
    • This will be a simple API to manage sessions. Sessions will be stored with Redis.

Long-term

  • Integration with an ORM
  • Faster route searching
    • PatchBay uses JavaScript RegExp to find routes, which can be slow compared to other regex engines – at least in V8/Node. If there’s a need for better regex performance, I’d like to try using Bun:ffi to “drop in” a potentially faster regex engine like Rust's.

Getting Started

I recommend reading this whole section before reading the in-depth docs.

There are three major concepts you'll need to know to begin:

  1. Patch is an abstract class that you will extend to build your logical components.

  2. Router is an abstract class that is essentially a "folder" of Patchables.

  3. Patchable is an interface defining any route that can be put into a router. A Patchable just stores its own route and a single function that takes a PBRequest and returns a Response. Patch and Router both implement Patchable... meaning Routers can have recursive Routers inside them (and they commonly will).

The starting point for your app is the bay.ts file. This module has a default export which we'll call the main bay. In a new project, your main bay looks like this:

const mainBay: MainBay = {
    baseURL: "http://localhost:3000",
    port: 3000,
    patches: [
        new StaticAssetRouter("/", "./dist"),
        new UserPage("/{username}{queryString}")
    ]
}

export default mainBay;

Let's break that down! First we've got the baseURL property, a string which lets the main router know where it lives. Then we have the port property, a number which tells Bun what port to serve on.

Then, things get interesting with patches. This is an array of any objects implementing Patchable, which the main router will use to route top-level requests. Our only patchable in there right now is a StaticAssetRouter, a utility class that serves the assets inside a given directory (in this case "./dist"). Notice how we construct a new StaticAssetRouter anonymously, and it just lives in the array. This is how nearly all patchable components will be created. The first parameter we pass to the StaticAssetRouter constructor is its route, "/". This means what you'd expect it to mean – the router will serve these assets at the top level (e.g. http://localhost:3000/index.html).

The next Patchable is UserPage, the class extending Patch that you can see above the main bay. Notice how the route contains curly brackets. These are capture groups ("wildcards") that allow you to use any content inside them inside a Patch. Why would you want to do this instead of using a router? Well, a router is essentially a "dumb" component; it just passes requests onward based on the route. But sometimes the URL contains information you want to use, such as a username. A router can't do anything with that information...

Instead, that data needs to land in a Patch that can do something useful like request the user's data from a database and display it in a template. These route parameters can be accessed in a Patch via the instance property routeParameters, but only after being parsed with this.parseRouteParams(req) – this is to remove overhead where you don't need it (a running theme you'll notice when programming with PatchBay).

There is also a special route parameter you can specify at the end of a route, {queryString}. If you include this, parseRouteParams will also initialize the instance property queryStringParameters to access the query string (if it exists) as key-value pairs just like the route parameters.

You can look at the UserPage class to see these properties in action.

An important question remains: if both these patches operate on the top-level, how can the main router know whether index.html is a file or a username? This is where an important aspect of PatchBay comes in: fall-through behavior. Routers search for routes in a linear fashion, picking the first one that matches the URL. If nothing matches, by default it will hand back control to its parent router to continue searching.

Say that the URL being requested is http://localhost:3000/johnsmith. Although both StaticAssetRouter and UserPage have top- level routes (since route parameters don't count as concrete values in the route), the StaticAssetRouter comes first in the main router's Patchable array, so it gets to check this request first. When it looks through all of its Patchables and doesn't see one matching "/johnsmith", it knows this request is not for itself, and hands control back to the main router. The main router continues on to the UserPage Patch – which has a route starting with a wildcard – so it will hand this request off to the Patch, which will be its last stop. The UserPage gladly accepts this request, processes it, and returns a template response for the user's homepage.

launch.ts

The launch.ts file is the entrypoint for a PatchBay app, as well as its configuration file. Its only required functionality is to instantiate the PBApp and tell it to start serving. That's all it needs to do, and out of the box that's all it will do. We've kept it simple on purpose, so you have a mostly-blank slate to easily do things like declare a global database instance, set options for the Bun server, and so on.