Skip to content

Nauja/simple-typescript-tcp-jsonrpc

Repository files navigation

simple-typescript-tcp-jsonrpc

build status Lint codecov License: MIT code style: prettier

This is just an example of a simple TCP server using JSON-RPC as a protocol.

The goal is to demonstrate how to build a simple TCP server with NodeJS and make a good use of basic best practices such as TypeScript, unit testing or lint.

Table of contents:

Usage

This sample is not meant to be run standalone, it is more meant as a demonstration of writing a TCP server in NodeJS, respecting the best practices, and running unit tests.

But you can still run the sample with:

$ npm run build
$ npm run start
Server listening for connection requests on socket 0.0.0.0:65521

This starts a TCP server you can send JSON-RPC messages to.

TypeScript for static type definitions

It is greatly recommended to use TypeScript on a big codebase as it make the code more readable, secure and reliable by adding static type definitions.

The first thing is to install typescript and @types/node for NodeJS type definitions:

$ npm i --save-dev typescript @types/node

Now create a tsconfig.json configuration file containing:

{
    "compilerOptions": {
        "module": "commonjs",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "target": "es6",
        "noImplicitAny": true,
        "moduleResolution": "node",
        "sourceMap": true,
        "outDir": "dist",
        "baseUrl": ".",
        "paths": {
            "*": [
                "node_modules/*",
                "src/types/*"
            ]
        }
    },
    "include": [
        "src/**/*"
    ]
}

This tells TypeScript to compile all .ts files from "src/**/*" to the "outDir": "dist" folder. Also note the "module": "commonjs" configuration that makes TypeScript compile your code to CommonJS modules.

For example, a foo.ts file with the following code:

export function foo()
{
    // content
}

Would be compiled to foo.js:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.foo = void 0;
function foo() {
    // content
}
exports.foo = foo;

The last step is to add the following script in package.json:

"scripts": {
    "build": "tsc"
}

You can now build your code with:

$ npm run build

ESLint for code quality

ESLint is a tool for helping to find and fix problems in JavaScript code. It is easily usable from command line or with Visual Code.

Create an .eslintrc file:

{
    "parser": "@typescript-eslint/parser",
    "extends": ["plugin:@typescript-eslint/recommended"],
    "rules": {
        "sort-imports": [
            "error",
            {
                "ignoreCase": false,
                "ignoreDeclarationSort": false,
                "ignoreMemberSort": false,
                "memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
                "allowSeparatedGroups": false
            }
        ],
        "@typescript-eslint/no-explicit-any": 1,
        "@typescript-eslint/no-unused-vars": "warn"
    }
}

This make eslint use the default rules existing in @typescript-eslint/recommended plus the ones you define under "rules". Here we force to sort imports by names, to avoid using the explicit type any in our code, and we warn about unused variables.

Create an .eslintignore file:

node_modules
dist

This will make eslint ignore node_modules and dist folders.

Add the following script in package.json:

"scripts": {
    "lint": "tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix"
}

Run eslint with:

$ npm run lint

There are many rules you can enforce with eslint to make your code more safe and readable. For example, you can forbidden the require statement as part of an assignment:

const Server = require("jsonrpc-node").TCP.Server
// Should be:
// import * as jsonrpc from "jsonrpc-node"
// const Server = jsonrpc.TCP.Server

Running eslint would output:

simple-typescript-tcp-jsonrpc\src\app.ts
  3:16  error  Require statement not part of import statement  @typescript-eslint/no-var-requires

✖ 1 problem (1 error, 0 warnings)

Or in Visual Code (make sure to install the ESLint extension):

Prettier for code formatting

Prettier is an opinionated tool to format your code consistently so everyone working on the project follow the same coding style and the code is more readable. You can use it both from command line and from VSCode.

Just install it:

$ npm i --save-dev prettier

Create a .prettierrc configuration file:

{
    "trailingComma": "none",
    "tabWidth": 4,
    "semi": true,
    "singleQuote": false
}

While those settings may be subjective, make sure to commit .prettierrc in your project so everyone follow the same rules.

Add the following script in package.json:

"scripts": {
    "format": "prettier --write \"src/**/*.ts\"  \"test/**/*.ts\""
}

And now you can format your codebase with:

$ npm run format

Jest for JavaScript testing

This sample use Jest for unit testing as explained here github.com/microsoft/TypeScript-Node-Starter.

The first thing is to install jest and @types/jest for type definitions:

$ npm i --save-dev jest @types/jest

You also need to install ts-jest for testing a project written in TypeScript:

$ npm i --save-dev ts-jest

Jest configuration is done in jest.config.js:

module.exports = {
    globals: {
        "ts-jest": {
            tsconfig: "tsconfig.json"
        }
    },
    moduleFileExtensions: [
        "ts",
        "js"
    ],
    transform: {
        "^.+\\.(ts|tsx)$": "ts-jest"
    },
    testMatch: [
        "**/test/**/*.test.(ts|js)"
    ],
    testEnvironment: "node"
};

This configures Jest to run all tests from the test directory matching the **/test/**/*.test.(ts|js) pattern. It also enable ts-jest to allow testing code written in TypeScript.

The last step is to add the following script in package.json:

"scripts": {
    "test": "jest --forceExit --coverage --verbose"
}

You can now run tests with:

$ npm run test
> simple-typescript-tcp-jsonrpc@1.0.0 test simple-typescript-tcp-jsonrpc
> jest --forceExit --coverage --verbose

 PASS  test/app.test.ts
  test server RPCs
    √ call ping should send pong (37 ms)

  console.log
    Server listening for connection requests on socket 0.0.0.0:65402

      at Server.<anonymous> (src/app.ts:23:17)

  console.log
    server port is 65402

      at test/app.test.ts:13:21

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |       50 |     100 |     100 |
 app.ts   |     100 |       50 |     100 |     100 | 28
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.808 s, estimated 4 s
Ran all test suites.

Publish coverage to codecov

For continuous integration, it is best to automatically publish coverage results to a service like codecov.io.

It can be easily done with codecov:

$ npm install --save-dev codecov
$ npm run test
$ ./node_modules/.bin/codecov --token=CODECOV_TOKEN

Once your report is uploaded to codecov, you can see it online:

Testing

$ git clone https://github.com/Nauja/simple-typescript-tcp-jsonrpc.git
$ cd simple-typescript-tcp-jsonrpc
$ npm install
$ npm test

License

Licensed under the MIT License.