Data migrations in Node applications

Setup db-migrate tool

As your application grow your database model evolve too.  The changes from one version to another is made by migrations. Each migration in the db-migrate tool is in essence  a small javascript program. You can code your migration changes manually in the javascript syntax or generate small javascript program (with –sql-file option) to execute your SQL script files. One for the UP and one for the DOWN function of migration.

up: you upgrade your database schema to the new version
down: you reverse latest changes to the previous version
create: create new migration, with an –sql-file option create a SQL file runner

Installation

We install db-migrate module and corresponding database driver with npm.

npm install db-migrate
npm install db-migrate-pg

Depends where you install it the command is available as:

$ ./node_modules/.bin/db-migrate
or
$ db-migrate

It is possible to add local ./node_module/.bin folder to the local path variable as I described in this article and call it the same way as you would if you install it in to the global space.

Configuration

Minimal config file (database.json) to work with postgres database in the development environment:

{
  "dev": "postgresql://postgres:postgres@localhost:5432/dbsam"  
}

The file should be in the main project folder. More about configuration.

Migrations with SQL files

To create new empty migration use the command:

$ db-migrate create new_mig_name --sql-file

By default your migration javascript files reside in the “./migrations” folder and sql files in the “./migrations/sqls”  subfolder.

Three new files are created, all files are prefixed with the timestamp which represent order of execution.

Additional two files with the suffix sql are prepared for your upgrade script and downgrade script. If you wish to have ability to downgrade database to the previous level make sure you write down script to.

Example of upgrade script (it is just for the sake of example, you basically write DDL statements with the familiar SQL syntax):

/*
* Table: Currency  
*
* Contains currencies 
*/
CREATE TABLE currency (
	code                varchar(60) not null,               -- code 
	abbreviation        varchar(60),						-- ex. EUR, USD
	description         text,								
	row_id              serial primary key,                 -- row identifier, autonumber, pk 
	org_id				smallint not null,					-- multitenant identifier 
	co_id				int NOT NULL,						-- company identifier  
	created_at          timestamptz not null default now(),
	created_by          varchar(60) not null,
	modified_at         timestamptz,
	modified_by         varchar(60),
	CONSTRAINT currency_sys_org_fk FOREIGN KEY (org_id) REFERENCES sys_org (row_id),
	CONSTRAINT currency_sys_co_fk FOREIGN KEY (co_id) REFERENCES sys_co (row_id)
) WITHOUT OIDS;                                               -- don't create object identifier field 
CREATE INDEX currency_org_id_fkix ON currency (org_id);
CREATE INDEX currency_co_id_fkix ON currency (co_id);
CREATE INDEX currency_created_at ON currency (created_at DESC) ;
CREATE INDEX currency_modified_at ON currency (modified_at DESC) ;

Example of downgrade script:

DROP TABLE currency;

Run “db-migrate up” command and all your migrations which are not yet executed will run.

The migration log is kept in the database migrations table. The table is automatically created at first run.

Other useful commands

reset: will rewind your database to the state before first migration is called

Using db-migrate with typescript

If you write node programs with the typescript you probably wish to use it with migration to. I didn’t go in this direction simply because I write my scripts in SQL language and runners are just perfect already in javascript. Because the migration are already in the javascript (the runner part), you should exclude migrations folder from the typescript compiler path.

Sample tsconfig.json file with excluded migrations folder :

{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "moduleResolution": "node",
        "noEmitOnError": true,
        "noImplicitAny": true,
        "experimentalDecorators": true,
        "sourceMap": true,
        "baseUrl": ".",
        "allowJs": true,
        "paths": {
            "*":[ 
                "src/types/*"
            ]
        },
        "outDir": "dist"
    },
    "typeRoots": [
        "node_module/@types",
        "src/@types"
    ],
    "include": [
        "src/**/*",
        "spec/**/*"    
    ],
    "exclude": [
        "dist",
        "migrations",
        "node_modules"
    ]
}

I didn’t include migration scripts to production deployment code yet, that step would be necessary if you wan’t to upgrade database automatically after deployment.

