Cube.js, the Open Source Dashboard Framework: Ultimate Guide

Cube.js, the Open Source Dashboard Framework: Ultimate Guide

<a href="https://cube.dev/" target="_blank">Cube.js</a>&nbsp;is an open source framework for building analytical web applications. It is primarily used to build internal business intelligence tools or to add customer-facing analytics to an existing application. In a majority of cases, the first step of building such an application is an analytics dashboard. It usually starts with—“let’s add an analytics dashboard to our admin panel.” Then, as it always happens in software development, things get more complicated, much more complicated.

Cube.js is an open source framework for building analytical web applications. It is primarily used to build internal business intelligence tools or to add customer-facing analytics to an existing application. In a majority of cases, the first step of building such an application is an analytics dashboard. It usually starts with—“let’s add an analytics dashboard to our admin panel.” Then, as it always happens in software development, things get more complicated, much more complicated.

When we started working on Cube.js, we wanted to build a tool, which is simple to start but scales easily in features, complexity, and data volume. Cube.js puts down a solid foundation for your future analytical system, whether it is a standalone application or embedded into the existing one.

You can think about this tutorial as “Cube.js 101.” I’ll walk you through the basic steps of designing the first dashboard from the database to visualizations.

The live demo of the final dashboard is available here. The full source code is on Github.

Architecture

The majority of modern web applications are built as a single-page application, where the frontend is separated from the backend. The backend also usually is split into multiple services, following a microservice architecture.

Cube.js embraces this approach. Conventionally you run Cube.js Backend as a service. It manages the connection to your database, including queries queue, caching, pre-aggregation, and more. It also exposes an API for your frontend app to build dashboards and other analytics features.

Backend

Analytics starts with the data and data resides in a database. That is the first thing we need to have in place. You most likely already have a database for your application and usually, it is just fine to use for analytics. Modern popular databases such as Postgres or MySQL are well suited for a simple analytical workload. By simple, I mean a data volume with less than 1 billion rows.

MongoDB is fine as well, the only thing you’ll need to add is MongoDB Connector for BI. It allows executing SQL code on top of your MongoDB data. It is free and can be easily downloaded from the MongoDB website. One more thing to keep in mind is replication. It is considered a bad practice to run analytics queries against your production database mostly because of the performance issues. Cube.js can dramatically reduce the amount of a database’s workload, but still, I’d recommend connecting to the replica.

To summarize— If you use Postgres or MySQL, just create a replica and we’re good to go. If you use MongoDB—download MongoDB Connector for BI and create a replica.

If you don’t have any data for the dashboard, you can load our sample e-commerce Postgres dataset.

$ curl http://cube.dev/downloads/ecom-dump.sql > ecom-dump.sql
$ createdb ecom
$ psql --dbname ecom -f ecom-dump.sql

Now, as we have data in the database, we’re ready to create the Cube.js Backend service. Run the following commands in your terminal:

$ npm install -g cubejs-cli
$ cubejs create dashboard-backend -d postgres

The commands above install Cube.js CLI and create a new service, configured to work with Postgres database.

Cube.js uses environment variables for configuration. It uses environment variables starting with CUBEJS_. To configure the connection to our database, we need to specify the db type and name. In the Cube.js project folder replace the contents of `.env` with the following:

CUBEJS_API_SECRET=SECRET
CUBEJS_DB_TYPE=postgres
CUBEJS_DB_NAME=ecom
Cube.js Data Schema

The next step is to create a Cube.js data schema. Cube.js uses the data schema to generate an SQL code, which will be executed in your database. The data schema is not a replacement for SQL. It is designed to make SQL reusable and give it a structure while preserving all of its power. Basic elements of the data schema are measures and dimensions.

Measure is referred to as quantitative data, such as the number of units sold, number of unique visits, profit, and so on.

Dimension is referred to as categorical data, such as state, gender, product name, or units of time (e.g., day, week, month).

Conventionally, schema files are located in the schema folder. Here is an example of the schema, which can be used to describe users’ data.

cube(`Users`, {
  sql: `SELECT * FROM users`,

measures: {
count: {
sql: id,
type: count
}
},

dimensions: {
city: {
sql: city,
type: string
},

signedUp: {
  sql: `created_at`,
  type: `time`
},

companyName: {
  sql: `company_name`,
  type: `string`
}

}
});

