Skip to content

@contextjs/webserver-middleware-controllers

Tests  npm  License

Controllers middleware for the ContextJS webserver.

Installation

bash
npm i @contextjs/webserver-middleware-controllers

Overview

This package provides attribute-based routing support for the ContextJS webserver. It introduces a controller and verb decorator model, allowing you to build organized, testable route handlers similar to established MVC patterns.

Features

  • Attribute-based route mapping using @Controller, @Get, @Post, @Put, and @Delete
  • Auto-discovery of compiled .js or .mjs controllers
  • Customizable default controller and action fallbacks
  • Integration via .useControllers() on WebServerOptions

Usage

Basic usage

typescript
import "@contextjs/di";
import "@contextjs/webserver";
import "@contextjs/webserver-middleware-controllers";

import { Application } from "@contextjs/system";
import { WebServerOptions } from "@contextjs/webserver";
import { Controller, Get } from "@contextjs/webserver-middleware-controllers";

interface ILoggerService {
    log(message: string): void;
}

class LoggerService implements ILoggerService {
    log(message: string): void {
        console.log(message);
    }
}

@Controller()
class HomeController {
    private readonly loggerService: ILoggerService;

    public constructor(loggerService: ILoggerService) {
        this.loggerService = loggerService;
    }

    @Get("index")
    public async index() {
        this.loggerService.log("HomeController index method called");
        return "Home";
    }
}

const application = new Application();

application.useDependencyInjection();

application.services.addTransient<ILoggerService, LoggerService>();

application.useWebServer((options: WebServerOptions) => {
    options.onEvent = (event) => console.log(event.type, event.detail);
    options.useControllers();
});

await application.runAsync();

Requests to /home/index will be routed to the index() method of HomeController.

Uploading and Saving a File from the Request Body

typescript
import { HttpContext } from "@contextjs/webserver";
import { Controller, IActionResult, Ok, Post } from "@contextjs/webserver-middleware-controllers";
import fs from "node:fs";
import path from "node:path";
import { pipeline } from "node:stream/promises";

@Controller()
export class HomeController {

    @Post("index")
    public async indexPost(context: HttpContext): Promise<IActionResult> {
        // Choose upload directory and file name
        const uploadDir = path.join(process.cwd(), "uploads");
        const fileName = "uploaded-file.png";
        const uploadPath = path.join(uploadDir, fileName);

        // Ensure upload directory exists
        await fs.promises.mkdir(uploadDir, { recursive: true });

        // Stream the request body directly to disk (no memory buffering)
        const writeStream = fs.createWriteStream(uploadPath);
        await pipeline(context.request.body, writeStream);

        return Ok("File uploaded successfully!");
    }
}

Configuration

You can optionally customize the default controller and action name:

typescript
application.useWebServer((options: WebServerOptions) => {
    options.onEvent = (event) => console.log(event.type, event.detail);
    options.useControllers(controllerOptions => {
        controllerOptions.defaultController = "about";
        controllerOptions.defaultAction = "index";
    });
});

Controller Auto-Discovery

Controllers are discovered automatically from the outDir specified in your tsconfig.json. You must compile your controllers to .js or .mjs files.

API Reference

typescript
/**
 * Defines the Controller decorator, which is used to mark a class as a controller.
 * The optional template parameter specifies the base route for the controller.
 * @param template - The base route for the controller.
 * @return A class decorator that marks the class as a controller.
 */
export declare function Controller(template?: string): ClassDecorator;

/**
 * Defines the Get decorator for methods in a controller.
 * This decorator marks a method to handle HTTP GET requests.
 * The template parameter specifies the route for the method.
 * @param template - The route for the method.
 * @return A method decorator that marks the method to handle GET requests.
 */
export declare function Get(template: string): MethodDecorator;

/**
 * Defines the Post decorator for methods in a controller.
 * This decorator marks a method to handle HTTP POST requests.
 * The template parameter specifies the route for the method.
 * @param template - The route for the method.
 * @return A method decorator that marks the method to handle POST requests.
 */
export declare function Post(template: string): MethodDecorator;

/**
 * Defines the Put decorator for methods in a controller.
 * This decorator marks a method to handle HTTP PUT requests.
 * The template parameter specifies the route for the method.
 * @param template - The route for the method.
 * @return A method decorator that marks the method to handle PUT requests.
 */
