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.
- Usage
- TypeScript for static type definitions
- ESLint for code quality
- Prettier for code formatting
- Jest for JavaScript testing
- Publish coverage to codecov
- Testing
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.
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 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 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
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
> [email protected] 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.
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:
$ git clone https://github.com/Nauja/simple-typescript-tcp-jsonrpc.git
$ cd simple-typescript-tcp-jsonrpc
$ npm install
$ npm test
Licensed under the MIT License.