@ckn-technology/server · Web Framework

@ckn-technology/server
Web Framework Documentation

CKN Web Server Framework for building modular backend services, page-based web apps, and real-time features (WebSocket) on top of the CKN Framework stack. This guide covers project structure, middleware pipeline, modules, controllers, sessions, WebSocket handling, and frontend integration.

1. High-level Structure

Typical project layout based on the sample project ckn.ws.

ckn.ws/
├─ apps/              # Frontend components (CKN UI, browser-side)
│  ├─ app.js
│  └─ Default.js
├─ client/            # Auto-generated frontend bundle (Webpack output)
│  └─ app.js
├─ dist/              # Compiled TypeScript → JavaScript (server runtime)
├─ node_modules/
├─ public/            # Static assets to be served directly
├─ src/               # Server-side TypeScript source (framework usage)
│  ├─ modules/
│  │  ├─ main/
│  │  │  ├─ controllers/
│  │  │  │  └─ DefaultController.ts
│  │  │  ├─ models/
│  │  │  │  └─ TestModel.ts
│  │  │  ├─ public/
│  │  │  └─ views/
│  │  │     ├─ main.default.ejs
│  │  │     └─ module.ts
│  │  └─ otherModule/
│  ├─ index.ts
│  └─ start.ts
├─ themes/
├─ .gitignore
├─ .npmignore
├─ .npmrc
├─ Dockerfile
├─ package-lock.json
├─ package.json
└─ tsconfig.json

1.1 Root level

  • apps/ – Source for frontend components (written with @ckn-technology/ui).
  • client/ – Auto-generated frontend bundle from apps/ (via Frontend middleware).
  • public/ – Static files served directly (images, CSS, JS, downloads).
  • src/ – TypeScript server code (controllers, modules, bootstrap).
  • themes/ – Theme / layout / branding resources.
  • dist/ – Build output (Node.js runtime code).
  • Dockerfile – Container build for deployment.

1.2 Module structure under src/modules/main

src/modules/main/
├─ controllers/
│  └─ DefaultController.ts
├─ models/
│  └─ TestModel.ts
├─ public/           # Static files specific to this module (optional)
└─ views/
   ├─ main.default.ejs
   └─ module.ts

A typical module contains:

  • controllers/ – Route handlers (HTTP & WebSocket).
  • models/ – Business / data models.
  • views/ – View templates & module configuration.
  • public/ – Module-specific static assets.

Each module can be registered automatically by ModuleMiddleware so you don’t have to wire each controller manually.

2. Server Bootstrap (src/start.ts)

Simplified example of server startup code:

import "@ckn-technology/core";
import { Middleware, Server } from "@ckn-technology/server";
import { StaticContentMiddleware } from "@ckn-technology/server";
import { FrontendMiddleware } from "@ckn-technology/server";
import { PageViewEngineMiddleware } from "@ckn-technology/server";
import { ModuleMiddleware } from "@ckn-technology/server";
import { EjsViewEngineMiddleware } from "@ckn-technology/server";
import { CknHtmlViewEngineMiddleware } from "@ckn-technology/server";
import { ThemeMiddleware } from "@ckn-technology/server";
import { EnvironmentVariableMiddleware } from "@ckn-technology/server";

class CKNServer {
    server: Server;
    constructor() {
        this.server = new Server();
    }

    use = (middleware: Middleware) => {
        this.server.use(middleware);
    }

    async start() {
        this.server.use(
            new StaticContentMiddleware().addPublicFolder("/client", "client")
        );
        this.server.use(new FrontendMiddleware());
        this.server.use(new PageViewEngineMiddleware());
        this.server.use(new ModuleMiddleware());
        this.server.use(new EjsViewEngineMiddleware());
        this.server.use(new CknHtmlViewEngineMiddleware());
        this.server.use(new ThemeMiddleware());
        this.server.use(new EnvironmentVariableMiddleware());

        this.server.onBeforeStartingServer(() => {
            this.server.port =
                process.env.SERVICE_PORT != null
                    ? Number(process.env.SERVICE_PORT)
                    : this.server.port;
        });

        this.server.start();
    }
}

const server = new CKNServer();
server.start();

