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:
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
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:
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.