External links

Database migration tool: Db-migrate.
Gui prototyping and drawing tool: The pencil.

Testing TypeScript Node app with Jasmine

Setup Jasmine testing framework with Node and TypeScript

My typescript project setup is described in this article. To be able to write tests in typescript with Jasmine framework we need to setup project environment with some Jasmine specifics.

This scaffold project is also available on the github.

Initialize typescript compiler and linter :

npm install typescript@latest --save-dev
tsc --init --moduleResolution node --target ES2017 --sourceMap --module commonjs --removeComments --outDir dist 

npm install tslint --save-dev
tslint --init

npm install @types/node --save-dev

Compiled javascript files for code and jasmine test specifications will be saved to “dist” folder.

Now we need to install Jasmine and jasmine to typescript reporter:

npm install jasmine @types/jasmine --save-dev
npm install jasmine-ts-console-reporter --save-dev

Jasmine typescript console reporter (jasmine-ts-console-reporter) will report errors based on typescript files instead of javascript files. It will tell you which test and the location in the file failed referring to typescript files instead of javascript files.

Initialize jasmine in your project :

./node_modules/.bin/jasmine init

Change generated jasmine.json to match this one:

{
  "spec_dir": "dist/spec",
  "spec_files": [
    "**/*[sS]pec.js"
  ],
  "helpers": [
    "helpers/**/*.js"
  ],
  "stopSpecOnExpectationFailure": false,
  "random": false
}

Create special helper to enable “jasmine to typescript” console reporter. In the spec/helpers folder add new file “ts_console.ts” with the content:

// tslint:disable-next-line:no-var-requires
const TSConsoleReporter = require("jasmine-ts-console-reporter");

jasmine.getEnv().clearReporters(); // Clear default console reporter
jasmine.getEnv().addReporter(new TSConsoleReporter());

Prepare test code

Write some program logic and tests:

export class MyApi {
    public getName(): string {
        return "";
    }
}

To test this simple api we create “myapi-spec.ts” file in the spec folder:

import {MyApi} from "../src/MyApi";

describe("MyApi getName function return value", () => {
    it("Should be defined.", () => {
        const myapi = new MyApi();
        expect(myapi.getName()).toBeDefined("The function getName() should be defined.");
    });

    it("Should't return blank.", () => {
        const myapi = new MyApi();
        expect(myapi.getName()).not.toMatch("", "The function getName() should return the name.");
    });

    it("Should return 'MyName'" , () => {
        const myapi = new MyApi();
        expect(myapi.getName()).toMatch("MyName");
    });

} );

The full project structure:

To compile all typescript to javascript run “tsc” command in the root of the project.

tsc

And to run jasmine tests we run locally installed jasmine with the command:

./node_modules/.bin/jasmine

If everything is correct, jasmine will report result of the run in the console:

Started
.FF

Failures:
1) MyApi getName function return value Should't return blank.
  Message:
    Expected '' not to match '', 'The function getName() should return the name.'.
  Stack:
        at UserContext.it (c:\Bisaga\Workspaces\learning\mytest\spec\myapi-spec.ts:11:36)

2) MyApi getName function return value Should return 'MyName'
  Message:
    Expected '' to match 'MyName'.
  Stack:
        at UserContext.it (c:\Bisaga\Workspaces\learning\mytest\spec\myapi-spec.ts:16:32)

3 specs, 2 failures
Finished in 0.025 seconds

Because we add additional “typescript console reporter” to jasmine, the errors reported are referenced to typescript (.ts) files with exact line/column and error message.

Integrate jasmine test results with Code editor

First we need to understand how vs code editor tasks works. As we see in jasmine report, each failure is described in multiple lines. The problem matcher should analyze multiple lines pattern, this is described here.

Problems tab should display errors in tests after test run

Create task for running jasmine and to intercept errors as “problems” in the visual studio code problems tab:

As we see in the screenshot, the exceptions in jasmine test results are intercepted by vs code editor and presented as “problems”.  If you click on the single problem line, the file under the error will be opened and cursor will be moved to the corresponding location.