Now, with the above schema in place, we can send queries to the Cube.js backend about users’ data. Cube.js queries are plain javascript objects. Usually it has one or more measuresdimensions, and timeDimensions.

If we want to answer the question “Where are our users based?” we can send the following query to the Cube.js:

{
measures: ['Users.count'],
dimensions: ['Users.city']
}

Cube.js will generate the required SQL based on the schema, execute it, and send the result back.

Let’s create a slightly more complicated query. We can add a timeDimensions to see how the ratio of different cities has been changing every month during the last year. To do this, we will add a signedUp time dimension, group it by monthly, and filter only last year’s signups.

{
measures: ['Users.count'],
dimensions: ['Users.city'],
timeDimensions: [{
dimension: 'Users.signedUp',
granularity: 'month',
dateRange: ['2018-01-31', '2018-12-31']
}]
}

Cube.js can generate simple schemas based on your database’s tables. Let’s generate schemas we need for our dashboard and then start a dev server.

$ cubejs generate -t users,orders
$ npm run dev

You can inspect generated schemas and send test queries by opening a development playground at http://localhost:4000.

Frontend

We’ll build our frontend and dashboard with React, using the Cube.js React client. But you can use any framework or just vanilla javascript to build a frontend with Cube.js. This tutorial shows you how to build a dashboard in pure javascript. We’ll set everything up using Create React App, which is officially supported by the React team. It packages all the dependencies for React app and makes it easy to get started with a new project. Run the following commands in your terminal:

$ npx create-react-app dashboard-frontend
$ cd cubejs-dashboard
$ npm start

The last line starts a server on port 3000 and opens your web browser at http://localhost:3000.

We’ll build our UI with Reactstrap, which is a React wrapper for Bootstrap 4. Install Reactstrap and Bootstrap from NPM. Reactstrap does not include Bootstrap CSS, so this needs to be installed separately:

$ npm install reactstrap bootstrap --save

Import Bootstrap CSS in the src/index.js file before importing ./index.css:

import 'bootstrap/dist/css/bootstrap.min.css';

Now we are ready to use the Reactstrap components.

The next step is to install Cube.js client to fetch the data from the server and our visualization library to display it. For this tutorial, we’re going to use Recharts. Cube.js is visualization agnostic, meaning you can use any library you want. We’ll also use moment and numeral to nicely format dates and numbers.

$ npm install --save @cubejs-client/core @cubejs-client/react recharts moment numeral

Finally, we’re done with dependencies, so let’s go ahead and create our first chart. Replace the contents of src/App.js with the following:

import React, { Component } from "react";
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer
} from "recharts";
import cubejs from "@cubejs-client/core";
import moment from "moment";
import { QueryRenderer } from "@cubejs-client/react";

const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, {
apiUrl: process.env.REACT_APP_API_URL
});

const dateFormatter = item => moment(item).format("MMM YY");

