ZenWeb Framework

website·Indexed 29 days ago

https://zenweb.vercel.app

A modular TypeScript web framework designed for large-scale SaaS and monolithic projects. It features a dependency injection system inspired by Spring, multi-tenant multi-database support, and a comprehensive ecosystem of modules including MySQL integration, Redis caching, WebSocket support, and automated CRUD generation via ZenORM.

Tokens
42.3K
Snippets
234
Records
278
Agent score
74%

What's inside ZenWeb

  1. Overview of ZenWeb bundled modules

    The zenweb package integrates several core modules to provide a complete web application scaffold. The bundled modules include:

    • @zenweb/core: Core package for module loading and service startup.
    • @zenweb/inject: Dependency injection.
    • @zenweb/controller: Controller entry point for dependency injection.
    • @zenweb/meta: Runtime basic information, processing time, and versioning.
    • @zenweb/log: Logging support.
    • @zenweb/router: Routing support.
    • @zenweb/messagecode: Message code formatting.
    • @zenweb/body: Submission content parsing.
    • @zenweb/result: Return result processing.
    • @zenweb/helper: Input data type conversion and validation.
  2. Overview of zenweb framework design and architecture

    zenweb is a modular web framework written entirely in TypeScript, specifically designed for large-scale SaaS projects, though it is also suitable for monolithic projects. It emphasizes maintainability and flexibility through a modular architecture where components can be composed on-demand. The minimal installation requires only the @zenweb/core module, with additional modules added based on project requirements.
  3. ZenWeb Framework Architecture Overview

    ZenWeb is a modular web framework developed entirely in TypeScript, providing strong type checking and full code completion for enterprise-level maintenance. Key architectural features include:

    • Dependency Injection: Used to decouple code and simplify maintenance.
    • Modular Design: Features are modularized to prevent bloat and allow for on-demand configuration based on project needs.
  4. Understand ZenWeb multi-tenant database architecture

    ZenWeb implements a shared connection pool strategy for multi-tenancy to avoid exhausting database connections. Instead of creating a separate pool per tenant, all tenants on the same database server share one pool. When a request arrives, the system identifies the tenant, retrieves a connection from the server's pool, and executes USE <database_name> to switch to the tenant's specific database before running queries.
  5. Implement a custom ResultRender for non-JSON output

    To support output formats like XML or HTML, implement the ResultRender interface. Define the type, set enwrap (whether to wrap the result), and implement match(ctx) to determine if the renderer should be used (e.g., checking the Accept header) and render(ctx, data) to produce the final output. Register the renderer in the renders array of the @zenweb/result setup.
    // src/HtmlRender.ts
    import { ResultRender, Context } from 'zenweb';
    
    export class HtmlRender implements ResultRender {
      enwrap = false;
      type = 'html';
    
      async match(ctx: Context) {
        return ctx.accepts('text/html') === 'text/html';
      }
    
      async render(ctx: Context, data: unknown) {
        if (typeof data === 'string') return data;
        return `<!DOCTYPE html><html><body><h1>Error</h1><p>${JSON.stringify(data)}</p></body></html>`;
      }
    }
    
    // src/index.ts
    app.setup(result, {
      renders: [HtmlRender],
    });
  6. Handle Request Body data

    ZenWeb supports form-urlencoded, json, xml, and text/* formats. Depending on the required level of detail and validation, you can use different injection objects: BodyHelper for validated objects, ObjectBody for raw parsed objects, Body for parser metadata, or RawBody for the original Buffer.
    import { Post, BodyHelper, ObjectBody, Body, RawBody } from 'zenweb';
    
    export class DemoController {
      // 1. BodyHelper: Type conversion and validation
      @Post()
      demo1(bh: BodyHelper) {
        return bh.get({
          age: '!int', // Required integer
          name: { type: '!trim', minLength: 2, maxLength: 10 }, // Required, trimmed string
        });
      }
    
      // 2. ObjectBody: Direct access to parsed object (e.g., JSON/Form)
      @Post()
      demo2(body: ObjectBody) {
        return body; // { age: "10", name: "Bob" }
      }
    
      // 3. Body: Access parser info and data
      @Post()
      demo3(body: Body) {
        console.log(body.parser); // Parser object
        console.log(body.type);   // Data type
        return body.data;         // Parsed result
      }
    
      // 4. RawBody: Original Buffer (automatically decompressed if HTTP compression used)
      @Post()
      demo4(raw: RawBody) {
        console.log(raw.data); // Buffer object
        return 'Raw data processed';
      }
    }
  7. Install the Sentry module for ZenWeb

    Install the @zenweb/sentry module to integrate Sentry error monitoring into your ZenWeb application. This module automatically captures runtime exceptions and attaches request context (URL, headers, client IP, etc.) to the reports.
    npm install @zenweb/sentry
  8. Handle errors with fail() in templates

    When fail() is called in a controller, the process terminates and a fail variable (a ResultFail object from @zenweb/result) is passed to the template. You can check for this variable in the template to display error messages.
    <h1>Registration Page</h1>
    {{#if fail}}
      <h2>Error</h2>
      <p>{{fail.message}}</p>
    {{/if}}
  9. Install additional ZenWeb modules

    To add modules that are not included in the default bundle, use the .setup() method on the application instance created by create(). This allows you to load external modules (such as @zenweb/xml-body) before calling .start().
    import { create } from 'zenweb';
    import modXmlBody from '@zenweb/xml-body';
    
    create()
      .setup(modXmlBody()) // Install the module
      .start();
  10. Create custom global Helper shortcuts

    In ZenWeb v4.0+, you can create global shortcuts for any HelperBase subclass using the $helperBase() factory method. This allows you to use the helper directly in controllers without injecting it into the method signature. The resulting shortcut object provides the same methods as the original helper (e.g., get(), page()).
    // src/helper.ts
    import { $helperBase } from 'zenweb';
    import { UploadHelper } from '@zenweb/upload';
    
    // Create a global shortcut for UploadHelper
    export const $upload = $helperBase(UploadHelper);
    
    // src/controller/upload.ts
    import { Post } from 'zenweb';
    import { $upload } from '../helper';
    
    export class UploadController {
      @Post()
      async upload() {
        // Use the global $upload shortcut
        const fields = $upload.get({ title: 'trim' });
        const file = $upload.file('avatar');
        return { fields, file };
      }
    }
  11. Configure multi-tenant database support in ZenWeb

    Use modTenant in app.setup() to configure how tenants are identified and how database connection pools are managed. You must provide a tenantGetter to resolve the tenant's server and database name from the request context, and a pools configuration mapping server identifiers to their connection settings.
    import { create } from 'zenweb';
    import modTenant, { Tenant } from '@zenweb/tenant';
    
    export const app = create();
    
    const tenants: Record<string, Tenant> = {
      'a.example.com': { server: 'S1', database: 'db_a' },
      'b.example.com': { server: 'S1', database: 'db_b' },
      'c.example.com': { server: 'S2', database: 'db_c' },
    };
    
    app.setup(modTenant({
      tenantGetter: ctx => {
        const tenant = tenants[ctx.host];
        if (!tenant) throw new Error('Tenant not found: ' + ctx.host);
        return tenant;
      },
      pools: {
        S1: {
          MASTER: {
            host: '127.0.0.1',
            port: 3306,
            user: 'root',
            password: '123456',
            charset: 'utf8mb4',
            timezone: '+08:00',
            connectionLimit: 100,
          },
        },
        S2: {
          MASTER: {
            host: '192.168.1.100',
            port: 3306,
            user: 'root',
            password: 'secret',
            charset: 'utf8mb4',
            timezone: '+08:00',
            connectionLimit: 50,
          },
        },
      },
    }));
    
    app.start();