export declare function Put(template: string): MethodDecorator;

/**
 * Defines the Delete decorator for methods in a controller.
 * This decorator marks a method to handle HTTP DELETE requests.
 * The template parameter specifies the route for the method.
 * @param template - The route for the method.
 * @return A method decorator that marks the method to handle DELETE requests.
 */
export declare function Delete(template: string): MethodDecorator;

/**
 * Extends the WebServerOptions interface with a method to configure controllers.
 */
declare module "@contextjs/webserver" {
    export interface WebServerOptions {
        useControllers(configure?: (controllersOptions: ControllerOptions) => void): WebServerOptions;
    }
}

/**
 * Defines the ControllerOptions class, which is used to configure default controller and action names.
 */
export declare class ControllerOptions {
    /**
     * The default controller name.
     */
    public defaultController: string;

    /**
     * The default action name.
     */
    public defaultAction: string;
}

/**
 * Defines the VerbRouteInfo class, which extends RouteInfo to include an HTTP verb.
 */
export declare class VerbRouteInfo extends RouteInfo {

    /**
     * The HTTP verb for the route.
     */
    public readonly verb: HttpVerb;

    /**
     * Creates an instance of VerbRouteInfo.
     * @param verb - The HTTP verb for the route.
     * @param template - The route template.
     */
    constructor(verb: HttpVerb, template: string);
}

/**
 * Defines the ControllerDefinition class, which represents a controller with its name, class reference, and optional route.
 */
export declare class ControllerDefinition {

    /**
     * The name of the controller.
     */
    public readonly name: string;

    /**
     * The class reference for the controller.
     */
    public readonly classReference: Function;

    /**
     * An optional RouteInfo object representing the route for the controller.
     */
    public readonly route?: RouteInfo;

    /**
     * Creates an instance of ControllerDefinition.
     * @param name - The name of the controller.
     * @param classReference - The class reference for the controller.
     * @param route - An optional RouteInfo object representing the route for the controller.
     */
    constructor(name: string, classReference: Function, route?: RouteInfo);
}

/**
 * Represents an action result that can execute and write to the HTTP context.
 */
export declare interface IActionResult {
    /**
     * Executes the result, writing to the given HTTP context.
     * @param httpContext - The HTTP context.
     */
    executeAsync(httpContext: HttpContext): Promise<any>;
}

/**
 * Returns a result indicating a successful response with status 200 ("OK") and an optional string value as the response body.
 * @param value - The response body as a string.
 */
export declare function Ok(value?: string): IActionResult;

/**
 * Returns a result that serializes the given value as JSON and responds with status 200 ("OK").
 * @param value - The value to serialize as JSON.
 */
export declare function Json(value: any): IActionResult;

/**
 * Returns a result indicating a successful response with status 204 ("No Content").
 */
export declare function NoContent(): IActionResult;

/**
 * Returns a result indicating a successful response with no body and no modification to status code.
 */
export declare function Empty(): IActionResult;

/**
 * Returns a result indicating a response with status 400 ("Bad Request") and an optional message as the response body.
 * @param message - The optional error message.
 */
export declare function BadRequest(message?: string): IActionResult;

/**
 * Returns a result indicating a response with status 401 ("Unauthorized").
 */
export declare function Unauthorized(): IActionResult;

/**
 * Returns a result indicating a response with status 403 ("Forbidden").
 */
export declare function Forbidden(): IActionResult;

/**
 * Returns a result indicating a response with status 404 ("Not Found").
 */
export declare function NotFound(): IActionResult;

/**
 * Returns a result indicating a response with status 201 ("Created"), an optional value as the response body, and an optional "Location" header.
 * @param value - The response body as a string.
 * @param location - The optional Location header value.
 */
export declare function Created(value?: string, location?: string): IActionResult;

/**
 * Returns a result with custom content, content-type, status code, and reason phrase.
 * @param content - The content to write to the response.
 * @param contentType - The content-type header value.
 * @param statusCode - The HTTP status code.
 * @param statusMessage - The HTTP reason phrase.
 */
export declare function Content(content: string, contentType?: string, statusCode?: number, statusMessage?: string): IActionResult;