Skip to content

Extending Example

Wouter den Bakker edited this page Nov 29, 2019 · 4 revisions

The best way to extend the server is using it as a dependency. This makes it easy to get the latest updates without having to worry about merge conflicts. This tutorial/example will take you through setting up a node.js project with typescript, as well as some examples for creating a custom api. For a full list of all available functions see Extending Functions

The environment

  • Download and install node.js and yarn.
  • Create a folder with:
    • A tsconfig.json file, you can copy the file from the Validana Server.
    • A package.json file. A minimum file can look like:
{
	"name": "myapp",
	"version": "1.0.0",
	"scripts": {
		"build": "tsc",
		"start": "node dist/index.js"
	},
	"dependencies": {
		"@types/node": "*",
		"@coinversable/validana-server": "^2.1.1"
	},
	"devDependencies": {
		"typescript":"^3.7.0"
	}
}
  • Run yarn install in the folder to download the dependencies.

The code

  • Create a file 'src/index.ts'. This is the entry point of the program. An example of how it looks like can be found below.
import * as cluster from "cluster";
import { Log, Config, start } from "@coinversable/validana-server";
//Import the class that will take care of incoming api requests. We will create this in a moment.
import { MyHandler } from "./myhandler";

//Optionally, if you want to use Sentry use this to report the version to Sentry.
Log.options.tags.version = require("../package.json").version;

//Want more config options? Add them here and it will load them from the config file/environmental variables.
//First argument is the name of the option, second argument is a default value (if any), third argument a validator function (if any).
Config.addStringConfig("VSERVER_APPNAME", "defaultName", (name) => {
	if (name === "") {
		throw new Error("Name may not be empty.");
	}
});
Config.addNumberConfig("VSERVER_MYFAVORITENUMBER");

//Need to load some files? Do some other setup? Custom setup can go here.
//Note that the server creates worker threads for handling incoming api requests, while the master just ensure they all stay online.
if (cluster.isWorker) {
	Log.info("Here is another worker.");
}

//The handler we create will be for version 1 of the api, we make it available at oursite.com/v1
const myApiVersions = new Map();
myApiVersions.set("v1", new MyHandler())

//Finally we start the server. At this point all config values will be loaded.
start(myApiVersions);

//Any further setup you want to do now that the config values have been loaded.
  • Create a file 'src/myhandler.ts'. This will contain all requests you can make to the api.
import { addBasics, RequestHandler, Database, Message, Config } from "@coinversable/validana-server";

//Create a custom class that extends RequestHandler. We use addBasics() to add all build-in api queries.
export class MyHandler extends addBasics(RequestHandler) {
	constructor() {
		super();


		//Here we add custom api requests. The first argument is the query to call the api with.
		//The second argument is the function that deals with the request.
		//The function itself will receive 2 arguments: The data that came with the request,
		// and an object containing information about the request and response.
		this.addMessageHandler("guessfavoritenumber", this.guessFavoriteNumber);
		this.addMessageHandler("getobjects", this.objectsRequest);
	}

	protected async guessFavoriteNumber(data: number): Promise<string> {
		//Validate if data we receive is correct.
		if (typeof data === "number") {
			//By returning a string instead of an error we notify the server this is a user error.
			//This also sets the status code of the response to 400 (instead of 500 on errors).
			return Promise.reject("That is not a number.");
		}

		//Load a config value.
		const favNumber = Config.get<any>().VSERVER_MYFAVORITENUMBER;
		//We gave it no default or validator requiring it has a value, so it may be undefined
		if (favNumber === undefined) {
			return "We do not have a favorite number."
		} else if (favNumber > data) {
			return "Too low.";
		} else if (favNumber < data) {
			return "Too high.";
		} else {
			return "Correct.";
		}
	}

	//If you added the example contracts from: https://github.com/Coinversable/validana-processor/wiki/Example-Smart-Contracts
	protected async objectsRequest(data: string, message: Message): Promise<any[]> {
		if (typeof data !== "string") {
			return Promise.reject("Invalid owner.");
		}

		//Do a parameterized database query.
		const dbResult = await Database.get().query("SELECT * FROM objects WHERE owner = $1;", [data]);
		if (dbResult.rows.length === 0) {
			//We can use the message to overwrite the default status code.
			message.statusCode = 404;
			return Promise.reject("No objects found for this owner.");
		} else {
			return dbResult.rows;
		}
	}
}

Running the server

  • The server assumes a Validana Processor/Node has been set up.
  • Create a Config file and fill in the values. You can also add the custom VSERVER_MYFAVORITENUMBER from the example if you want.
  • Next build the server with yarn build.
  • If everything succeeds you can start the server with yarn start path/to/config.json.
  • You can now access the server, for example try http://localhost:8080/v1/guessfavoritenumber?123

Using docker

  • Create a file named 'Dockerfile', see an example below, that will copy the build example server from the same folder:
ARG NODEVERSION=12
FROM node:${NODEVERSION}

ENV NODE_ENV=production
ENV NODE_NO_WARNINGS=1
COPY . /usr/node

# Add any environmental variables here. See the Validana Server Dockerfile.

USER node
WORKDIR /usr/node
ENTRYPOINT ["node", "dist/index.js"]
  • To build and start it follow the Setup

Using docker-compose

Assuming you are running a Validana Processor/Node with docker-compose:

  • Shutdown the Processor/Node (See the setup of the processor/node for how.)
  • Create a Dockerfile (see above)
  • Modify the docker-compose file and change services.server.build.context to this folder. You may need to move the docker-compose file to a parent folder/this folder.
    • Alternately remove the services.server entirely, change services.database.expose to services.database.ports and change "5432" to "5432:5432". In this case build, start and stop the Validana Server separately using the docker Setup.
  • Start the Processor/Node (See the setup of the processor/node for how.)

Clone this wiki locally