Task

In the .vscode folder create “tasks.json” file with the jasmine command and additional logic to parse results (problemMatcher) :

{
    "version": "0.1.0",
    "isShellCommand": false,
    "showOutput": "always",
    "tasks": [
        {
            "taskName": "run test",
            "showOutput": "always",
            "command": "npm",
            "args": [
                "run", 
                "test"
            ],
            "problemMatcher": {
                "owner": "javascript",
                "fileLocation": ["absolute"],
                "severity": "error",
                "pattern": [
                    {
                        "regexp": "Message.*"
                    },
                    {
                        "regexp": "[^ ](.*)",
                        "message": 1
                    },
                    {
                        "regexp": "Stack.*"
                    },
                    {
                        "regexp": "at +.*\\((.*\\\\*.ts):(\\d+):(\\d+)\\)",
                        "file": 1,
                        "line": 2,
                        "column": 3
                    }
                ]
            }
        }
    ]
}

How to write regular expressions

If you will study the pattern matching you should remember that matcher works on the console results line by line. All patterns together (all regexp lines patterns) represent one intercepted error.

If you need to change any of patterns in the future, the regex string in the tasks.json file is written in the “escaped” form. To develop and test patterns interactively, use this interactive javascript regex tester.  You can copy specific line from the result (line by line!) into the regex tester and develop a pattern to recognize searched variables.

Un-escaped form:  at +.*\((.*\\*.ts):(\d+):(\d+)\)
Escaped form:     at +.*\\((.*\\\\*.ts):(\\d+):(\\d+)\\)

Some problems still exists:

Because the matching is done line by line, when console window is small, and the lines are wrapped, they are not consistent with the written lines from the jasmine reporter anymore. The reported errors are wrapped at the width of the console and pattern matching doesn’t recognize them as errors any more.

You can somehow mitigate that with larger terminal window or with smaller terminal window font. This way the reported errors will remain in single lines consistent with the jasmine reporter. I wish that this wrapping could be disabled but for now it is not an option.

To change the size of the font in the terminal window add this settings to the settings.json file:

    "terminal.integrated.fontFamily": "Consolas, 'Courier New', monospace",
    "terminal.integrated.fontSize": 10,

Anyway, this problem is already recognized and I hope it will be resolved in the future.

Create a Node cluster with Koa and TypeScript

Cluster

Any JavaScript program runs in single OS process but of course is able to process requests asynchronously.  On the Node server we are able to start multiple processes.  To do that we use cluster module, so you are not really limited to one CPU core anymore.

Before start you need to install project prerequisites:

npm install koa @types/koa koa-router @types/koa-router node-fetch @types/node-fetch --save

If you need help with basic project setup look at this blog article where I describe basic setup steps.

Create Koa web server application

The purpose of this server application is for testing cluster technology only.  The application support two routes , first is default route (/) for checking on which process runs and another (/stop) for stopping active process. The console log show workers statuses from starting , stopping and restarting.

import * as Koa from "koa";
import * as Router from "koa-router";

export class ServerRun {
    private port: number;
    private app: Koa;
    private router: Router;
    private count: number;

    public constructor(port: number) {
        this.port = port;
        this.app = new Koa();
        this.router = new Router();
        this.count = 0;
    }

    public start() {
        this.router.get("/", async (ctx, next) => {
            this.count++;
            ctx.body = `This is server process : ${process.pid} and count: ${this.count}`;
            ctx.status = 200;
            await next();
        });

        this.router.get("/stop", async (ctx, next) => {
            console.log(`Stopping server process : ${process.pid}`);
            process.exit(1);
            await next();
        });

        this.app.use(this.router.routes());
        this.app.use(this.router.allowedMethods());

        const server = this.app.listen(this.port, () => {
            console.log(`Server process: ${process.pid} listen on port ${this.port}`);
        });

        this.app.on("error", (e) => console.log(`SEVERE ERROR: ${e.message}`) );
    }
}

If you want to debug this server application just create simple index.ts and start the ServerRun class.