class App extends Component {
render() {
return (
<QueryRenderer
query={{
measures: ["Orders.count"],
timeDimensions: [
{
dimension: "Orders.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
if (!resultSet) {
return "Loading...";
}

     return (
       &lt;ResponsiveContainer width="100%" height={300}&gt;
         &lt;BarChart data={resultSet.chartPivot()}&gt;
           &lt;XAxis dataKey="x" tickFormatter={dateFormatter} /&gt;
           &lt;YAxis /&gt;
           &lt;Tooltip labelFormatter={dateFormatter} /&gt;
           &lt;Bar dataKey="Orders.count" fill="rgba(106, 110, 229)" /&gt;
         &lt;/BarChart&gt;
       &lt;/ResponsiveContainer&gt;
     );
   }}
 /&gt;

);
}
}

export default App;

You can check out this example in the CodeSandbox below.

Let’s look deeper at how we load data and draw the chart.

First, we’re initializing the Cube.js API client:

const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, {
apiUrl: process.env.REACT_APP_API_URL
});

Here we are using the REACT_APP_CUBEJS_TOKEN and REACT_APP_API_URL environment variables. Create React App automatically loads your env variables from the .env file if they start with REACT_APP_. The Cube.js backend will print the development API token during the startup.

Create a .env file with the correct credentials.

REACT_APP_CUBEJS_TOKEN=COPY-API-TOKEN-FROM-TERMINAL-OUTPUT
REACT_APP_API_URL=http://localhost:4000/cubejs-api/v1

Next, we are using the QueryRenderer Cube.js React Component to load Orders data.

<QueryRenderer
query={{
measures: ["Orders.count"],
timeDimensions: [
{
dimension: "Orders.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
// Render result
}}
/>

QueryRenderer performs an API request to the Cube.js backend and uses the render props technique to let you render the result however you want to. We’ve already covered the query format above, but in case you want to refresh—here is the query format full reference.

The render parameter of QueryRenderer is a function of the type ({error, resultSet, isLoading}) => React.Node. The output of this function will be rendered by the QueryRenderer. A resultSet is an object containing data obtained from the query. If this object is not defined, it means that the data is still being fetched.

resultSet provides multiple methods for data manipulation, but in our case, we need just the chartPivot method, which returns data in a format expected by Recharts.

We’ll plot the Orders data as a bar chart inside a responsive container.

if (!resultSet) {
return "Loading...";
}

return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={resultSet.chartPivot()}>
<XAxis dataKey="x" tickFormatter={dateFormatter} />
<YAxis />
<Tooltip labelFormatter={dateFormatter} />
<Bar dataKey="Orders.count" fill="rgba(106, 110, 229)" />
</BarChart>
</ResponsiveContainer>
);

Building a Dashboard

We learned how to build a single chart with Cube.js and Recharts, and we are now ready to start building the whole dashboard. There are some best practices regarding designing the layout of the dashboard. The common practice is to put the most important and high-level metrics on the top as single value charts, sometimes called KPIs, and then list the relevant breakdowns of those metrics.

Here is the screenshot of our final dashboard with KPIs on top followed by bar and line charts.

First, let’s refactor our chart and extract the common code into a reusable <Chart /> component. Create an src/Chart.js file the following content:

import React from "react";
import { Card, CardTitle, CardBody, CardText } from "reactstrap";
import { QueryRenderer } from "@cubejs-client/react";

const Chart = ({ cubejsApi, title, query, render }) => (
<Card>
<CardBody>
<CardTitle tag="h5">{title}</CardTitle>
<CardText>
<QueryRenderer
query={query}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
if (!resultSet) {
return <div className="loader" />;
}

       return render(resultSet);
     }}
   /&gt;
 &lt;/CardText&gt;

</CardBody>
</Card>
);

export default Chart;

Next, let’s use this component to create the dashboard. Replace the content of src/App.js with the following:

import React, { Component } from "react";
import { Container, Row, Col } from "reactstrap";
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
Legend,
BarChart,
Bar
} from "recharts";
import moment from "moment";
import numeral from "numeral";
import cubejs from "@cubejs-client/core";
import Chart from "./Chart.js";

const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, {
apiUrl: process.env.REACT_APP_API_URL
});
const numberFormatter = item => numeral(item).format("0,0");
const dateFormatter = item => moment(item).format("MMM YY");

const renderSingleValue = (resultSet, key) => (
<h1 height={300}>{numberFormatter(resultSet.chartPivot()[0][key])}</h1>
);

