Skip to content

Upgrade Guide

This guide provides step-by-step instructions for upgrading existing StruxJS projects to newer framework versions, covering dependency management, version-specific changes, and post-upgrade verification.


General Upgrade Workflow

Upgrading a StruxJS application consists of updating your core dependencies, clearing compiled artifacts, recompiling TypeScript, and running tests.

1. Check Current Version

Inspect the version of struxjs-core currently installed in your project:

bash
npm list struxjs
# or
npm list struxjs-core

2. Update Core Framework

StruxJS projects created via create-strux-app alias struxjs-core to struxjs in package.json:

json
"dependencies": {
  "struxjs": "npm:struxjs-core@^1.0.12"
}

To update to the latest release, run the appropriate command for your package manager:

bash
# If using the default alias 'struxjs':
npm install struxjs@npm:struxjs-core@latest

# Or if you installed 'struxjs-core' directly:
npm install struxjs-core@latest
bash
# If using the default alias 'struxjs':
pnpm add struxjs@npm:struxjs-core@latest

# Or if you installed 'struxjs-core' directly:
pnpm add struxjs-core@latest
bash
# If using the default alias 'struxjs':
yarn add struxjs@npm:struxjs-core@latest

# Or if you installed 'struxjs-core' directly:
yarn add struxjs-core@latest

Alternatively, you can manually update the version number in your package.json and run npm install:

json
{
  "dependencies": {
    "struxjs": "npm:struxjs-core@^1.0.12"
  }
}

3. Clear Cache and Rebuild

After updating packages, clear any previous compiled artifacts in dist/ and recompile TypeScript:

bash
# Remove build artifacts
rm -rf dist

# Recompile TypeScript
npm run build

4. Verify & Test

Run your test suite and verify the application boots properly:

bash
# Run tests (if configured)
npm test

# Start in development mode
npm run dev

Upgrading to v1.0.12 (from v1.0.11)

Highlights

  • Zero Breaking Changes: Fully backward compatible with 1.0.11.
  • IoC Container Resolution for Default, Optional & Rest Parameters: Resolved an issue where registering or resolving classes with default arguments (such as Route.middleware(ApiAuthMiddleware) or Route.middleware(AuthMiddleware)) threw [StruxJS IoC Error]: Auto-injection failed for parameter 'defaultGuard="api"'. The IoC container now properly detects parameters with default values (= defaultValue), optional modifiers (?), and rest parameters (...args), applying default parameter values automatically when no explicit container binding exists.
  • Auth.guard(name) Facade & AuthGuard Interface: Auth.guard('web') is now officially exposed with TypeScript type definitions, returning the corresponding AuthGuard driver for multi-guard scoping.
  • Dedicated Web & API Separation for Middlewares: Simplified AuthMiddleware to focus purely on web session authentication and redirect handling, while ApiAuthMiddleware handles stateless JWT verification and 401 Unauthorized responses.

Upgrading to v1.0.11 (from v1.0.10)

Highlights

  • Zero Breaking Changes: Fully backward compatible with 1.0.10.
  • Middleware Constructor Arguments & Fluent Helpers: All security middlewares (CanMiddleware, RoleMiddleware, PermissionMiddleware, AuthMiddleware, ApiAuthMiddleware) can now be passed as instantiated objects (new CanMiddleware('edit-post')) or via convenient fluent helpers / static factories (can('edit-post'), role('admin'), permission('publish'), ApiAuthMiddleware.guard('admin')) without requiring container binding.
  • ORM Model Primary Key id Accessor Fix: Resolved an issue where model instances queried from the database had id evaluate to undefined due to modern ES2022 class field initialization. BaseModel now uses an ambient declare id: any declaration and Proxy-prioritized resolution for id, _id, and table primary keys.

If your existing application has app/Middleware/ApiAuthMiddleware.ts, update it to support constructor guard arguments and attach the resolved user directly to the request context:

typescript
// app/Middleware/ApiAuthMiddleware.ts
import { Middleware, Request, Response, Auth } from "struxjs";

export class ApiAuthMiddleware implements Middleware {
    constructor(private defaultGuard: string = "api") {}

    public static guard(guardName: string): ApiAuthMiddleware {
        return new ApiAuthMiddleware(guardName);
    }

    public async handle(request: Request, response: Response, guard?: string): Promise<void> {
        const targetGuard = guard || this.defaultGuard;

        if (!(await Auth.jwt().check(targetGuard))) {
            response.status(401).send({ error: "Unauthorized" });
            return;
        }

        const user = await Auth.jwt().user(targetGuard);
        if (!user) {
            response.status(401).send({ error: "Unauthorized" });
            return;
        }

        request.setUser(user);
    }

    public toString(): string {
        return this.defaultGuard !== "api" ? `apiauth:${this.defaultGuard}` : "ApiAuthMiddleware";
    }
}

If your application has app/Middleware/AuthMiddleware.ts, update it to support custom redirect paths and guard parameters:

typescript
// app/Middleware/AuthMiddleware.ts
import { Middleware, Request, Response, Auth } from "struxjs";

export class AuthMiddleware implements Middleware {
    constructor(
        private redirectTo: string = "/login",
        private guard: string = "web"
    ) {}

    public static redirectTo(url: string, guard: string = "web"): AuthMiddleware {
        return new AuthMiddleware(url, guard);
    }