// Run server for debug in single process without cluster
import {ServerRun} from "./server_run";

const app = new ServerRun(3002);
app.start();

Create cluster server start

To start this server in the cluster we need to fork process workers and start koa application.

import * as cluster from "cluster";
import {ServerRun} from "./server_run";

import {cpus} from "os";

const numCPUs = cpus().length;

if (cluster.isMaster) {
    console.log(`This machine has ${numCPUs} CPUs.`);
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on("online", (worker) => {
        console.log(`Worker ${worker.process.pid} is online`);
    });

    cluster.on("exit", (worker, code, signal) => {
        console.log(`Worker ${worker.process.pid} died with code: ${code} and signal: ${signal}`);
        console.log("Starting a new worker...");
        cluster.fork();
    });

} else {
    const app = new ServerRun(3002);
    app.start();
}

To start this server we need to compile everything to TypeScript first and then start node with “run_cluster” procedure.

tsc
node ./dist/run_cluster.js

As we see on the console the server start multiple processes and listen on the same port. Well, that’s whole point, to be able to send requests to the same address and server will divide load to multiple processes.

Test the server

Now if you go to browser and navigate to “localhost:3002/”, the server will respond with value of the current process id and count variable. Try refreshing multiple times, the counter will always increase by 1. If you then kill the process and navigate again, you will get another process id and the counter will start again with one. Each process has his own Koa application instance.

Kill the process and test server robustness

If you navigate to “localhost:3002/stop”, you will essentially kill current process. After irrecoverable failure on the server process,  cluster “exit” event is triggered and we simply start new process again.

Stopping server process : 9892
Worker 9892 died with code: 1 and signal: null
Starting a new worker...
Worker 7040 is online
Server process: 7040 listen on port 3002

 

 

Asynchronous programming with async/await pattern

JavaScript is single threaded

Everything running in JavaScript is running in the single OS process and consume a single thread. But when you are waiting for something outside the JavaScript process to happen, like waiting for database query to be executed or some file stream reader to load a file, you are able to return control back to your main JavaScript event loop and therefore any new event/request in your JavaScript program will be processed in the meantime.

It means your JavaScript program, if written properly, will never really waiting. It just runs all the time.

Async/Await pattern

Returning control to JavaScript event loop when waiting something outside JavaScript to happen,  is not automatic. You need to specify this manually in code with proper use of “Promise” objects and async/await functionality.

Sometimes the promise object is implicitly defined by “async” keyword in front of the function but on the end you always work with the promises.

Using asynchronous programming with TypeScript

To learn how to properly use promises and async/await in typescript I create a very small web application.

Create minimal server app

For this mini app I use Koa server. The app will support two routes: “/” and “/start”.  With “start” I will start long running operation and with “/” route I will check if server is still available and responsive.

Prepare project for sample code

npm install koa @types/koa koa-router @types/koa-router node-fetch @types/node-fetch --save

 

After running the sample (index.ts) node application we should be able to start long running task with navigating to “localhost:3002/start” address and in the same time check if the same server is still active and responsive with default route “localhost:3002” which show new value of the counter after each refresh.

Long running task will not always succeed, only when random value is higher then 500 will. If value is lower then the error is thrown and implemented error handling intercept it with try/catch block.

Code for this sample app:

import * as Koa from "koa";
import * as Router from "koa-router";
import fetch from "node-fetch";

async function long_run(): Promise<number> {
    const rnd = Math.round(Math.random() * 1000);
    if (rnd > 500) {
        for (let i = 0; i < 10; i++) {
            const response = await fetch("https://www.google.com/search?q=typescript");
        }
        return rnd;
    } else {
        throw new Error(`ERROR: The value (${rnd}) is under 500.`);
    }
}

async function start(ctx: Router.IRouterContext): Promise<number> {
    let message = "";
    let result: number;
    try {
        result = await long_run();
        console.log(`This is inside start, after long_run ${result}`);
        message = `Returned from long run: ${result}`;
    } catch (e) {
        message = `Intercepted [${e.message}] exception.`;
        result  = -1;   // we didn't receive a value if throw is intercepted from await !
                        // Promise was rejected, value is not available.
    }
    // form the web response
    ctx.body = message;
    ctx.status = 200;
    return result;
}

