## Realtime Web Suite Server package

# RWS-JS Server Setup and Configuration Guide

RWS-JS is a flexible server for fullstack Realtime Web Suite framework that you can use to set up web servers, WebSocket servers, and more in sync with your frontend. The application's configuration is crucial for its successful operation. In this guide, you'll learn how to create and configure an RWS-JS server application.

## Table of Contents

1. Prerequisites
2. Setting Up
   - Install Package
     - Package File
     - TS Config File
   - Initialize Server Setup
   - Server Configuration
3. Creating Server Configuration
   - Example Webpack Config
   - RWS Webpack Config
   - RWS App Configuration
4. Commands
   - Sample Command
   - New Command
   - Database Models
5. Route Configuration
   - Request Route Configuration
   - Websocket Route Configuration
6. Running the Server
   - Server Initialization
   - DB Service
   - Implementation
7. BASH Installs for Node Libraries

## Prerequisites

Make sure you have Node.js and yarn installed on your local machine. If not, you can download it from the official Node.js website.

## Setting Up

### Install yarn

    npm install -g yarn

### Install package

    yarn add @rws-framework/server

### Package file

To use serve you need this packages in your package.json:

    {
       "dependencies": {,
            "@types/archiver": "^6.0.2",
            "@types/body-parser": "^1.19.5",
            "@types/express": "^4.17.21",
            "compression": "^1.7.4",
            "dotenv": "^16.3.1",
            "jsonwebtoken": "9.0.2",
            "nodemon": "^1.12.1",
            "npm-run-all": "^4.1.1",
            "puppeteer": "^21.0.3",
            "readable-stream": "^4.5.2",
            "reflect-metadata": "^0.2.1",
            "@rws-framework/server": "*",
            "ts-transformer-keys": "^0.4.4",
            "tsconfig-paths-webpack-plugin": "^4.1.0",
            "typescript": "^5.3.3",
            "webpack-node-externals": "^3.0.0"
        },
        "devDependencies": {
            "@types/chai": "^4.3.5",
            "@types/chai-like": "^1.1.1",
            "@types/chai-things": "^0.0.35",
            "@types/compression": "^1.7.5",
            "@types/jsonwebtoken": "9.0.2",
            "@types/lodash": "^4.14.202",
            "@types/mocha": "^10.0.1",
            "chai": "^4.3.7",
            "chai-like": "^1.1.1",
            "chai-things": "^0.2.0",
            "mocha": "^10.2.0",
            "ts-node": "^10.9.1",
            "webpack": "^5.75.0",
            "webpack-bundle-analyzer": "^4.10.1",
            "webpack-cli": "^5.1.4"
        }
    }

### Tsconfig file

tsconfig.json:

    {
        "compilerOptions": {
          "baseUrl": ".",
          "experimentalDecorators": true,
          "emitDecoratorMetadata": true,
          "target": "ES2018",
          "module": "commonjs",
          "moduleResolution": "node",
          "strict": true,
          "esModuleInterop": true,
          "sourceMap": true,
          "resolveJsonModule": true,
          "outDir": "dist",
          "strictNullChecks": false,
          "allowSyntheticDefaultImports": true,
          "paths": {
          }
        },
        "include": ["./src"],
        "exclude": []
      }

### Initialzie server setup (by default uses src/config/config.ts)

    yarn rws init

### OR with specifig config path

    yarn rws init path/to/cfg.ts/from/src

## Creating Server Configuration

Create a new file named `config.ts` in the root of your project. This file will export a function that returns a configuration object.

### Example webpack config

    const path = require('path');
    const keysTransformer = require('ts-transformer-keys/transformer').default;
    const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

    const  RWSWebPackSettings  = require('@rws-framework/server/rws.webpack.config');

    RWSWebPackSettings.resolve.plugins = [
      new TsconfigPathsPlugin({configFile: './tsconfig.json'})
    ]

    RWSWebPackSettings.output.path = path.resolve(__dirname, 'build');
    RWSWebPackSettings.output.filename = 'jtrainer.server.js',

    RWSWebPackSettings.devtool = 'source-map';
    RWSWebPackSettings.mode = 'development';

    // console.log(RWSWebPackSettings);

    module.exports = RWSWebPackSettings;

### RWS webpack config