    public async handle(
        request: Request,
        response: Response,
        redirectParam?: string,
        guardParam?: string
    ): Promise<void> {
        const targetRedirect = redirectParam || this.redirectTo;
        const targetGuard = guardParam || this.guard;

        if (await Auth.guard(targetGuard).guest()) {
            response.redirect(targetRedirect);
        }
    }

    public toString(): string {
        return this.redirectTo !== "/login" ? `auth:${this.redirectTo}` : "AuthMiddleware";
    }
}

Upgrading to v1.0.10 (from v1.0.9)

Dual-Guard Authorization (JWT & Session Support)

Prior to 1.0.10, authorization checks (Gate.allows(), Gate.authorize(), CanMiddleware, RoleMiddleware) strictly inspected the Web Session cookie (_auth_id), which meant JWT-authenticated requests (Authorization: Bearer <token>) were evaluated with user = null and resulted in unexpected 403 Forbidden responses unless Gate.forUser(user) was called manually.

In 1.0.10:

  • Unified Dual-Guard Resolution: Gate.allows(), Gate.denies(), and Gate.authorize() automatically resolve the authenticated user from either Session cookies or JWT Bearer tokens.
  • Route Middlewares on API Routes: RoleMiddleware (role:admin,editor) and CanMiddleware (can:ability) now seamlessly inspect JWT Bearer tokens on API routes.
  • New PermissionMiddleware: Direct permission route middleware (permission:publish-post).
  • Flexible HasRoles: Safely extracts roles and permissions from user.role (string), user.roles (array or JSON-encoded database string), and model attributes.

Upgrading to v1.0.9 (from v1.0.8)

High Impact Highlights

  • Zero Breaking Changes: Fully backward compatible with 1.0.8.
  • IoC Container Resolution Caching: Constructor dependency plans are now cached using a WeakMap. Repetitive reflection and regex parameter extraction during class resolution are eliminated.
  • Router Action Parameter Caching: Controller action parameter resolvers are pre-compiled and cached on first invocation, significantly reducing routing overhead on subsequent HTTP requests.

Optional: Adding ESLint to Existing Projects

Starting in 1.0.9, the official StruxJS project template includes ESLint (Flat Config) configured for TypeScript. If your existing project does not yet have ESLint configured, you can add it as follows:

1. Install ESLint dependencies

bash
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

2. Create eslint.config.js in your project root

javascript
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";

export default [
  {
    ignores: [
      "dist/**",
      "node_modules/**",
      "public/**",
      "resources/**",
      "storage/**",
    ],
  },
  {
    files: ["app/**/*.ts", "config/**/*.ts", "routes/**/*.ts", "database/**/*.ts", "bootstrap*.ts", "*.ts"],
    languageOptions: {
      parser: tsParser,
      ecmaVersion: "latest",
      sourceType: "module",
    },
    plugins: {
      "@typescript-eslint": tsPlugin,
    },
    rules: {
      ...tsPlugin.configs.recommended.rules,
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "@typescript-eslint/no-unused-vars": ["warn", { "args": "none", "varsIgnorePattern": "^_" }],
      "no-console": "off",
      "no-empty": ["error", { "allowEmptyCatch": true }],
    },
  },
];

3. Add lint scripts to package.json

json
{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

Upgrading to v1.0.8 (from v1.0.7)

Authentication & Token Security Updates

1. Strict Token Type Separation

In 1.0.8, access tokens are signed with type: "access", and refresh tokens are signed with type: "refresh".

  • The ApiAuthMiddleware and JwtGuard.resolveRequestPayload() automatically reject requests if a client attempts to pass a Refresh Token to an endpoint expecting an Access Token.
  • If your frontend previously reused refresh tokens in Authorization: Bearer <token> headers on regular API routes, ensure your frontend client correctly routes the access token for standard API requests and reserves the refresh token exclusively for token refresh endpoints (/auth/refresh).

2. Shared JTI and Token Rotation

When calling JwtGuard.issueTokenPair(user):

  • Both access and refresh tokens share the same jti identifier.
  • When refreshToken(oldRefreshToken, { rotation: true }) is invoked, the previous jti is blacklisted. This immediately revokes the associated access token as well, mitigating replay attack risks.

3. Redis TTL String Parsing

TTL configuration values in .env or configurations can now safely use string notation:

  • Values such as JWT_REFRESH_TTL=30d, JWT_EXPIRES_IN=1h, "15m", or "60s" are automatically parsed to positive integers by parseTtlToSeconds(), preventing Redis errors like ERR value is not an integer or out of range.

4. Global Redis Prefix

The JWT blacklist and refresh token stores now respect REDIS_PREFIX defined in .env (e.g. REDIS_PREFIX=strux_), keeping Redis keys organized and collision-free across multiple environments.


Troubleshooting & FAQs

Package resolution issues

If your package manager fails to resolve the aliased package:

bash
npm cache clean --force
rm -rf node_modules package-lock.json
npm install

TypeScript build errors after upgrade

Ensure your dist/ directory is deleted before compiling, as outdated .d.ts or .js files might conflict:

bash
rm -rf dist
npm run build

Verifying Environment Variables

Whenever upgrading between minor or major versions, check if new configuration options are available in .env.example of the latest StruxJS template (e.g., Redis prefixes or JWT TTL formats).

Released under the MIT License.