2.1 Middleware pipeline (order matters)

  1. StaticContentMiddleware
    Serves static assets.
    addPublicFolder("/client", "client") means:
    Path /client/* → files from local ./client/ folder.
  2. FrontendMiddleware
    Builds frontend components from apps/ (e.g. via Webpack) and outputs to client/.
  3. PageViewEngineMiddleware
    Handles page routing conventions and passes rendering to view engines.
  4. ModuleMiddleware
    Discovers and loads modules (e.g. src/modules/main), including controllers & views.
  5. EjsViewEngineMiddleware
    Enables EJS templates such as views/main.default.ejs.
  6. CknHtmlViewEngineMiddleware
    CKN custom HTML engine (enhanced templating & directives).
  7. ThemeMiddleware
    Handles themes/layout and controlled loading via session.loadTheme.
  8. EnvironmentVariableMiddleware
    Reads environment variables and injects them into runtime / session.

2.2 Server lifecycle hook

server.onBeforeStartingServer(callback)

Hook to customize server before it starts listening (e.g., configure server.port from SERVICE_PORT).

3. Controllers

Controllers live under src/modules/<moduleName>/controllers and extend the base Controller class from @ckn-technology/server. Each property in a controller is a route definition and may handle both HTTP and WebSocket for the same URL.

3.1 Sample: DefaultController.ts

import { ckn } from "@ckn-technology/core";
import { Controller, Session } from "@ckn-technology/server";
import { WebSocketSession } from "@ckn-technology/server/src/core/WebSocketSession";

class DefaultController extends Controller {
    constructor() {
        super();
        this.url = ""; // base url for this controller
    }

    // GET /
    default = {
        url: "",
        process: async (session: Session) => {
            let theme = session.loadTheme?.("default", "default");
            theme.header = "";
            theme.views.push("main.default.ejs");
            await theme.renderView(session);
        },
    };

    // HTTP + WebSocket route /data
    data = {
        url: "data",

        // WebSocket: connection opened on /data
        connected: (session: WebSocketSession) => {
            console.log("connected");
        },

        // WebSocket: data received on /data
        received: (session: WebSocketSession, data: any) => {
            console.log(data);
            for (let client of session.service?.clients ?? []) {
                console.log("sending...", data);
                client.send(data);
            }
        },

        // WebSocket: connection closed on /data
        closed: (session: WebSocketSession) => {
            console.log("disconnected");
        },

        // HTTP GET /data?message=...
        process: async (session: Session) => {
            let message = session.request.query.message;
            for (let client of session.clients) {
                console.log("sending...", message);
                client.send(message);
            }
            session.send({});
        },
    };
}

export { DefaultController };

3.2 Controller structure

Each property (e.g. default, data) defines a route:

  • url – path relative to controller base URL.
    Example: this.url = "" and data.url = "data" → route path /data.
  • process(session: Session) – HTTP handler (GET/POST/etc.). Uses session to access request/response.

WebSocket-specific optional handlers on the same object:

  • connected(session: WebSocketSession)
  • received(session: WebSocketSession, data: any)
  • closed(session: WebSocketSession)

This enables handling HTTP and WebSocket on the same URL.

3.3 Session basics

For HTTP (Session):

  • session.request – incoming request (params/query/body/headers).
  • session.request.query.message – read query string.
  • session.send(result) – send JSON/response.
  • session.loadTheme(name, variant) – load theme/layout.
  • theme.views.push("viewFile") – add view to render.
  • theme.renderView(session) – perform view rendering.

For WebSocket (WebSocketSession):

  • session.service?.clients – all connected WS clients (for broadcasting).
  • session.clients – clients linked to this session (usable from HTTP handler integrated with WebSocket).
  • client.send(data) – send a message to client.

4. Views & Themes

From the controller, views and themes are typically wired like this:

let theme = session.loadTheme?.("default", "default");
theme.header = "";
theme.views.push("main.default.ejs");
await theme.renderView(session);
  • session.loadTheme("default", "default") – loads theme configuration (e.g. from themes/default) and returns a theme instance.
  • theme.header = "" – customize header HTML or disable default header.
  • theme.views.push("main.default.ejs") – queue a view file located under src/modules/main/views/ as main content.
  • theme.renderView(session) – perform final view rendering via configured view engines (EJS, CKN HTML, etc.).

Layout logic (header/footer/menu) is centralized in the Theme, while page content is defined in Views, keeping responsibilities clean.

5. Frontend Components (apps/)

Frontend components are written using @ckn-technology/ui and placed in apps/. They are compiled into client/ by FrontendMiddleware and can be used inside views or as SPA sections.

5.1 Sample component: apps/Default.js

import { ckn } from "@ckn-technology/core";
import { Component } from "@ckn-technology/ui";
import { Socket } from "@ckn-technology/server";

class Default extends Component {
    async onTemplateBuilding() {
        return /*html*/ `
        <div>
            <h1>Test Web Socket</h1>
            <div>Name: {{this.data.name}}</div>
            <div>Time: {{this.data.time.dateTimeDataFormat()}}</div>
            <div>Message: {{this.data.message}}</div>
            <input type="text" @value="{{this.text}}">
            <button click()="{{this.clickMe()}}">send</button>
        </div>`;
    }

    async clickMe() {
        let data = this.text;
        this.#socket.send(data);
        // Alternatively, via HTTP API:
        // await ckn.api.get("http://localhost:5900/data?message=" + encodeURI(data));
    }

    #socket = null;

    async onInitializing() {
        this.text = "";
        this.data = {
            name: "Name1",
            time: new Date(),
            message: "",
        };

        this.#socket = new Socket("ws://localhost:5900/data");

        this.#socket.onStateChanged = async () => {
            await this.reload();
        };

        this.#socket.onConnected = async () => {
            console.log("Connected");
        };

        this.#socket.onReceived = async (data) => {
            this.data.message = data;
        };

        await this.#socket.connect();
    }
}