rws.webpack.config.js:

    const path = require('path');
    const keysTransformer = require('ts-transformer-keys/transformer').default;
    const webpackFilters = require('./webpackFilters');
    const nodeExternals = require('webpack-node-externals');
    const UtilsService = require('./_tools');

    const rootPackageNodeModules = path.resolve(UtilsService.findRootWorkspacePath(process.cwd()), 'node_modules')

    const modules_setup = [rootPackageNodeModules];

    // console.log(modules_setup)s;

    module.exports = {
      entry: `${process.cwd()}/src/index.ts`,
      mode: 'development',
      target: 'node',
      devtool: 'source-map',
      output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'rws.server.js',
        sourceMapFilename: '[file].map',
      },
      resolve: {
        modules: modules_setup,
        extensions: ['.ts', '.js', '.node'],
        alias: {

        },
        symlinks: false
      },
      context: process.cwd(),
      module: {
        rules: [
          {
            test: /\.(ts)$/,
            use: [
              {
                loader: 'ts-loader',
                options: {
                  allowTsInNodeModules: true,
                  configFile: path.resolve(process.cwd() + '/tsconfig.json'),
                  // compilerOptions: {
                  //   paths: {
                  //     '*': [rootPackageNodeModules + '/*']
                  //   }
                  // }
                }
              }
            ],
            exclude: /node_modules\/(?!@rws-framework/server)/,
          },
          {
              test: /\.node$/,
              use: 'node-loader',
          }
        ],
      },
      plugins: [
      ],
      stats: {
        warningsFilter: webpackFilters,
      },
      externals: [nodeExternals({
        allowlist: ['@rws-framework/server'],
      })],
    };

### RWS App Configuration

Define the connection details for your MongoDB instance and server configurations:

src/config/config.ts:

    import { ConsoleService, IAppConfig } from "@rws-framework/server";

    import JWTUser from "../user/model";
    import { getModels } from "../models";
    import ControllerList from '../controllers/index';
    import TimeTrackerSocket from "../sockets/ChatSocket";
    import routes from '../routing/routes';
    import ws_routes from '../routing/sockets';
    import CommandList from '../commands';
    import dotenv from 'dotenv';

    export default (): IAppConfig => {
        dotenv.config();
        const DB_NAME: string = process.env.MONGO_DB_NAME;
        const DB_HOST: string = process.env.MONGO_HOST;
        const DB_PORT: number = parseInt(process.env.MONGO_PORT);
        const DB_USER: string = process.env.MONGO_INITDB_ROOT_USERNAME;
        const DB_PASS: string = process.env.MONGO_INITDB_ROOT_PASSWORD;

        const AWS_ACCESS_KEY: string = process.env.AWS_ACCESS_KEY;
        const AWS_SECRET_KEY: string = process.env.AWS_SECRET_KEY;

        const APP_DOMAIN: string = process.env.APP_DOMAIN;
        const PUB_FOLDER: string = process.env.PUB_FOLDER;

        const APP_PORT: number = parseInt(process.env.APP_PORT);
        const APP_WS_PORT: number = parseInt(process.env.APP_WS_PORT);
        const TESTING_PORT: number = parseInt(process.env.TESTING_PORT);
        const APP_SSL: boolean = process.env.APP_SSL === 'True';
        const APP_CORS_ALLOW: string = process.env.APP_CORS_ALLOW ? process.env.APP_CORS_ALLOW : APP_DOMAIN;

        const dbString: string = `mongodb+srv://${DB_USER}:${DB_PASS}@${DB_HOST}/${DB_NAME}?retryWrites=true&w=majority`;

        return {
            features: {
                ws_enabled: true,
                routing_enabled: true,
                ssl: APP_SSL
            },
            mongo_url: dbString,
            mongo_db: DB_NAME,
            port: APP_PORT,
            ws_port: APP_WS_PORT,
            test_port: TESTING_PORT,
            domain: APP_DOMAIN,
            cors_domain: APP_CORS_ALLOW,
            ssl_cert: '',
            ssl_key: '',
            secret_key: '',
            user_class: JWTUser,
            user_models: getModels(),
            controller_list: ControllerList,
            ws_routes: ws_routes,
            http_routes: routes(),
            commands: CommandList,
            aws_lambda_region: null,
            aws_access_key: AWS_ACCESS_KEY,
            aws_secret_key: AWS_SECRET_KEY,
            aws_lambda_role: null,
            aws_lambda_bucket: null,
            pub_dir: PUB_FOLDER,
        }
    }

### Commands

