Skip to main content

Router

Yggdrasil Server includes a simple router. With the router, you can register different handlers for each URL path and method combination you support. Then, Yggdrasil Server will call the appropriate handler when requests come in.

Implementation

Setup routes

To define routes, simply use YggdrasilServer.defineRoute to create a route object. Then, pass all of your route objects into YggdrasilRouter's constructor to register them.

Please note that route handlers are passed a YggdrasilRouterContext, which extends YggdrasilContext with additional context about your routing.

The following sample shows how to define and register routes using the recommended convention that keeps each route defined in its own file:

src/routes/hello.ts
import { DemoServer } from "../server";

// The first parameter is the HTTP method, or * to match all methods.
// The second parameter is the route to match (see the Routing section below)
export const hello = DemoServer.defineRoute("GET", "/hello", async (ygg) => {
return new Response("Hello world!", { status: 200 });
});

Re-export all the routes from a single file

src/routes/index.ts
export * from "./hello";
// repeat the above for each route file in your project

Handle requests

Once you have your router setup, simply call YggdrasilRouter.handleRequest to use it:

src/index.ts
import * as routes from "./routes";

// This should ideally be defined outside your request handler
const DemoRouter = new YggdrasilRouter(DemoServer, routes);

// Additional required setup omitted for brevity

// Inside DemoServer.handleRequest:
return DemoRouter.handleRequest(ygg);

Routing

When you define a route, you need to specify a method and path. Requests that match the defined method and path will be handled by the route.

HTTP method

The method can be any HTTP method, or * to match all methods.

Path

Normalization

Incoming requests match paths based on their normalized, relative paths. Paths must be exactly equal to match.

For example, if you have no normalization configured, a route path /abc will match requests made to /abc but not /ABC, /abc/, or /ABC/. If you have lowercase normalization and trailing slash removal enabled, /abc will match requests made to /abc, /ABC, /abc/, and /ABC/.

However, please keep in mind that your route configuration is assumed to already be normalized. For example, if you have lowercase normalization enabled, and define a route with path /ABC, it will never match, because all requests will have their paths converted to lowercase before matching.

Since routes use the relative path, a route path /abc will match requests made to /demo/abc if the base url is https://yggdrasil.jottocraft.com/demo, for example.

Variable segments

It is possible to make segments variable so they can match any slug. To do this, simply prefix the segment with a dollar sign ($).

For example, to match any arbitrary ID in the path /posts/<any arbitrary ID>, define the route path as /posts/$postID. Then, when handling the request, you can retrieve the post ID as ygg.params.postID.

Variable segment names MUST be consistent between routes, or the behavior of which name will be used is undefined. For example, if you have routes /posts/$postID and /posts/$PostID/likes, the name of the entry in the ygg.params object is undefined.

Multiple matches

YggdrasilRouter does not check every possible way to match a request. This creates the potential for requests to unexpectedly fail to match.

Same method and path

If you define multiple routes with the exact same method and path, the behavior for which will be called is undefined.

Different method, same path

If you define multiple routes with different methods, but the same path, YggdrasilRouter will call the route that matches the specific method used in the request. If no route is defined for the specific method, it will call the route that can match any method (*) if it exists.

Different paths

YggdrasilRouter only checks each path segment once. If it sees that a segment can be matched with either a variable segment or a literal match, it will prefer the literal match. This can lead to some potentially unexpected behavior, however. It's best to avoid allowing a route to be matched with different paths for this reason.

For example, assume you have routes /posts/$postId and /posts/$postId/likes. Then, you create a new route to render a special post, e.g. /posts/specialPost. A request to /posts/specialPost/likes will fail to match because Yggdrasil Router sees and matches posts and specialPost, but doesn't see any likes segment under specialPost. It will not consider the possibility that $postId could be used to match specialPost to locate the intended route.

YggdrasilRouter matches the path before the method, so if you have routes GET /posts/$postId, POST /posts/$postId, and GET /posts/specialPost, a request POST /posts/specialPost will fail because it took the literal path instead of the variable path, and there isn't a POST handler on the literal path.

Errors

YggdrasilRouter can throw YggdrasilStatus objects when a route can't be matched, an invalid method is used, etc. Please see the "Error handling" page for documentation of these internal errors.