const router = new Router();
router.get("/start", async (ctx, next) => {
    const res = await start(ctx);
    const status = res === -1 ? "Error" : "Success";
    console.log(`Long running task finished with ${status}.`);
    await next();
});

router.get("/", async (ctx, next) => {
    counter++;
    ctx.body = `You ran for the ${counter} times.`;
    ctx.status = 200;
    await next();
});

let counter = 0;
const app = new Koa();
const port = 3002;

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(port, () => console.log(`Server listen on port ${port}`) );
app.on("error", (e) => console.log(`SEVERE ERROR: ${e.message}`) );

As we see the code looks more or less like ordinary synchronously written one except async keyword used in function declaration and await keyword where asynchronous methods are called.

Promise

If function is marked as async then the function will automatically return promise object. If function doesn’t have return statement, then this promise will be void (Promise<void>). If you write return with some real value, then promise will represent that value. Function will automatically wrap your value and return promise with it. Returning a value from async function means resolving the promise. If you throw exception from async function means your promise will be rejected.

Writing promise by hand

let p = new Promise((resolve, reject) => {
    const ok = true; // do something 
    if (ok) {
        resolve(ok);  // same as "return ok;" if function is async  
    } else {
        reject("Error - failed promise"); // same as throw in async function
    }
});

p.then( ... )    // "then" is executed after await return back  
 .catch( ... );  // "catch" is actually catch part of try/catch block

If you use async functions what’s really behind is actually exactly the same as when you work with promises directly, just syntactically is more synchronous looking and much more readable.

Working with multiple async functions in parallel

If you need to execute more then one function in parallel, you need to use Promise.all( [ array of promises] ).

import fetch from "node-fetch";

// this is "promisified" timeout, can be used with await
function delay(ms: number): Promise<void> {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

async function first(): Promise<boolean> {
    try {
        await delay(10000); // 10 sec long task 
        console.log("first() finished after delay of 10 sec");
        return true;
    } catch (error) {
        throw(error);
    }
}

async function second(): Promise<boolean> {
    try {
        await delay(5000); // 5 sec long task 
        console.log("second() finished after delay of 5 sec");
        return true;
    } catch (error) {
        throw(error);
    }
}

async function parallel() {
    let isFirstFinished = false;
    let isSecondFinished = false;
    try {
        const start = (new Date()).getTime();

        [isFirstFinished, isSecondFinished] = await Promise.all([first(), second()]);

        const end = (new Date()).getTime();
        const diff = Math.round((end - start) / 1000);
        console.log(`end of parallel execution after ${diff} sec`);
    } catch (error) {
        console.log(error);
    }
}

parallel();

The parallel call is made in the line :

[isFirstFinished, isSecondFinished] = await Promise.all([first(), second()]);

Await in “parallel()” function will be called back after all promises will be resolved. The longest operation in array of promises define the maximum time spend here.

 

 

Setting up the Environment for Node.js and TypeScript

To be productive as a programmer the write/run/debug cycle should be as fast as possible.

Because I am a little spoiled by angular-cli setup for client side web development I search for similar experience in the server side. It means that transpiling the typescript to javascript and node server restarts should be automatic and with hassle free debugging.

Install prerequisites

To create basic node application in the current folder use this command :

npm init

Install additional features needed for the application:

npm install typescript --save-dev
npm install @types/node --save-dev
npm install nodemon --save-dev

If you wish to write with express framework you will need additional libs:

npm install express --save
npm install @types/express --save-dev

 

The structure of the project folder

Server application is running in completely different project (folder) as angular client application. To develop client and server I will run two instances of vscode editors, each open corresponding folder.

The server folder contains this base structure:

I created src sub-folder where all source files will reside. For now I put index.ts file in it.

Setup typescript compiler

Create tsconfig.json file in the root folder of the project :

{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "moduleResolution": "node",
        "noEmitOnError": true,
        "noImplicitAny": true,
        "experimentalDecorators": true,
        "sourceMap": true,
        "baseUrl": ".",
        "paths": {
            "*":[ 
                "src/types/*"
            ]
        },
        "sourceRoot": "src",
        "outDir": "dist"
    },
    "typeRoots": [
        "node_module/@types"
    ],
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "dist",
        "node_modules"
    ]
}

