Routing
Routing is the process of defining how your application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, and so on).
In Minima.js, you can define routes using the routes export in a module file, or programmatically using the application instance. Each route has a handler function that is executed when the route is matched.
Quick Reference
export const routes- Define routes declaratively using theRoutestypeapp.get/post/put/delete()- Define routes programmaticallyapp.route()- Define routes with advanced optionsparams()- Get route parameterssearchParams()- Get query string parameters- Route metadata - Attach metadata to routes
Basic Routing
The recommended way to define routes is by exporting a routes object from a module file. The Routes type is a record that maps "<METHOD> /path" keys to handler functions.
import type { Routes } from "@minimajs/server";
function getHome() {
return "Hello, World!";
}
function createUser() {
return { message: "User created" };
}
export const routes: Routes = {
// GET /
"GET /": getHome,
// POST /users
"POST /users": createUser,
};The key format is "METHOD /path", where METHOD is an uppercase HTTP method and /path is the route path.
Programmatic Routing
You can also define routes programmatically using the app.get(), app.post(), app.put(), app.delete() methods on the application instance. This is useful for standalone app scripts or when you need dynamic route registration.
import { createApp } from "@minimajs/server";
const app = createApp();
// GET /
app.get("/", () => "Hello, World!");
// POST /users
app.post("/users", () => ({ message: "User created" }));You can also use the app.route() method to define routes with more advanced options:
app.route({
method: "GET",
url: "/",
handler: () => "Hello, World!",
});Available Methods
Minima.js supports all the standard HTTP methods.
Routes key format:
"GET /path"- GET requests"POST /path"- POST requests"PUT /path"- PUT requests"PATCH /path"- PATCH requests"DELETE /path"- DELETE requests"HEAD /path"- HEAD requests"OPTIONS /path"- OPTIONS requests"ALL /path"- matches all methods
Programmatic app methods:
app.get(path, handler)app.post(path, handler)app.put(path, handler)app.patch(path, handler)app.delete(path, handler)app.head(path, handler)app.options(path, handler)app.all(path, handler)(matches all methods)
Route Parameters
Route parameters are named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the params object, which can be accessed from the @minimajs/server package.
import type { Routes } from "@minimajs/server";
import { params } from "@minimajs/server";
function getUser() {
const { id } = params<{ id: string }>();
return { id };
}
export const routes: Routes = {
// GET /users/123
"GET /users/:id": getUser,
};You can define multiple parameters in a single route:
import type { Routes } from "@minimajs/server";
import { params } from "@minimajs/server";
function getPost() {
const { userId, postId } = params<{ userId: string; postId: string }>();
return { userId, postId };
}
export const routes: Routes = {
// GET /users/123/posts/456
"GET /users/:userId/posts/:postId": getPost,
};Optional Parameters
You can make a route parameter optional by adding a question mark (?) to the end of its name.
import type { Routes } from "@minimajs/server";
import { params } from "@minimajs/server";
function getUser() {
const { id } = params<{ id?: string }>();
return { id: id || "No ID provided" };
}
export const routes: Routes = {
// GET /users/123 or /users
"GET /users/:id?": getUser,
};Wildcards
Wildcards (*) can be used to match any character in a URL segment.
import type { Routes } from "@minimajs/server";
import { params } from "@minimajs/server";
function getWildcard() {
const wildcard = params.get("*");
return { wildcard };
}
export const routes: Routes = {
// Matches /posts/foo, /posts/bar, etc.
"GET /posts/*": getWildcard,
};Regular Expressions
You can also use regular expressions to define routes. This is useful for more advanced matching scenarios.
import type { Routes } from "@minimajs/server";
import { params } from "@minimajs/server";
function getFile() {
const { file } = params<{ file: string }>();
return { file }; // { file: '123' }
}
export const routes: Routes = {
// Matches /files/123.png
"GET /files/:file(^\\d+).png": getFile,
};Route Metadata
Minima.js allows you to attach custom metadata to your routes. This is a powerful feature for adding route-specific configuration, flags, or contextual information that can be accessed by handlers, hooks, or plugins. Metadata is passed as [key, value] tuples directly in the route definition, before the final handler function.
It's recommended to use Symbols as keys for your metadata to avoid potential name collisions.
Defining Metadata with the routes export:
When using the routes object, you can attach metadata using the handler() function. This function takes any number of descriptors (metadata tuples or functions) and a final handler callback.
import { handler, type Routes } from "@minimajs/server";
// Define custom symbols for metadata keys
const kAuthRequired = Symbol("AuthRequired");
const kPermissions = Symbol("Permissions");
function getAdmin() {
return { message: "Welcome, admin!" };
}
function createData() {
return { message: "Data created" };
}
// Wrap handlers with descriptors using the handler() helper
const adminHandler = handler([kAuthRequired, true], [kPermissions, ["admin", "moderator"]], getAdmin);
const dataHandler = handler([kAuthRequired, false], createData);
export const routes: Routes = {
"GET /admin": adminHandler,
"POST /api/data": dataHandler,
};Defining Metadata programmatically:
Programmatic methods like app.get() and app.post() support variadic descriptors passed directly before the handler callback.
import { createApp } from "@minimajs/server";
const app = createApp();
const kAuthRequired = Symbol("AuthRequired");
const kPermissions = Symbol("Permissions");
// Descriptors are passed between path and handler
app.get("/admin", [kAuthRequired, true], [kPermissions, ["admin", "moderator"]], () => {
return { message: "Welcome, admin!" };
});
app.post("/api/data", [kAuthRequired, false], () => {
return { message: "Data created" };
});Accessing Metadata in a Handler:
You can access the metadata for the current route using the context().route.metadata object within any handler or any function called within the handler's scope.
import { context } from "@minimajs/server";
// Assuming kAuthRequired and kPermissions are imported or defined
const kAuthRequired = Symbol("AuthRequired");
const kPermissions = Symbol("Permissions");
app.get("/admin-dashboard", [kAuthRequired, true], [kPermissions, ["admin"]], () => {
const routeMetadata = context().route.metadata;
const authRequired = routeMetadata.get(kAuthRequired); // true
const requiredPermissions = routeMetadata.get(kPermissions); // ["admin"]
console.log(`Auth Required: ${authRequired}`);
console.log(`Required Permissions: ${requiredPermissions}`);
return { authRequired, requiredPermissions };
});Accessing Metadata in a Hook:
Metadata is especially useful in hooks for implementing cross-cutting concerns dynamically. For instance, an authentication hook can check if a route requires authentication based on its metadata.
import { app } from "./your-app-instance"; // Your app instance
import { hook, context, abort } from "@minimajs/server";
const kAuthRequired = Symbol("AuthRequired"); // Must be the same Symbol instance
app.register(
hook("request", () => {
const routeMetadata = context().route.metadata;
const authRequired = routeMetadata[kAuthRequired];
if (authRequired) {
// Perform actual authentication check
const isAuthenticated = false; // Replace with your auth logic
if (!isAuthenticated) {
abort("Unauthorized", 401);
}
}
})
);
app.get(
"/protected",
[kAuthRequired, true], // This route will be checked by the hook
() => {
return { message: "You accessed a protected route!" };
}
);
app.get(
"/public",
[kAuthRequired, false], // This route will skip the auth check
() => {
return { message: "This route is public." };
}
);By leveraging route metadata, you can create highly configurable and modular applications, where route-specific behavior can be easily defined and managed without cluttering your main handler logic.
Structuring Routes with Modules
As your application grows, it's a good practice to organize your routes into modules. Each module file can export a routes object typed as Routes, and the framework will automatically discover and register them.
// modules/users/index.ts
import type { Routes } from "@minimajs/server";
import { params } from "@minimajs/server";
function listUsers() {
return "List users";
}
function getUser() {
const { id } = params<{ id: string }>();
return { id };
}
function createUser() {
return { message: "User created" };
}
export const routes: Routes = {
"GET /": listUsers,
"GET /:id": getUser,
"POST /": createUser,
};To learn more about how to structure your application with modules, please refer to the Modules guide.