Routing

Security Guards (XyGuard)

XyGuard is the global declarative guard registry of the XyPriss framework. It lets you secure routes and route groups in a centralized way, without imposing any specific underlying implementation (JWT, sessions, RBAC, etc.).

Architecture and Core Concepts

XyGuard relies on a static registry and a compiled middleware that intercepts requests before the main handler runs.

XyGuard (static registry)

resolvers: Map<string, GuardResolver>

createGuardMiddleware()

helpers.ts
guards is an array
executes each inline function
guards is an object
calls XyGuard.get(key) for each active key

addRichRoute() / handleGroup()

registry.ts / groups.ts
injects the guard middleware
BEFORE all other route middlewares

How it works:

  1. At application startup, resolvers are registered via XyGuard.define(name, resolver).
  2. On each route (or group), required guards are declared using the guards property.
  3. The framework compiles a guard middleware that runs before the route handler.
  4. If any guard fails, the request is immediately rejected with the appropriate HTTP status code (401 or 403). The main handler is never called.

XyGuard.define() — Registering a Resolver

The XyGuard.define() method registers guard logic. Guards should be defined at the server entry point, before any routes are mounted.

typescript
import { XyGuard } from "xypriss";

// Authentication guard
XyGuard.define("authenticated", (req) => {
    const token = req.headers["authorization"]?.split(" ")[1];
    if (!token) return "Missing token";
    try {
        (req as any).user = verifyToken(token);
        return true;
    } catch {
        return "Invalid or expired token";
    }
});

// Role-based guard
XyGuard.define("roles", (req, required) => {
    const user = (req as any).user;
    if (!user) return false;
    return required.includes(user.role)
        ? true
        : `Role '${user.role}' is insufficient. Required: ${required.join(", ")}`;
});

// Permission-based guard
XyGuard.define("permissions", (req, required) => {
    const user = (req as any).user;
    if (!user) return false;
    const missing = required.filter((p) => !user.permissions?.includes(p));
    return missing.length === 0 ? true : `Missing permissions: ${missing.join(", ")}`;
});

// Custom guard — IP whitelist
XyGuard.define("ipWhitelist", (req) => {
    const allowedIps = ["127.0.0.1", "192.168.1.100"];
    return allowedIps.includes(req.ip ?? "") ? true : `IP ${req.ip} is not allowed`;
});

Usage on a Route

The guards property is passed in the route options object. The declarative object syntax is the recommended approach.

typescript
import { router } from "./router";

// Authentication only
router.get("/profile", { guards: { authenticated: true } }, async (req, res) => {
    res.json({ user: (req as any).user });
});

// Roles
router.post(
    "/admin/users",
    { guards: { authenticated: true, roles: ["admin", "super-admin"] } },
    async (req, res) => {
        res.status(201).json({ message: "User created" });
    },
);

// Permissions
router.delete(
    "/posts/:id",
    { guards: { authenticated: true, permissions: ["posts:delete"] } },
    async (req, res) => {
        res.json({ deleted: true });
    },
);

// Combining multiple guards
router.put(
    "/admin/settings",
    {
        guards: {
            authenticated: true,
            roles: ["admin"],
            permissions: ["settings:write"],
            ipWhitelist: true,
        },
    },
    handler,
);
Evaluation Order
Declarative guards (object form) are evaluated in the iteration order of Object.entries(). The first guard that fails immediately short-circuits the chain and sends the error response. Subsequent guards are not evaluated.

Usage on a Group

Guards can be applied to an entire route group via router.group(). They are injected before every route in the group. Individual routes within the group can define additional guards on top.

typescript
router.group(
    {
        prefix: "/admin",
        guards: {
            authenticated: true,
            roles: ["admin"],
        },
    },
    (adminRouter) => {
        adminRouter.get("/dashboard", dashboardHandler);

        // This route additionally requires the "reports:export" permission
        adminRouter.get(
            "/reports/export",
            { guards: { permissions: ["reports:export"] } },
            exportHandler,
        );
    },
);
Enforcement
There is no way to bypass a group or router guard from within a child route. Guards ensure that your security policy is strictly enforced top-down.

Return Values and HTTP Behavior

Returned ValueGuard TypeHTTP Status
trueAllNone — access granted
falseauthenticated401 Unauthorized
falseroles / permissions / custom403 Forbidden
"message"All401 / 403 with that message as the error body
Design Recommendation
For precise, user-facing error messages, prefer returning a descriptive string over false. The string is forwarded directly as the error field in the JSON response body.

TypeScript Auto-completion via Declaration Merging

XyPriss exposes the CustomGuards interface that you can augment in your project to get auto-completion and type validation for your custom guards.

typescript
// src/types/xypriss.d.ts  (or any .d.ts file included in tsconfig)
declare module "xypriss" {
    interface CustomGuards {
        ipWhitelist?: boolean;
        plan?: "free" | "starter" | "premium" | "enterprise";
        apiKey?: boolean;
    }
}

// TypeScript will now validate your guard keys and values at usage sites:
router.get(
    "/premium-feature",
    {
        guards: {
            authenticated: true,
            plan: "premium", // auto-completed and type-safe
        },
    },
    handler,
);

Guards vs Middleware

While both can intercept requests, guards and middleware serve different purposes:

MiddlewareGuards
DeclarationImperative (app.use)Declarative (inline on route/group)
Visible in inspectionNoYes
Standard failure protocolNoYes (true / false / string)
Execution timingDuring request chainBefore handler initializes
When to use which?
Prefer guards for authentication and authorization checks. Reserve middleware for cross-cutting concerns like logging, body parsing, or CORS.
Advanced Features

Optimize your routes with per-route rate limiting, response caching, and lifecycle hooks.