class App extends Component {
render() {
return (
<Container fluid>
<Row>
<Col sm="4">
<Chart
cubejsApi={cubejsApi}
title="Total Users"
query={{ measures: ["Users.count"] }}
render={resultSet => renderSingleValue(resultSet, "Users.count")}
/>
</Col>
<Col sm="4">
<Chart
cubejsApi={cubejsApi}
title="Total Orders"
query={{ measures: ["Orders.count"] }}
render={resultSet => renderSingleValue(resultSet, "Orders.count")}
/>
</Col>
<Col sm="4">
<Chart
cubejsApi={cubejsApi}
title="Shipped Orders"
query={{
measures: ["Orders.count"],
filters: [
{
dimension: "Orders.status",
operator: "equals",
values: ["shipped"]
}
]
}}
render={resultSet => renderSingleValue(resultSet, "Orders.count")}
/>
</Col>
</Row>
<br />
<br />
<Row>
<Col sm="6">
<Chart
cubejsApi={cubejsApi}
title="New Users Over Time"
query={{
measures: ["Users.count"],
timeDimensions: [
{
dimension: "Users.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
render={resultSet => (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={resultSet.chartPivot()}>
<XAxis dataKey="category" tickFormatter={dateFormatter} />
<YAxis tickFormatter={numberFormatter} />
<Tooltip labelFormatter={dateFormatter} />
<Area
type="monotone"
dataKey="Users.count"
name="Users"
stroke="rgb(106, 110, 229)"
fill="rgba(106, 110, 229, .16)"
/>
</AreaChart>
</ResponsiveContainer>
)}
/>
</Col>
<Col sm="6">
<Chart
cubejsApi={cubejsApi}
title="Orders by Status Over time"
query={{
measures: ["Orders.count"],
dimensions: ["Orders.status"],
timeDimensions: [
{
dimension: "Orders.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
render={resultSet => {
return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={resultSet.chartPivot()}>
<XAxis tickFormatter={dateFormatter} dataKey="x" />
<YAxis tickFormatter={numberFormatter} />
<Bar
stackId="a"
dataKey="shipped, Orders.count"
name="Shipped"
fill="#7DB3FF"
/>
<Bar
stackId="a"
dataKey="processing, Orders.count"
name="Processing"
fill="#49457B"
/>
<Bar
stackId="a"
dataKey="completed, Orders.count"
name="Completed"
fill="#FF7C78"
/>
<Legend />
<Tooltip />
</BarChart>
</ResponsiveContainer>
);
}}
/>
</Col>
</Row>
</Container>
);
}
}

export default App;

That is enough to build our first dashboard. Give it a try in the CodeSanbox below.

Next Steps

We’ve built a simple proof of concept dashboard with Cube.js. You can check the live demo here. The full source code is available on Github.

To learn more about Cube.js backend deployment, you can refer to the deployment documentation. Also, here you can find more tutorials on a variety of topics.

And join our Slack Community! It is a great to place to get help and stay up to date with new releases.

Top 7 Most Popular Node.js Frameworks You Should Know

Top 7 Most Popular Node.js Frameworks You Should Know

Node.js is an open-source, cross-platform, runtime environment that allows developers to run JavaScript outside of a browser. In this post, you'll see top 7 of the most popular Node frameworks at this point in time (ranked from high to low by GitHub stars).

Node.js is an open-source, cross-platform, runtime environment that allows developers to run JavaScript outside of a browser.

One of the main advantages of Node is that it enables developers to use JavaScript on both the front-end and the back-end of an application. This not only makes the source code of any app cleaner and more consistent, but it significantly speeds up app development too, as developers only need to use one language.

Node is fast, scalable, and easy to get started with. Its default package manager is npm, which means it also sports the largest ecosystem of open-source libraries. Node is used by companies such as NASA, Uber, Netflix, and Walmart.

But Node doesn't come alone. It comes with a plethora of frameworks. A Node framework can be pictured as the external scaffolding that you can build your app in. These frameworks are built on top of Node and extend the technology's functionality, mostly by making apps easier to prototype and develop, while also making them faster and more scalable.

Below are 7of the most popular Node frameworks at this point in time (ranked from high to low by GitHub stars).

Express

With over 43,000 GitHub stars, Express is the most popular Node framework. It brands itself as a fast, unopinionated, and minimalist framework. Express acts as middleware: it helps set up and configure routes to send and receive requests between the front-end and the database of an app.

Express provides lightweight, powerful tools for HTTP servers. It's a great framework for single-page apps, websites, hybrids, or public HTTP APIs. It supports over fourteen different template engines, so developers aren't forced into any specific ORM.

Meteor

Meteor is a full-stack JavaScript platform. It allows developers to build real-time web apps, i.e. apps where code changes are pushed to all browsers and devices in real-time. Additionally, servers send data over the wire, instead of HTML. The client renders the data.

The project has over 41,000 GitHub stars and is built to power large projects. Meteor is used by companies such as Mazda, Honeywell, Qualcomm, and IKEA. It has excellent documentation and a strong community behind it.

Koa

Koa is built by the same team that built Express. It uses ES6 methods that allow developers to work without callbacks. Developers also have more control over error-handling. Koa has no middleware within its core, which means that developers have more control over configuration, but which means that traditional Node middleware (e.g. req, res, next) won't work with Koa.

Koa already has over 26,000 GitHub stars. The Express developers built Koa because they wanted a lighter framework that was more expressive and more robust than Express. You can find out more about the differences between Koa and Express here.

Sails

Sails is a real-time, MVC framework for Node that's built on Express. It supports auto-generated REST APIs and comes with an easy WebSocket integration.

The project has over 20,000 stars on GitHub and is compatible with almost all databases (MySQL, MongoDB, PostgreSQL, Redis). It's also compatible with most front-end technologies (Angular, iOS, Android, React, and even Windows Phone).

Nest

Nest has over 15,000 GitHub stars. It uses progressive JavaScript and is built with TypeScript, which means it comes with strong typing. It combines elements of object-oriented programming, functional programming, and functional reactive programming.

Nest is packaged in such a way it serves as a complete development kit for writing enterprise-level apps. The framework uses Express, but is compatible with a wide range of other libraries.

LoopBack

LoopBack is a framework that allows developers to quickly create REST APIs. It has an easy-to-use CLI wizard and allows developers to create models either on their schema or dynamically. It also has a built-in API explorer.

LoopBack has over 12,000 GitHub stars and is used by companies such as GoDaddy, Symantec, and the Bank of America. It's compatible with many REST services and a wide variety of databases (MongoDB, Oracle, MySQL, PostgreSQL).

Hapi

Similar to Express, hapi serves data by intermediating between server-side and client-side. As such, it's can serve as a substitute for Express. Hapi allows developers to focus on writing reusable app logic in a modular and prescriptive fashion.

The project has over 11,000 GitHub stars. It has built-in support for input validation, caching, authentication, and more. Hapi was originally developed to handle all of Walmart's mobile traffic during Black Friday.

20. Node.js Lessons. Data Streams in Node.JS, fs.ReadStream

20. Node.js Lessons. Data Streams in Node.JS, fs.ReadStream

20. Node.js Lessons. Data Streams in Node.JS, fs.ReadStream

Hey all! Our topic for today is Data Streams In Node.js. We will try to learn all the aspects in details for the reason it turns out that on the one hand, common browser JavaScript development lack streams. And on the other hand, knowing and understanding stream principles is necessary for seamless server development because a stream is a versatile way of work with data sources universally used.

We can define two general stream types. The first one is

stream.Readable

It is a built-in class providing streams for reading. Generally, this type itself is never used, while its descendants are quite popular – in particular, we use fs.ReadStream to read from a file. To read from a visitor’s request for its handling, there is a special object familiar to us under its name req, which is the first argument of a request handler.

Stream.Writable

It is a versatile writing method. The very stream.Writable is rarely used, but its descendants – fs.WriteStream and res – are quite common.

There are some other stream types, but the most popular are these two and their variations.

The best way to understand streams is to see how they work in practice. So, right now we’ll start with using fs.ReadStream for reading a file. Let us create a file fs.js:

var fs = require('fs');
 
// fs.ReadStream nherits from stream.Readable
var stream = new fs.ReadStream(__filename);
 
stream.on('readable', function() {
    var data = stream.read();
    console.log(data);
});
 
stream.on('end', function() {
    console.log("THE END");
});

So, we get the module fs connected and create a stream:

var fs = require('fs');
 
var stream = new fs.ReadStream(__filename);

Stream is a JavaScript object receiving information about our resource – in our case, it is a path to the file (__filename) – which can work with this resource. fs.ReadStream implements a standard reading interface described in the stream.Readable class. Let us have a detailed look.

When a stream object new stream.Readable is created, it gets connected to the data source, which is file in our case, and tries to start reading from it. Once it has read something, it imitates the event readable. This event means that all the data have been computed and are contained within an inner stream buffer that can be received using the call read(). Then we can do something with data and wait for the next readable. This cycle will be the same.

Whenever the data source gets empty (however, there are certain sources that never get empty – for example, a random data generator), the file size is limited, so we will have the end, event in the very end meaning there will be no data anymore. Moreover we can call the method destroy() at any step of working with the stream. This method means we do not need the stream anymore and it can be closed, as well as the respective data sources and everything can be cleaned up.

So, let us refer to the original code. Here we create ReadStream, and it immediately wants to open up a file:

var stream = new fs.ReadStream(__filename);

but in our case it doesn’t necessarily mean the same string because any input/output-related operation is performed through libUV. At the same time, libUV has a structure that enables all synchronous input/output handlers to get implemented during the next event loop iteration, or once the current JavaScripthas finished its work. It means, we can seamlessly use all handlers knowing that they will be installed prior to the moment the first data fragment gets read. Launch fs.js.

Look at what has appeared in the console. The first one was the event readable. It outputted data. Right now it is an ordinary buffer, but we can transform it to the string by specifying the coding directly upon the stream opening.

var stream = new fs.ReadStream(__filename, {encoding: 'utf-8'});

Thus, the modification will be automatic. When a file ends, the event endoutputs THE END in the console. Here the file ended almost immediately because it was small at the moment. Let us modify our example a little bit by making a file big.html out of the current file contained in the current directory. Download this HTML file from our repository together with the other lesson materials.

Launch it. The file big.html is big, so the event readable has been initiated several times, and every time we received another data fragment as a buffer. So, let us calculate its length:

var fs = require('fs');
 
// fs.ReadStream nherits from stream.Readable
var stream = new fs.ReadStream("big.html");
 
stream.on('readable', function() {
    var data = stream.read();
    if (data){
        console.log(data.length);
    }
    else {
        console.log('data is null')
    }
});
 
stream.on('end', function() {
    console.log("THE END");
});

Get it launched. These numbers are the read file fragment length. When a stream opens a file, it reads only its part, but not the whole file, and inserts it into its internal variable. The maximum size is exactly 64 KB. Until we call stream.Read, it won’t read further. Once we’ve received the data, the internal buffer cleans up and can be ready for reading another abstract, etc. The last abstract length is 60,959 B. This example has vividly demonstrated the key advantages of stream usage. They help save memory. Whatever is the size our big file, we still handle only its small part at a moment. The second less obvious advantage is versatility of its interface. Here we use the stream ReadStream from the file. But we can replace it any time by any stream from our resource:

var stream = new OurStream("our resource");

It won’t need any change of the left code because streams are, first of all, our interface. So, it means, if theoretically our stream performs all needed events and methods – in particular, it inherits from stream.Readable – everything should be ok. Of course, it will happen only if we do not use any special abilities that only file streams have got. To be more specific, the stream fs.ReadStream has extra events

Here we can see a draft exactly for fs.ReadStream, new events are colored in red. First, it is a file opening, while the last event is its closure. Focus your attention on the fact that if a file is read till its end, the end event occurs followed by close. And if a file is not entirely read – for instance, because of an error or upon calling the destroy method – there will be no end because the file hasn’t been ended. But the event close is always ensured upon a file closure.

Finally, our last, but not least detail here is error handling. So, let us see what will happen, if there is no file.

var stream = new fs.ReadStream("noFile.html");

So, I get it launched. Oops! It crashed! Pay your attention to the fact the streams inherit from EventEmitter. If an error occurs, the whole Node.js process fails. It happens if an error of this kind does not have any handler. That’s why if we do not want our Node.js to fail because of an exception, we should install a handler:

var fs = require('fs');
 
// fs.ReadStream nherits from stream.Readable
var stream = new fs.ReadStream("noFile.html");
 
stream.on('readable', function() {
    var data = stream.read();
    if (data){
        console.log(data.length);
    }
    else {
        console.log('data is null')
    }
});
 
stream.on('error', function(err) {
    if (err.code == 'ENOENT') {
        console.log("File not Found");
    } else {
        console.error(err);
    }
});

So, we use streams to work with data sources in Node.js. Here we’ve analyzed a basic scheme, according to which they work, and a particular example – fs.ReadStream – that can read from a file.

This lesson’s coding can be found in our repository.