Compile and run the node application

Transpile (compile) typescript into javascript and watch any changes (use local tsconfig.json configuration):

tsc --watch

Start node with debug (inspect) and in auto restart mode (nodemon) :

nodemon --inspect ./dist/index.js --watch dist

Create combined npm command in package.json file

To specify multiple commands in the npm  under one script command, divide them with && character. Let’s create a “server” command:

"server": "tsc --watch | nodemon --inspect ./dist/index.js --watch dist",
  •  “tsc“: start typescript compiler in watch mode,
  •  “|” : pipe character allow to add aditional command in same script
  • nodemon“:  this command will start and restart “node” server after each detected change made by tsc compiler. Nodemon watch destination folder because it need to restart only after javascript files are changed (after transpile).

Before running nodemon for the first time in the project, you need to have compiled index.js at the required place (“dist folder”) so you need to compile at least first time the whole application manually (run tsc from the command line). After that initialization the watchers will watch for changes in source file automatically.

Run combined command from the terminal:

npm run server

Be aware of the small problem, if you ran the server inside vscode terminal window and then close the editor after that, without canceling the running process (with Ctrl+C), the node server will not automatically stop. You will need to find it between the running processes and stop it manually. To eliminate this obstacle create task and run combined command as task.

Tasks

To ease start and stop of “npm run server” command and to have better control over all started processes, create a task and a task runner for the “server” command.

if you do not have “.vscode” folder  and tasks.json file yet you create it with /Tasks/Configure tasks menu option.

Inside tasks.json file (in .vscode folder) define the command in that form (use version 0.1.0, the version 2.0.0 has problem with task termination on exiting editor):

{
    "version": "0.1.0",
    "isShellCommand": false,
    "showOutput": "always",
    "tasks": [
        {
            "taskName": "run server",
            "isBackground": true,
            "showOutput": "always",
            "promptOnClose": true,
            "command": "npm",
            "args": [
                "run", 
                "server"
            ]
        }
}

To run the task in vscode editor, open Tasks menu, open Run task and select run server task.

When you want to terminate the task, it means that stop will terminate the tsc instance and all node instances started by the “server” command.  From the Tasks menu select  Terminate task… .

If you close the editor with File/Exit , the program should also ask to terminate still running task.

If you omit “promptOnClose” setting you will not be interrupted at the exit from the editor and the processes will close as expected.

 

Keyboard shortcuts

Find command (F1)  “Open keyboard shortcuts file” and add definition to it.

// Place your key bindings in this file to overwrite the defaults
[
    {
        "key": "ctrl+t",
        "command": "workbench.action.tasks.terminate",
        "args": "run server"
    },
    {
        "key": "ctrl+r",
        "command": "workbench.action.tasks.runTask",
        "args": "run server"
    }
]

Press F1 and search for keyboard … The file is located in the Roaming folder in usual windows users location.

 

Create attach debuger command

To intercept debug breakpoints in code you need to attach to running node process with a debugger session in the vscode editor. Create new “attach” launch configuration in .vscode/launch.json file:

...
 "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "Attach by Process ID",
            "processId": "${command:PickProcess}",
            "outFiles": [
                "${workspaceRoot}/dist/**/*.js"
            ],
            "skipFiles": [
                "node_modules/**/*.js",
                "<node_internals>/**/*.js"
            ]
        },        
...

Start debugging with “Attach by Process ID” configuration, then select the running node server instance . You can use F5 key when staying in the source file.

Now execution should stop at defined breakpoints as expected.

