Photo by Kym Ellis on Unsplash

Getting started with Nodejs and flow, part 2

Artem Riasnianskyi
5 min readApr 9, 2018

Almost a year passed since my article about getting started with Nodejs and flow types. Quite much progress with flow ecosystem and flow itself happened. In this article we will cover more advanced topics and some enhancements:

  • Using of flow-node for development
  • Using typed Nodejs core API
  • Coverage for flow types
  • Integration with linter
  • Plugins for VSCode and Atom

Development with flow-node

If you install globally package flow-remove-types you will get executable flow-node. Which is basically run flow remove types for each file before running it. This is quite handy if you want to play around with types. But for production code, I recommend still use transpilation.

Command to install flow-node:

npm i -g flow-remove-types

By default, it will transpile only files with //@flow comment in the beginning. To avoid this behavior you need to add -a flag before script path you what to run. We will use flow-node in the next section.

Using typed Nodejs APIs

Importing types for system API could look a bit tricky since Nodejs doesn’t officially support ES2016 modules.
Let’s create a new file src/node-test.js where we will test imports. What we need to consider:

  1. Types are not objects
  2. You can’t have type and object with the same name.

We will create a simple function which will print the owner of the current directory. First let’s import our types from flow:

import typeof util from "util";
import typeof fs from "fs";
import type { Stats } from "fs";

Now let’s import API modules:

const { promisify }: util = require("util");
const { stat }: fs = require("fs");

As you can see we’re not using these modules by names, we use deconstruction in order to get functions we interested in. Because we already have fs and utils names in scope.

We will use promisify function to use fs asynchronously:

const getStats: (path: string) => Promise<Stats> = promisify(stat);

We used generic here to show which type will be returned by promise.
Now we’re able to write our main function:

async function main() {
const stats: Stats = await getStats(".");
const text = `This directory is owned by ${stats.uid}`;
console.log(text);
return text;
}
main();

And now we can run this file with flow-node command:

flow-node -a ./src/node-test.js

And you will get something like: “This directory is owned by 502”

Adding coverage

Because flow doesn’t enforce you to cover every file you have with types we need to care about coverage by ourselves. Luckily there is a special tool for it. Let’s install it with the command:

npm i flow-coverage-report

And let’s add a new script to our `package.json` file:

"flow:coverage": "flow-coverage-report --config .flowcoverage.json",

It will run coverage check based on the config. We need to create this config. Create an empty file with name .flowcoverage.json and paste there:

{
"concurrentFiles": 1,
"excludeGlob": ["node_modules/**", "flow-typed/**"],
"includeGlob": ["src/**/*.js"],
"threshold": 100,
"type": ["text", "html"]
}

In this config file, we defined which file to check, which to skip and we put our threshold up to 100% — so all files should be fully covered by flow types.

Let’s run it and check:

npm run flow:coverage 

It will fail the check because some of our files are not fully covered. Let’s check what we can do. One of the coverage reporters was html, so we can open it from open ./flow-coverage/index.html.

As we can see, we’re missing type annotation for text variable and some issue with main function.
Let’s fix it by adding proper types:

async function main(): Promise<string> {
const stats: Stats = await getStats(".");
const text: string = `This directory is owned by ${stats.uid}`;
console.log(text);
return text;
}

Run again this command:

npm run flow:coverage 

Now we have 100% coverage for src/node-test.js file.

Linting

If you use ESLint you need to install babel, so eslint will be able to parse javascript files with flow annotations. For additional rules related to flow code, you can also install a plugin. Let’s do it all. Run the command:

npm i -SD eslint babel-eslint eslint-plugin-flowtype

And let’s create our config file (with name .eslintrc):

{
"parser": "babel-eslint",
"extends": [
"plugin:flowtype/recommended"
],
"plugins": [
"flowtype"
],
"rules": {
"flowtype/no-types-missing-file-annotation": 0
}
}

You can check the list of available rules here ___LINK___ we will use recommended preset, except one rule related to flow command in file first lines. Since we use flow for all files we don’t need this rule.

Now let’s add new script command to package.json file:

 "lint": "eslint ./src",

It will lint all files from src folder.

Export/Import types

We have our main function. But we can’t reuse this function in any other file. Let’s fix it.
First, replace calling main function with this snippet:

module.exports = main;
export type Main = () => Promise<string>;

Here we exported function with Nodejs require system and exported type with flow. Now we can use it in other files like this:

import type { Main } from './node-api-example';
const main: Main = require('./node-api-example');

Firstly we import our type and then applying this type to an actual import via require.

Editors integration

So far we used the flow only as command line tool. But it also integrates quite nicely into code editors. It provides nice features like autocomplete, go to definition, on edit validation and so on.

VS Code

By default, VSCode uses typescript syntax analysis for .js files — if you try to use it with flow annotation it will show errors. We need to disable this feature: go to packages and search for @buildin typescript and then disable for the workspace.

Now you can install this plugin https://github.com/flowtype/flow-for-vscode#setup
After installation you need to enable few settings:
"flow.useNPMPackagedFlow": "true" and "flow.runOnAllFiles: "true"Now it will use our local flow binary and will run check on all files (without //@flow comment)

If you want to use debugger you need to add following property into launch.json file:

"runtimeArgs": ["-r", "flow-remove-types/register"]

Atom

For Atom editor process is easier. You just need to install plugin ide-flowtype. After installation tick the setting to use local binary.

The final code you can find in the repo https://github.com/asci/flow-node-boilerplate

Final words

I hope this small tutorial was useful for you. If you have any questions you can ask in the comments section.

Cheers!☕

--

--