Skip to content

Controllers & Dependency Injection

Controllers group related HTTP request handling logic into single ES6 classes. StruxJS manages Controllers via its built-in IoC Container, providing automatic dependency injection into constructors, action methods, and class properties.


Creating Controllers

Use the StruxJS CLI to generate Controllers.

Basic Controller

Generate a basic Controller scaffold in app/Controllers/:

bash
npx strux make:controller UserController

Generated file (app/Controllers/UserController.ts):

typescript
import { Request, Response } from "struxjs";

export class UserController {
    public async index(request: Request, response: Response) {
        return response.json({
            message: "Hello from UserController!"
        });
    }
}

Resource Controller (--resource)

Generate a Resource Controller containing standard CRUD action methods:

bash
npx strux make:controller UserController --resource

This generates index, create, store, show, edit, update, and destroy method stubs.


Binding Controllers to Routes

Bind Controller action methods to HTTP routes using Tuple Array or String Action syntax:

typescript
import { Route } from "struxjs";
import { UserController } from "../app/Controllers/UserController.js";

Route.get("/users", [UserController, "index"]).name("users.index");
Route.post("/users", [UserController, "store"]).name("users.store");
Route.get("/users/:id", [UserController, "show"]).name("users.show");

2. String Action Syntax

typescript
Route.get("/profile", "UserController@profile").name("profile");

Custom String Token Bindings

In addition to Class types, services and dependencies can be bound to custom String tokens inside app/Providers/AppServiceProvider.ts:

typescript
// app/Providers/AppServiceProvider.ts
import { Container } from "struxjs";
import { PostService } from "../Services/PostService.js";
import { AuthMiddleware } from "../Middleware/AuthMiddleware.js";

export class AppServiceProvider {
    public register(container: Container): void {
        // Register custom string token bindings
        container.singleton("post.service", (c) => c.make(PostService));
        container.bind("custom-auth", (c) => c.make(AuthMiddleware));
    }
}

Dependency Injection

StruxJS supports multiple dependency injection strategies.

1. Constructor Injection

A. Direct Type Hinting

Inject services directly by type-hinting them in the Controller constructor:

typescript
import { UserService } from "../Services/UserService.js";
import { PostService } from "../Services/PostService.js";

export class UserController {
    constructor(
        private userService: UserService,
        private postService: PostService
    ) {}

    public async index() {
        return await this.userService.getAll();
    }
}

B. String Token Injection via @Inject()

Inject custom string tokens using the @Inject("token_name") decorator:

typescript
import { Inject } from "struxjs";
import { PostService } from "../Services/PostService.js";

export class UserController {
    constructor(
        @Inject("post.service") private postService: PostService
    ) {}
}

2. Method Action Injection

StruxJS resolves action parameters automatically. You can inject Request, Response, FormRequest validation classes, application Services, or Route Parameters matching by parameter name:

A. Injecting Request, Response & Services

typescript
import { Request, Response } from "struxjs";
import { RegisterRequest } from "../Requests/RegisterRequest.js";
import { UserService } from "../Services/UserService.js";

// 1. Inline Validation via request.validate()
export class UserController {
    public async store(
        request: Request,
        response: Response,
        userService: UserService
    ) {
        const validatedData = await request.validate({
            email: "required|email",
            name: "required|min:2"
        });
        const user = await userService.create(validatedData);
        return response.json(user);
    }
}

// 2. FormRequest Class Validation via camelCase Parameter Name
export class UserController {
    public async store(
        registerRequest: RegisterRequest,
        response: Response,
        userService: UserService
    ) {
        const validatedData = registerRequest.validated(); // Or registerRequest.all()
        const user = await userService.create(validatedData);
        return response.json(user);
    }
}

FormRequest Parameter Naming Convention (camelCase Rule)

When injecting custom FormRequest classes (such as RegisterRequest or LoginRequest), name the action parameter in camelCase matching the Class name (e.g., registerRequest: RegisterRequest or loginRequest: LoginRequest).

Generic parameter names like request: Request or req: Request are reserved for resolving the standard HTTP Request object.

B. Direct Route Parameter Injection

StruxJS automatically matches route parameter names (:id, :postId, :commentId) to method parameter names:

typescript
// Route: Route.get("/users/:id", [UserController, "show"])
export class UserController {
    public async show(id: string) {
        return { userId: id };
    }
}
typescript
// Route: Route.get("/posts/:postId/comments/:commentId", [CommentController, "show"])
export class CommentController {
    public async show(postId: string, commentId: string) {
        return { postId, commentId };
    }
}

C. Combined Method Injection

Combine Request, Response, Services, and Route Parameters in any order:

typescript
// Route: Route.get("/users/:id", [UserController, "show"])
export class UserController {
    public async show(
        request: Request,
        response: Response,
        id: string,
        userService: UserService
    ) {
        const user = await userService.find(id);
        return response.json(user);
    }
}

3. Manual Resolution (make())

Resolve dependencies manually using the make() helper by passing a Class Constructor directly or a String Token:

typescript
import { make } from "struxjs";
import { UserService } from "../Services/UserService.js";
import { PostService } from "../Services/PostService.js";

export class UserController {
    public async index() {
        // 1. Passing Class Constructor directly
        const userService = make(UserService);

        // 2. Passing custom String Token
        const postService = make<PostService>("post.service");

        return await userService.getAll();
    }
}

Action Return Types

Controller action methods support various return types:

1. String

Returning a string sends a text/plain or text/html response:

typescript
public async index() {
    return "Hello World";
}

2. Array / Object (JSON)

Returning an Object or Array automatically serializes the data to JSON with an application/json header:

typescript
public async index() {
    return {
        success: true,
        data: [
            { id: 1, name: "Alice" },
            { id: 2, name: "Bob" }
        ]
    };
}

3. Server-Rendered View (response.view)

Render HTML templates (.strux) from resources/views/:

typescript
public async index(request: Request, response: Response) {
    const users = await this.userService.getAll();
    return response.view("users.index", { users });
}

4. HTTP Redirects (response.redirect)

Direct Redirect

typescript
public async store(request: Request, response: Response) {
    return response.redirect("/login");
}

Named Route Redirect

typescript
public async store(request: Request, response: Response) {
    return response.redirect().route("users.show", { id: 42 });
}

Back Redirect

typescript
public async store(request: Request, response: Response) {
    return response.redirect().back();
}

Redirect with Input & Flash Session

typescript
public async store(request: Request, response: Response) {
    return response.redirect()
        .route("users.create")
        .withInput()
        .with("error", "Email address already registered.");
}

Released under the MIT License.