Debugging doesn’t stop the tsc & nodemon processes ! In fact you are only attached to your running server process. If you detach from it, you will be able to work forward as you never dive into debugging.  If you change any typescript file and save the file, watchers will detect change and restart all processes. Though you will be automatically detached from the debugging session.

Sample index.ts file

The file is a simplest REST service example in express/node program written in typescript, needed to test compiling, debugging environment.

import  * as express from 'express';

const app: express.Express = express();

app.get('/', (req: express.Request, res: express.Response) => {

    let person = { name : 'Igor Babič' };

    person = Object.assign(person, req.query);

    res.json(person);
});

app.listen('3002',() => console.log('Server listening on port 3002'));

Final version of package.json file:

{
  "name": "wodia",
  "version": "1.0.0",
  "description": "Work Diary",
  "main": "dist/index.js",
  "scripts": {
    "server": "tsc --watch | nodemon --inspect --watch dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Igor Babic",
  "license": "MIT",
  "dependencies": {
    "express": "^4.15.4"
  },
  "devDependencies": {
    "@types/express": "^4.0.36",
    "@types/node": "^8.0.24",
    "nodemon": "^1.11.0",
    "typescript": "^2.5.1"
  }
}

Not sure if is important in this setup, but I use next workspace vscode settings:

{
    "search.exclude": {
        "**/node_modules": true,
        "**/dist": true
    },
    "typescript.referencesCodeLens.enabled": true,
    "tslint.ignoreDefinitionFiles": false,
    "tslint.autoFixOnSave": true,
    "tslint.exclude": "**/node_modules/**/*" 
}

And the user settings for vscode :

// Place your settings in this file to overwrite the default settings
{
    "editor.fontFamily": "Consolas, 'Courier New', monospace",
    "editor.fontSize": 14,
    "window.zoomLevel": 1,
    "workbench.colorTheme": "Default Dark+",
    "terminal.integrated.shell.windows": "C:\\Programs\\PortableGit-2.12.0\\bin\\bash.exe",
    "terminal.integrated.shellArgs.windows": ["--init-file", "c:\\Users\\igorb\\.bash_profile"],
    "git.enableSmartCommit": true,
    "workbench.startupEditor": "newUntitledFile",
    "editor.renderWhitespace": "none",
    "window.restoreFullscreen": true,
    "window.restoreWindows": "all"
}

Installed visual studio code extensions


Setup GIT BASH as internal terminal inside code

Download and install portable GIT Bash .

Create .bash_profile in /Users/_user_name_/ folder.

echo "RUN: .bash_profile | GIT: SSH agent | PATH: ./node_modules/.bin"
env=~/.ssh/agent.env

agent_load_env () { test -f "$env" && . "$env" >| /dev/null ; }

agent_start () {
    (umask 077; ssh-agent >| "$env")
    . "$env" >| /dev/null ; }

agent_load_env

# agent_run_state: 0=agent running w/ key; 1=agent w/o key; 2= agent not running
agent_run_state=$(ssh-add -l >| /dev/null 2>&1; echo $?)

if [ ! "$SSH_AUTH_SOCK" ] || [ $agent_run_state = 2 ]; then
    agent_start
    ssh-add
elif [ "$SSH_AUTH_SOCK" ] && [ $agent_run_state = 1 ]; then
    ssh-add
fi

unset env

export PATH="$PATH:./node_modules/.bin"

 

Add current project node modules binary folder to the path

Add at least “./node_modules/.bin/” folder to bash profile, to be able to call locally installed modules directly inside internal terminal window. The internal terminal window will open in the project root folder.

export PATH="$PATH:./node_modules/.bin"

To be able to use git bash terminal inside visual studio code we add the terminal setup command in user settings:

"terminal.integrated.shell.windows": "C:\\Programs\\PortableGit-2.12.0\\bin\\bash.exe", 
"terminal.integrated.shellArgs.windows": ["--init-file", "c:\\Users\\igorb\\.bash_profile"],

Don’t forget to define .bash_profile file in the shell arguments.

After everything is properly set up you will be able to use internal projects node programs directly from the command line:

How to setup git to run with ssh I described here.