A sample command:

    import { ICmdParams, RWSCommand } from '@rws-framework/server';

    class HelloCommand extends RWSCommand {
        constructor(){
            super('hello', module); // "module" in constructor is required
        }

        execute(params?: ICmdParams): void {
            console.log('<HELLO COMMAND>\n')
            console.log('    Thanks for installing RWS junction instance, ' + params.user + '\n\n');
            console.log('    This is output of example command for RWS JS server framework.');
            console.log('                                   (src/commands/HelloCommand.ts).');
            console.log('    Develop your server with "yarn dev"\n');
            console.log('    Or build and start with "yarn build" and "yarn server"');
            console.log('\n\n\n    Params passed to this command (those starting with "_" are autogenrated by console script)');
            console.log(params);
            console.log('\n</HELLO COMMAND>')
        }
    }

    export default new HelloCommand();

Command name is set in RWSCommand class constructor params

args are passed to execute() method

    class NewCommand extends RWSCommand {
        constructor(){
            super('newcommand');
        }

        execute(args){
          console.log('DATABASE NAME IS: ', args._rws_config.mongo_db)
        }
    }

args are passed like this:

    npx rws newcommand arg1=val1,arg2=val2

### Database models

You need to create and export your models in a separate models.ts file. In this example, the getModels() function returns an array of RWS models:

    import TimeSeries from "./TimeSeries";
    import TimeTracker from "./TimeTracker";

    export function getModels(): any[] {
        return [
            TimeTracker,
            TimeSeries
        ]
    }

RWSModel example:

    import { RWSannotations, RWSModel } from "@rws-framework/server";

    import ITimeTracker from "./interfaces/ITimeTracker";
    import 'reflect-metadata';

    import TimeSeries from "./TimeSeries";
    const { InverseTimeSeries, TrackType } = RWSannotations.modelAnnotations;

    class TimeTracker extends RWSModel<TimeTracker> implements ITimeTracker {
      @TrackType(String, { required: true }, ['unique'])
      asset_id: string;

      @TrackType(Number)
      elapsed_time: number = 0;

      @TrackType(String, { required: true })
      obj_id: string;

      @TrackType(String)
      asset_type: string;

      @TrackType(Date)
      trace_date: Date;

      @TrackType(String)
      user_id: string;

      @TrackType(Boolean)
      instructor: boolean;

      @TrackType(Object)
      params: any;

      @InverseTimeSeries('time_tracker_measurements', 'measurements')
      measurements_ids: string[] = [];
      measurements: TimeSeries[] = [];

      static _collection = 'time_tracker';
      // static _interface = ITimeTracker;

      constructor(data?: ITimeTracker) {
        super(data);

        this.trace_date = new Date();
      }

     addTime(series: TimeSeries){
        this.measurements_ids.push(series.id);
        this.measurements.push(series);

        let sum = 0;

        this.measurements.forEach(measurement => {
          sum += measurement.value;
        });

        this.elapsed_time = sum;
     }

     getTimes(): TimeSeries[]{
      return this.measurements;
     }
    }

    export default TimeTracker;

AFTER EVERY MODEL FIELD CHANGE RUN:

    yarn rws init

this will update prisma schema for async DB Calls with new fields and their types

## Route Configuration

### Request Route Configuration

Define your http routes in file that returns array IHTTPRoutes interfaces

    import routes from './routing/routes';

routes.ts:

"name" is annotation route name for controllers "path" is request path

    import {IHTTPRoute} from "@rws-framework/server";

    export default (): IHTTPRoute[] => {
        return [
            {
                prefix: '/prefix',
                routes: [
                    {
                        name: 'prefix:controller:route',
                        path: '/prefix/route/path/with/:param'
                    },
                ]
            },
            {
                name: 'home:route',
                path: '/*',
                noParams: true, // this route will not process parameters and put them to request object
            },
        ]
    }

Controller routing usage:

    import {
        RWSannotations,
        RWSController,
        IRequestParams
    } from "@rws-framework/server";

    const { Route } = RWSannotations.routingAnnotations;
    class HomeController extends RWSController{
        @Route('home:index', 'GET')
        public indexAction(params: IRequestParams): Object
        {

            return {
                'success': true
                'data': {
                  //your response stuff
                }
            } // Send a response for the root route
        }
    }

    export default HomeController.getSingleton();

A controller action with route ":param" usage - this one is called ":bookId'

      @Route('train:get:book', 'GET')
      public async getBookAction(params: IRequestParams<any>): Promise<IBook>
      {
          return await Book.findOneBy({ id: params.req.params.bookId });
      }

A controller action that outputs "template_name" HTML file from "pub_dir" config setting.

default responseType for

Generated using TypeDoc