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()
addRichRoute() / handleGroup()
BEFORE all other route middlewares
How it works:
- At application startup, resolvers are registered via
XyGuard.define(name, resolver). - On each route (or group), required guards are declared using the
guardsproperty. - The framework compiles a guard middleware that runs before the route handler.
- 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.
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.
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,
);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.
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,
);
},
);Return Values and HTTP Behavior
| Returned Value | Guard Type | HTTP Status |
|---|---|---|
| true | All | None — access granted |
| false | authenticated | 401 Unauthorized |
| false | roles / permissions / custom | 403 Forbidden |
| "message" | All | 401 / 403 with that message as the error body |
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.
// 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:
| Middleware | Guards | |
|---|---|---|
| Declaration | Imperative (app.use) | Declarative (inline on route/group) |
| Visible in inspection | No | Yes |
| Standard failure protocol | No | Yes (true / false / string) |
| Execution timing | During request chain | Before handler initializes |
Optimize your routes with per-route rate limiting, response caching, and lifecycle hooks.