export { Default };

5.2 Component lifecycle

  • onInitializing() – called when component is created; good place to initialize state and connect to WebSocket.
  • onTemplateBuilding() – returns HTML template string with data binding (e.g. {{this.data.name}}, @value="{{this.text}}").

5.3 WebSocket flow (browser side)

  • Component constructs new Socket("ws://localhost:5900/data").
  • Registers onStateChanged, onConnected, onReceived.
  • Calls await this.#socket.connect() in onInitializing().
  • When user clicks send, calls this.#socket.send(data).
  • Incoming messages update this.data.message and the UI re-renders via this.reload() triggered in onStateChanged.

6. How Things Connect (End-to-End Flow)

  1. Server (start.ts):
    Sets up middleware pipeline, port, and module discovery.
  2. Module (src/modules/main):
    Contains DefaultController, view main.default.ejs, and optional model/public resources.
  3. Controller:
    default route renders the initial page using the theme and EJS view.
    data route exposes both WebSocket and HTTP interface on /data.
  4. Component (apps/Default.js):
    Renders interactive UI in the browser and talks to server through WebSocket ws://localhost:5900/data (and optionally HTTP).
  5. Frontend pipeline:
    apps/* → built into client/* by FrontendMiddleware.
    StaticContentMiddleware serves /client/app.js, etc.

Combined, these pieces create a simple real-time example: user types a message in the browser, component sends it via WebSocket, controller broadcasts to all clients, and each client updates its UI accordingly.

7. Suggested Best Practices

  • One module per business domain
    Example: modules/user, modules/chat, modules/admin.
  • Keep controllers thin
    Controllers should primarily translate HTTP/WebSocket/session into domain/service calls. Move heavy business logic into services/models.
  • Use themes for layout
    Put header/footer/menu in themes instead of duplicating in each view.
  • Use WebSocketSession for real-time features
    Use session.service?.clients for broadcast and consider room/channel management via memory or external stores.
  • Use .env + EnvironmentVariableMiddleware
    Keep ports, DB config, external endpoints, etc. in environment variables.
  • Keep apps/ decoupled
    Frontend components should communicate through published APIs & WebSocket endpoints, not read server internals directly.

8. Next Steps for the Framework

Ideas for expanding this documentation in the future:

  • Full API reference for: Server, Middleware, Controller, Session, WebSocketSession.
  • CLI usage (if there is a ckn CLI).
  • Error handling & logging conventions with @ckn-technology/core.
  • Authentication / authorization patterns.
  • Production deployment guidance (Docker, reverse proxy, TLS, scaling, etc.).

This page is intended as a developer overview + practical guide for working with @ckn-technology/server as part of the CKN Framework.