Akash Mittal I am a software engineer and a die-hard animal lover. My life's goal is to create a mini jungle with a dispensary for stray animals who get diseased or injured.

Ethereum blockchain development using Web3.js

22 min read 6396

Web.JS and Ethereum Logos

Blockchain is a brilliant technology that has gained the attention of corporations, tech leaders, and developers all around the world. A world of cryptocurrencies emerged out of a new hope for digital payments, and blockchain makes it all possible.

In this comprehensive tutorial, we’ll show you how to develop a blockchain app using Web3.js. We’ll also review the basics of Ethereum and decentralized blockchain in general and introduce key concepts to help you get started on your blockchain development journey.

Here’s what we’ll cover:

What is blockchain?

Let’s try to understand blockchain, in the simplest possible terms, by breaking down its etymology. Blockchain is a kind of database that stores information in blocks and then connect those blocks together with a chain. You can think of it as a link list where consecutive blocks are connected with their unique hashes.

Cryptocurrency uses the decentralized blockchain, which means the database is spread among a number of devices so that no single party owns it but all of them hold it. All transactions are immutable, meaning they can’t be changed once recorded. We’ll learn about transactions, smart contracts, coins, tokens and lot more in this article.

Decentralized blockchain vs. client/server network

A decentralized blockchain is a trustless system where there is no need to trust. That means if you are the part of network, you will hold a copy of the database yourself. It’s a peer-to-peer network where all the peers are connected with each other. The data is immutable and can’t be changed. But new data can be added.

On the other hand, the client-server configuration requires handshaking. The data is under the authority of a server or group of servers. They can change it whenever they want.

Since blockchain is immutable, it is great for keeping data secure from changes. It can be used for contracts, ledgers, marksheets, degrees, licenses, and even voting in elections. Keep in mind, however, that the data is public and anybody can access it, so you shouldn’t load confidential data on public chains. Blockchain technology is better suited for information that is meant to be open and transparent.

Decentralized blockchain is quite secure compared to centralized servers. First, everything is highly encrypted with the SHA algorithm. Second, data is immutable and connected through the chain. If there is an attempt to change the data, the chain will break and whole transaction will be nulled.

For example, if a bad actor were to change the block, they would need to change the connected block as well because it holds the hash of the vulnerable block. If this connected block were updated with the hash of a changed block, its hash would change too, which would require a change in further connected blocks.

In other words, altering a single datum in any block requires all the blocks to be changed. If an incredibly persistent hacker were to somehow accomplish this, even then, the correct data would be persisted over thousands of peer nodes around the world and they would mark the faulty data invalid. The bad actor would need to hack thousands of nodes and change the data everywhere at the same time, which is nearly impossible.

Ethereum
Source: https://iotbl.blogspot.com/2017/03/ethereum-and-blockchain-2.html

What is Ethereum?

Ethereum is a decentralized blockchain that supports smart contracts. Smart contracts are the byte codes written in Solidity language and compiled on Ethereum Virtual Machines (EVM). Due to these smart contracts, Ethereum allows external apps to build and run on its network.

The Ethereum blockchain runs a famous cryptocurrency known as Ether. Every transaction on this chain requires a gas fee to be paid in Ether.

A lot of decentralized finance (DeFi) apps are built on Ethereum. It is free from all the financial transaction intermediaries such as brokerage, exchange, banks, etc. It only costs a gas fee, which is not dependent on the amount. This gas fee is required to keep the network running.

Apart from Ether coins, many tokens are built over Ethereum using the ERC-20 protocol. This protocol defines all the functions that are required to run a token into the market. A token can represent anything, like a lottery ticket, currency, company’s share etc. That’s why startups are creating their own tokens and circulating in the market on exchanges such as Coinbase, Binance, Gate.io, etc.

What is Web3.js?

Web3.js is a JavaScript library that talks over Ethereum node. This could be a locally deployed Ethereum network or the live chain. It can be used to access information about tokens and Ether coin.

You can also deploy your own application and access using Web3. This library enables you to connect your JavaScript-based frontend to the Ethereum network using HTTP, IPC, and WebSockets.

Accessing the Ethereum network using Infura

Web3 can be used to talk with the Ethereum node, but it needs a node to talk to. Just like when you want to connect to a torrent network, you need torrent client like BitTorrent. Here we need a running peer. Blockchain is a peer-to-peer network, so to connect with the network, you either need to connect to a peer or become a peer yourself.

To become a peer node, you can use Geth, also known as Go Ethereum. Geth is an open-source library written in Go language for implementing the Ethereum protocol. After running it on your system, you can connect your JavaScript app with the live network using Web3 by listening it to the localhost.

If you don’t want to run node, you can connect with another peer who is running the network. Infura is one of them. You can create a free account, create a new project, and get the API key.

Infura provides five endpoints: one for production and four for test networks. The production endpoint is Mainnet and the test endpoints are Ropsten, Kovan, Rinkeby, and goerli.

Testing Infura

Details Page

After getting the mainnet endpoint, we can connect with live the Ethereum network using Web3.js.



If you want to run your own Ethereum network locally, you can use Ganache. Ganache is not a live Ethereum network, but a clone that you own. This enables you to test out the protocol easily and without spending real money in gas fees. You won’t have access to any tokens because they are on a production chain and not on your own. However, Ether coin is implemented and it provides 10 accounts with 100 ether each.

Ganache Blockchain

You can see that the RPC server is running at 7545 port on localhost. You can configure the gas price and gas limit. Also, a few accounts are provided by default with 100 ETH.

Installing and running Web3.js

Since Web3.js is a JavaScript library, you can include it in your webpages or in any JS-based framework. To test the functionality, we’re going to run it on a Node.js client.

Install Web3.js using this command:

npm install web3

After installing Web3.js, open your Node console and load it in a variable:

var Web3 = require('web3')

Now, let’s check what this variable holds:

Variable

Fromdecimal

Continued

This object shows all the functions that we can access through Web3.js. But Ethereum-specific functions are not yet available because we are not connected with the chain. We need to instantiate it with the Infura mainnet endpoint.

var web3 = new Web3('https://mainnet.infura.io/v3/b583160797e24b88a643ad9a38b0f5aa')

Now if you check web3, you will find a huge list of functions. These functions provide all the information related to the Ethereum chain network.

Web3 {
  currentProvider: [Getter/Setter],
  _requestManager: RequestManager {
    provider: HttpProvider {
      withCredentials: false,
      timeout: 0,
      headers: undefined,
      agent: undefined,
      connected: false,
      host: 'https://mainnet.infura.io/v3/b583160797e24b88a643ad9a38b0f5aa',
      httpsAgent: [Agent]
    },
    providers: {
      WebsocketProvider: [Function: WebsocketProvider],
      HttpProvider: [Function: HttpProvider],
      IpcProvider: [Function: IpcProvider]
    },
    subscriptions: Map(0) {}
  },
  givenProvider: null,
  providers: {
    WebsocketProvider: [Function: WebsocketProvider],
    HttpProvider: [Function: HttpProvider],
    IpcProvider: [Function: IpcProvider]
  },
  _provider: HttpProvider {
    withCredentials: false,
    timeout: 0,
    headers: undefined,
    agent: undefined,
    connected: false,
    host: 'https://mainnet.infura.io/v3/b583160797e24b88a643ad9a38b0f5aa',
    httpsAgent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object],
      requests: {},
      sockets: {},
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: true,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 0,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    }
  },
  setProvider: [Function (anonymous)],
  setRequestManager: [Function (anonymous)],
  BatchRequest: [Function: bound Batch],
  extend: [Function: ex] {
    formatters: {
      inputDefaultBlockNumberFormatter: [Function: inputDefaultBlockNumberFormatter],
      inputBlockNumberFormatter: [Function: inputBlockNumberFormatter],
      inputCallFormatter: [Function: inputCallFormatter],
      inputTransactionFormatter: [Function: inputTransactionFormatter],
      inputAddressFormatter: [Function: inputAddressFormatter],
      inputPostFormatter: [Function: inputPostFormatter],
      inputLogFormatter: [Function: inputLogFormatter],
      inputSignFormatter: [Function: inputSignFormatter],
      inputStorageKeysFormatter: [Function: inputStorageKeysFormatter],
      outputProofFormatter: [Function: outputProofFormatter],
      outputBigNumberFormatter: [Function: outputBigNumberFormatter],
      outputTransactionFormatter: [Function: outputTransactionFormatter],
      outputTransactionReceiptFormatter: [Function: outputTransactionReceiptFormatter],
      outputBlockFormatter: [Function: outputBlockFormatter],
      outputLogFormatter: [Function: outputLogFormatter],
      outputPostFormatter: [Function: outputPostFormatter],
      outputSyncingFormatter: [Function: outputSyncingFormatter]
    },
    utils: {
      _fireError: [Function: _fireError],
      _jsonInterfaceMethodToString: [Function: _jsonInterfaceMethodToString],
      _flattenTypes: [Function: _flattenTypes],
      randomHex: [Function: randomHex],
      BN: [Function],
      isBN: [Function: isBN],
      isBigNumber: [Function: isBigNumber],
      isHex: [Function: isHex],
      isHexStrict: [Function: isHexStrict],
      sha3: [Function],
      sha3Raw: [Function: sha3Raw],
      keccak256: [Function],
      soliditySha3: [Function: soliditySha3],
      soliditySha3Raw: [Function: soliditySha3Raw],
      encodePacked: [Function: encodePacked],
      isAddress: [Function: isAddress],
      checkAddressChecksum: [Function: checkAddressChecksum],
      toChecksumAddress: [Function: toChecksumAddress],
      toHex: [Function: toHex],
      toBN: [Function: toBN],
      bytesToHex: [Function: bytesToHex],
      hexToBytes: [Function: hexToBytes],
      hexToNumberString: [Function: hexToNumberString],
      hexToNumber: [Function: hexToNumber],
      toDecimal: [Function: hexToNumber],
      numberToHex: [Function: numberToHex],
      fromDecimal: [Function: numberToHex],
      hexToUtf8: [Function: hexToUtf8],
      hexToString: [Function: hexToUtf8],
      toUtf8: [Function: hexToUtf8],
      stripHexPrefix: [Function: stripHexPrefix],
      utf8ToHex: [Function: utf8ToHex],
      stringToHex: [Function: utf8ToHex],
      fromUtf8: [Function: utf8ToHex],
      hexToAscii: [Function: hexToAscii],
      toAscii: [Function: hexToAscii],
      asciiToHex: [Function: asciiToHex],
      fromAscii: [Function: asciiToHex],
      unitMap: [Object],
      toWei: [Function: toWei],
      fromWei: [Function: fromWei],
      padLeft: [Function: leftPad],
      leftPad: [Function: leftPad],
      padRight: [Function: rightPad],
      rightPad: [Function: rightPad],
      toTwosComplement: [Function: toTwosComplement],
      isBloom: [Function: isBloom],
      isUserEthereumAddressInBloom: [Function: isUserEthereumAddressInBloom],
      isContractAddressInBloom: [Function: isContractAddressInBloom],
      isTopic: [Function: isTopic],
      isTopicInBloom: [Function: isTopicInBloom],
      isInBloom: [Function: isInBloom],
      compareBlockNumbers: [Function: compareBlockNumbers],
      toNumber: [Function: toNumber]
    },
    Method: [Function: Method]
  },
  version: '1.5.3',
  utils: {
    _fireError: [Function: _fireError],
    _jsonInterfaceMethodToString: [Function: _jsonInterfaceMethodToString],
    _flattenTypes: [Function: _flattenTypes],
    randomHex: [Function: randomHex],
    BN: <ref *1> [Function: BN] {
      BN: [Circular *1],
      wordSize: 26,
      isBN: [Function: isBN],
      max: [Function: max],
      min: [Function: min],
      red: [Function: red],
      _prime: [Function: prime],
      mont: [Function: mont]
    },
    isBN: [Function: isBN],
    isBigNumber: [Function: isBigNumber],
    isHex: [Function: isHex],
    isHexStrict: [Function: isHexStrict],
    sha3: [Function: sha3] { _Hash: [Object] },
    sha3Raw: [Function: sha3Raw],
    keccak256: [Function: sha3] { _Hash: [Object] },
    soliditySha3: [Function: soliditySha3],
    soliditySha3Raw: [Function: soliditySha3Raw],
    encodePacked: [Function: encodePacked],
    isAddress: [Function: isAddress],
    checkAddressChecksum: [Function: checkAddressChecksum],
    toChecksumAddress: [Function: toChecksumAddress],
    toHex: [Function: toHex],
    toBN: [Function: toBN],
    bytesToHex: [Function: bytesToHex],
    hexToBytes: [Function: hexToBytes],
    hexToNumberString: [Function: hexToNumberString],
    hexToNumber: [Function: hexToNumber],
    toDecimal: [Function: hexToNumber],
    numberToHex: [Function: numberToHex],
    fromDecimal: [Function: numberToHex],
    hexToUtf8: [Function: hexToUtf8],
    hexToString: [Function: hexToUtf8],
    toUtf8: [Function: hexToUtf8],
    stripHexPrefix: [Function: stripHexPrefix],
    utf8ToHex: [Function: utf8ToHex],
    stringToHex: [Function: utf8ToHex],
    fromUtf8: [Function: utf8ToHex],
    hexToAscii: [Function: hexToAscii],
    toAscii: [Function: hexToAscii],
    asciiToHex: [Function: asciiToHex],
    fromAscii: [Function: asciiToHex],
    unitMap: {
      noether: '0',
      wei: '1',
      kwei: '1000',
      Kwei: '1000',
      babbage: '1000',
      femtoether: '1000',
      mwei: '1000000',
      Mwei: '1000000',
      lovelace: '1000000',
      picoether: '1000000',
      gwei: '1000000000',
      Gwei: '1000000000',
      shannon: '1000000000',
      nanoether: '1000000000',
      nano: '1000000000',
      szabo: '1000000000000',
      microether: '1000000000000',
      micro: '1000000000000',
      finney: '1000000000000000',
      milliether: '1000000000000000',
      milli: '1000000000000000',
      ether: '1000000000000000000',
      kether: '1000000000000000000000',
      grand: '1000000000000000000000',
      mether: '1000000000000000000000000',
      gether: '1000000000000000000000000000',
      tether: '1000000000000000000000000000000'
    },
    toWei: [Function: toWei],
    fromWei: [Function: fromWei],
    padLeft: [Function: leftPad],
    leftPad: [Function: leftPad],
    padRight: [Function: rightPad],
    rightPad: [Function: rightPad],
    toTwosComplement: [Function: toTwosComplement],
    isBloom: [Function: isBloom],
    isUserEthereumAddressInBloom: [Function: isUserEthereumAddressInBloom],
    isContractAddressInBloom: [Function: isContractAddressInBloom],
    isTopic: [Function: isTopic],
    isTopicInBloom: [Function: isTopicInBloom],
    isInBloom: [Function: isInBloom],
    compareBlockNumbers: [Function: compareBlockNumbers],
    toNumber: [Function: toNumber]
  },
  eth: <ref *2> Eth {
    currentProvider: [Getter/Setter],
    _requestManager: RequestManager {
      provider: [HttpProvider],
      providers: [Object],
      subscriptions: Map(0) {}
    },
    givenProvider: null,
    providers: {
      WebsocketProvider: [Function: WebsocketProvider],
      HttpProvider: [Function: HttpProvider],
      IpcProvider: [Function: IpcProvider]
    },
    _provider: HttpProvider {
      withCredentials: false,
      timeout: 0,
      headers: undefined,
      agent: undefined,
      connected: false,
      host: 'https://mainnet.infura.io/v3/b583160797e24b88a643ad9a38b0f5aa',
      httpsAgent: [Agent]
    },
    setProvider: [Function (anonymous)],
    setRequestManager: [Function (anonymous)],
    BatchRequest: [Function: bound Batch],
    extend: [Function: ex] {
      formatters: [Object],
      utils: [Object],
      Method: [Function: Method]
    },
    handleRevert: [Getter/Setter],
    defaultCommon: [Getter/Setter],
    defaultHardfork: [Getter/Setter],
    defaultChain: [Getter/Setter],
    transactionPollingTimeout: [Getter/Setter],
    transactionConfirmationBlocks: [Getter/Setter],
    transactionBlockTimeout: [Getter/Setter],
    defaultAccount: [Getter/Setter],
    defaultBlock: [Getter/Setter],
    maxListenersWarningThreshold: [Getter/Setter],
    clearSubscriptions: [Function: bound ],
    removeSubscriptionById: [Function: bound ],
    net: Net {
      currentProvider: [Getter/Setter],
      _requestManager: [RequestManager],
      givenProvider: null,
      providers: [Object],
      _provider: [HttpProvider],
      setProvider: [Function (anonymous)],
      setRequestManager: [Function (anonymous)],
      BatchRequest: [Function: bound Batch],
      extend: [Function],
      getId: [Function],
      isListening: [Function],
      getPeerCount: [Function],
      getNetworkType: [Function: bound getNetworkType]
    },
    accounts: Accounts {
      currentProvider: [Getter/Setter],
      _requestManager: [RequestManager],
      givenProvider: null,
      providers: [Object],
      _provider: [HttpProvider],
      setProvider: [Function (anonymous)],
      setRequestManager: [Function (anonymous)],
      _ethereumCall: [Object],
      wallet: [Wallet]
    },
    personal: Personal {
      currentProvider: [Getter/Setter],
      _requestManager: [RequestManager],
      givenProvider: null,
      providers: [Object],
      _provider: [HttpProvider],
      setProvider: [Function (anonymous)],
      setRequestManager: [Function (anonymous)],
      BatchRequest: [Function: bound Batch],
      extend: [Function],
      net: [Net],
      defaultAccount: [Getter/Setter],
      defaultBlock: [Getter/Setter],
      getAccounts: [Function],
      newAccount: [Function],
      unlockAccount: [Function],
      lockAccount: [Function],
      importRawKey: [Function],
      sendTransaction: [Function],
      signTransaction: [Function],
      sign: [Function],
      ecRecover: [Function]
    },
    Contract: [Function: Contract] {
      setProvider: [Function (anonymous)],
      defaultAccount: null,
      defaultBlock: 'latest',
      transactionBlockTimeout: 50,
      transactionConfirmationBlocks: 24,
      transactionPollingTimeout: 750,
      handleRevert: false,
      _requestManager: [RequestManager],
      _ethAccounts: [Accounts],
      currentProvider: [HttpProvider]
    },
    Iban: [class Iban],
    abi: ABICoder {},
    ens: ENS {
      eth: [Circular *2],
      _detectedAddress: null,
      _lastSyncCheck: null,
      registry: [Getter],
      resolverMethodHandler: [Getter],
      registryAddress: [Getter/Setter]
    },
    getNodeInfo: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'web3_clientVersion'
    },
    getProtocolVersion: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_protocolVersion'
    },
    getCoinbase: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_coinbase'
    },
    isMining: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_mining'
    },
    getHashrate: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_hashrate'
    },
    isSyncing: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_syncing'
    },
    getGasPrice: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_gasPrice'
    },
    getFeeHistory: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_feeHistory'
    },
    getAccounts: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_accounts'
    },
    getBlockNumber: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_blockNumber'
    },
    getBalance: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getBalance'
    },
    getStorageAt: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getStorageAt'
    },
    getCode: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getCode'
    },
    getBlock: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: [Function: blockCall]
    },
    getUncle: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: [Function: uncleCall]
    },
    getBlockTransactionCount: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: [Function: getBlockTransactionCountCall]
    },
    getBlockUncleCount: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: [Function: uncleCountCall]
    },
    getTransaction: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getTransactionByHash'
    },
    getTransactionFromBlock: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: [Function: transactionFromBlockCall]
    },
    getTransactionReceipt: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getTransactionReceipt'
    },
    getTransactionCount: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getTransactionCount'
    },
    sendSignedTransaction: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_sendRawTransaction'
    },
    signTransaction: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_signTransaction'
    },
    sendTransaction: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_sendTransaction'
    },
    sign: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_sign'
    },
    call: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_call'
    },
    estimateGas: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_estimateGas'
    },
    submitWork: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_submitWork'
    },
    getWork: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getWork'
    },
    getPastLogs: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getLogs'
    },
    getChainId: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_chainId'
    },
    requestAccounts: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_requestAccounts'
    },
    getProof: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_getProof'
    },
    getPendingTransactions: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'eth_pendingTransactions'
    },
    subscribe: [Function (anonymous)]
  },
  shh: Shh {
    currentProvider: [Getter/Setter],
    _requestManager: RequestManager {
      provider: [HttpProvider],
      providers: [Object],
      subscriptions: Map(0) {}
    },
    givenProvider: null,
    providers: {
      WebsocketProvider: [Function: WebsocketProvider],
      HttpProvider: [Function: HttpProvider],
      IpcProvider: [Function: IpcProvider]
    },
    _provider: HttpProvider {
      withCredentials: false,
      timeout: 0,
      headers: undefined,
      agent: undefined,
      connected: false,
      host: 'https://mainnet.infura.io/v3/b583160797e24b88a643ad9a38b0f5aa',
      httpsAgent: [Agent]
    },
    setProvider: [Function (anonymous)],
    setRequestManager: [Function (anonymous)],
    BatchRequest: [Function: bound Batch],
    extend: [Function: ex] {
      formatters: [Object],
      utils: [Object],
      Method: [Function: Method]
    },
    net: Net {
      currentProvider: [Getter/Setter],
      _requestManager: [RequestManager],
      givenProvider: null,
      providers: [Object],
      _provider: [HttpProvider],
      setProvider: [Function (anonymous)],
      setRequestManager: [Function (anonymous)],
      BatchRequest: [Function: bound Batch],
      extend: [Function],
      getId: [Function],
      isListening: [Function],
      getPeerCount: [Function]
    },
    subscribe: [Function (anonymous)],
    getVersion: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_version'
    },
    getInfo: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_info'
    },
    setMaxMessageSize: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_setMaxMessageSize'
    },
    setMinPoW: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_setMinPoW'
    },
    markTrustedPeer: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_markTrustedPeer'
    },
    newKeyPair: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_newKeyPair'
    },
    addPrivateKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_addPrivateKey'
    },
    deleteKeyPair: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_deleteKeyPair'
    },
    hasKeyPair: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_hasKeyPair'
    },
    getPublicKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_getPublicKey'
    },
    getPrivateKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_getPrivateKey'
    },
    newSymKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_newSymKey'
    },
    addSymKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_addSymKey'
    },
    generateSymKeyFromPassword: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_generateSymKeyFromPassword'
    },
    hasSymKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_hasSymKey'
    },
    getSymKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_getSymKey'
    },
    deleteSymKey: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_deleteSymKey'
    },
    newMessageFilter: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_newMessageFilter'
    },
    getFilterMessages: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_getFilterMessages'
    },
    deleteMessageFilter: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_deleteMessageFilter'
    },
    post: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_post'
    },
    unsubscribe: [Function: send] {
      method: [Method],
      request: [Function: bound ],
      call: 'shh_unsubscribe'
    }
  },
  bzz: Bzz {
    givenProvider: null,
    currentProvider: null,
    isAvailable: [Function (anonymous)],
    upload: [Function (anonymous)],
    download: [Function (anonymous)]
  }
}

Out of this whole object, we are interested in the eth and utils keys. web3.utils holds the utility functions, which will help in working with the blockchain, and web3.eth holds all Ethereum blockchain-related functions.

Understanding Ether units

It’s important to know about the different units in ether. If you look at the above object, you can find the unitMap property in utils. You can also get the list by running this command:

web3.utils.unitMap
Unit Weight
noether 0
wei 1
kwei / babbage / femtoether 1,000
mwei / lovelace / picoether 1,000,000
gwei / shannon / nanoether / nano 1,000,000,000
szabo / microether / micro 1,000,000,000,000
finney / milliether / milli 1,000,000,000,000,000
ether 1,000,000,000,000,000,000
kether / grand 1,000,000,000,000,000,000,000
mether 1,000,000,000,000,000,000,000,000
gether 1,000,000,000,000,000,000,000,000,000
tether 1,000,000,000,000,000,000,000,000,000,000

The smallest unit is wei and the largest is tether. There are three important units, which are used for different tasks: wei, Gwei, and ether (1 ether = 1,000,000,000,000,000,000 wei):

Unit Use Case
wei Everything is represented in wei internally. So, if you are looking for the number of coins an account holds, it will be returned in wei by the library.
Gwei The gas fees are represented in Gwei unit. Every transaction needs to pay the gas fees. These fees are not fixed and transaction processing time depends on how much it can pay for gas.
ether The cryptocurrency is represented in this unit.

web3.utils provides two functions that can be used to convert one unit to another: toWei and fromWei. toWei converts from another unit to Wei.

web3.utils.toWei(count_to_convert, unit_to_convert_from)

count_to_convert is the amount that needs to be converted. It should be in the string. unit_to_convert_from is the current unit of the amount (for example, Ether). Use it like this:

> web3.utils.toWei('34', 'ether')
'34000000000000000000'

fromWei is for converting from Wei to another unit.

web3.utils.fromWei(count_to_convert, unit_to_convert_to)

For example:

> web3.utils.fromWei('48593000000000000000', 'micro')
'48593000'

You may use these functions in combination to convert any unit to another unit.

web3.utils.fromWei(web3.utils.toWei(count_to_convert, unit_to_convert_from), unit_to_convert_to)

Suppose, for example, you want to convert 23 Ether to micro:

> web3.utils.fromWei(web3.utils.toWei('23', 'ether'), 'micro')
'23000000'

Unit Conversion

We will review a number of other functions, but before that, we need go over Etherscan.

What is Etherscan?

Etherscan is the Ethereum blockchain explorer. You can get all kinds of information regarding Ether coin, tokens, smart contracts, accounts, etc. So all the operations that Web3 can do can be confirmed by looking into Etherscan.

Etherscan

The homepage displays the information related to Ether coin. You can get information regarding tokens by searching in the bar. At the time of writing, the stats were as follows:

Stat Value
Price $2,923
Transactions 1300 M
Gas Price 61 Gwei
Market Cap ~ $343 B
Difficulty 9,043 TH
Hash Rate 718,380 GH/s
Latest Block 133192777

Let’s see these stats through Web3.js — except price because it’s not a technical figure. The price is a matter of trading and blockchain has nothing to do with it. It is determined by supply and demand. If you want the price, you will need to call the APIs of exchanges like Poloniex, Binance, Coinmarketcap, etc.

To get a count of all the transactions, you will need to get the transaction count from each block in the chain. Ethereum does not directly provide the total transaction count. Etherscan must keep this count in its database separately.

Latest block

First, get the latest block number:

web3.eth.getBlock('latest').then(console.log)

Notice that we’re using the then block in this function. This is because all the eth branch functions make network requests on the chain. They return a promise and the then function is used to get the response.

This will display the information about the latest block.

Web3 Latest Block

Here you can see some useful information about a single block. First of all, it is the latest block at the time of running the code.

gasLimit and gasUsed

You can also see the gasLimit and gasUsed. gasLimit is the maximum amount that the users are willing to pay to mine this block and gasUsed is the amount that was actually used.

This amount is multiplied by a base gas fee, which is baseFeePerGas, to get the total fees. In this object, baseFeePerGas is represented in hexadecimal. You can use the web3.utils function to convert it into a decimal:

> web3.utils.toDecimal('0x998388ca6')
45677054459

Remember, all the amounts are stored in wei unit in this chain. So, the per-gas fee for this block is 45677054459 Wei. But how much gas was used that is listed in the gasUsed key? To get the total gas fee of this block, we can use the following:

> var singleGasFee = web3.utils.toDecimal('0x998388ca6')
> var totalGas = 6156205
> var totalFeeInWei = singleGasFee * totalGas
> totalFeeInWei
253688272429254180
> var totalFeeInEther = web3.utils.fromWei(totalFeeInWei.toString(), 'ether')
> totalFeeInEther
'0.25368827242925418'

We can see that this block has paid 0.2537 Ether to the miners.

Difficulty

There are other parameters, such as difficulty, which determines the complexity of the puzzle the miners need to solve. This keeps on increasing and making it difficult for miners to mine the coins. That’s why more computation and heavy hardware are required, not to mention a lot of time.

For this block, the difficulty is 8,937,488,432,688,686. If we compare it with the difficulty of the first block, we’ll see another huge number.

Difficulty of the first block:

> web3.eth.getBlock(1).then(res => {console.log(res.difficulty)})
17171480576

The difficulty of first block was 17,171,480,576, but for our block it is 8,937,488,432,688,686.

Block number

The object also lists the block number in the number key. This means that 13,320,123 blocks have already been mined.

Run the loop over all blocks to get the total transactions in each of them and then add them to get the total transactions in the Ethereum chain.

var totalTransactions = 0;
for(var i=1; i <= 13320123; i++){
    web3.eth.getBlockTransactionCount(i).then(res => {
      totalTransactions += res;
    })
}

Here I am using the getBlockTransactionCount function, but you can also use the getBlock function and count the transactions from the provided array. You can see in the above image that we are getting an array of transactions in the block. You can use the length property of JavaScript to get the total transactions.

Note: DO NOT run the above loop. I’m showing it to demonstrate how to get the total transactions, but it’s very costly. There will be 13 million API calls, which is a disaster. If you need the total transaction count, simply get it from the Etherscan and store it in a database. For all additional blocks, you can hit the API and add the transactions to the stored count.

Gas price

To get the current gas price of the network, you can use the getGasPrice function of web3.eth.

> web3.eth.getGasPrice().then(console.log)
110063065122
> web3.utils.fromWei('110063065122', 'gwei')
'110.063065122'

The gas price of the network at the time of writing is 110 Gwei.

Accessing blockchain account information

Everything in blockchain has an address represented in a SHA3 string. This could be an account, token, coin, smart contract, or anything else. If you have an account on the Ethereum blockchain, it must be represented in a unique SHA3 string.

Etherscan Top Accounts

These are the top 10 accounts holding Ether coin. You can see that there are smart contract accounts too. Contracts can also hold the coins.

Let’s perform some operations on an account. We will use the third account from the list, which has a name tag of Binance 7.

First, store the account address in a variable:

> var accountAddress = '0xbe0eb53f46cd790cd13851d5eff43d12404d33e8'

You can see that the address is a hexadecimal string encoded in SHA3 algorithm.

To read the Ether balance of this account:

> web3.eth.getBalance(accountAddress).then(console.log)
2296896444430813427853147

The number is in wei, so we need to convert it to Ether:

> web3.utils.fromWei('2296896444430813427853147', 'ether')
'2296896.444430813427853147'

This account holds 2,296,896 Ether:

Ether Count

Transaction count

To get the total number of transactions sent by this account, use the following:

> web3.eth.getTransactionCount(accountAddress).then(console.log)
891

Etherscan Transactions

Etherscan is showing 966 transactions because it includes received transactions as well.

Creating a new account

You can create a new account on the Ethereum blockchain using Web3.js:

> web3.eth.accounts.create()
{
  address: '0x8F0cE820cecf482D0909501508E730cb0d8C806d',
  privateKey: '0x1912e5bc09cc0def85ae95cdd******************35c96230a59a834ee',
  signTransaction: [Function: signTransaction],
  sign: [Function: sign],
  encrypt: [Function: encrypt]
}

You can see the address and private key. Although I am not going to use this account for anything but to safeguard others from doing transactions through this account, I have obfuscated some characters. Do not add any coin or token to this account as it will stay orphan.

Address Private Key

This account will be available on Etherscan. It will be empty but available:

Etherscan Empty

Connecting to a local Ethereum chain

Suppose you are developing an application and want to test it. If you test it on live chain, you will have to pay a gas fee for any operation you perform. These operations are recorded as transactions and thus require gas fees.

To solve this issue, you can run your private chain locally using Ganache and deploy your application on it. That way, you won’t need to pay real money as gas fees. You will need to pay the gas fees, but you will have all the Ethers free of cost.

We already installed Ganache and saw that it is running on 7545 port on localhost. Let’s connect with it using Web3.js.

> var web3G = new Web3('http://127.0.0.1:7545')

We are instantiating Ganache in the web3G variable because web3 is used for the Infura live chain. Also, there are few accounts already created by Ganache.

Let’s create one more account. Here we will need to use the personal property of Web3.js because we can create an account on our node.

First, check how many accounts we have on Ganache:

> web3G.eth.getAccounts().then(console.log)
[
  '0xF148563F7968cEcea44957aDa1A9aF795A880718',
  '0x0AF35629772CFDEb2DE80db66D7D5Bb855965b77',
  '0x6df34da7BB12eD848AD9f6e169DD27fcCDb06FEd',
  '0x60B9f90C7C2896Ba7C46f7eE761725E449ef3E89',
  '0xfA099835F789210A108E8A0A181173a14a9aac63',
  '0x17188DF995D9ec32D26633DDeFAa3a1644B3e0f2',
  '0x396D3a9138E39E6F5D5a6e09ab6425eA330AfDbD',
  '0x17d8c3dDe00Fb8f686591430cCB6a517FB97Ea96',
  '0xDF5CC5f9Fed1B9a091C6B73984C52D2555d62BEe',
  '0x55a4AdC17c6903f064E181f3BD2A34374B7C6543'
]

There are 10 accounts. You can see these accounts on Ganache UI too.

Let’s create one more using Web3.js:

> web3G.eth.personal.newAccount('this|is|password').then(console.log)
0x84D8FA3a23ac9066066Dd6c63680BC0FBd31aEe6

This is the new account and it should be available in the list:

> web3G.eth.getAccounts().then(console.log)
[
  '0xF148563F7968cEcea44957aDa1A9aF795A880718',
  '0x0AF35629772CFDEb2DE80db66D7D5Bb855965b77',
  '0x6df34da7BB12eD848AD9f6e169DD27fcCDb06FEd',
  '0x60B9f90C7C2896Ba7C46f7eE761725E449ef3E89',
  '0xfA099835F789210A108E8A0A181173a14a9aac63',
  '0x17188DF995D9ec32D26633DDeFAa3a1644B3e0f2',
  '0x396D3a9138E39E6F5D5a6e09ab6425eA330AfDbD',
  '0x17d8c3dDe00Fb8f686591430cCB6a517FB97Ea96',
  '0xDF5CC5f9Fed1B9a091C6B73984C52D2555d62BEe',
  '0x55a4AdC17c6903f064E181f3BD2A34374B7C6543',
  '0x84D8FA3a23ac9066066Dd6c63680BC0FBd31aEe6'
]

Get Accounts

The UI might not show the new account because it shows only the autogenerated ones.

Checking balance

We can easily check the balance of any account like we did for the live chain:

> web3G.eth.getBalance('0xF148563F7968cEcea44957aDa1A9aF795A880718').then(console.log)
100000000000000000000

The amount is in wei and is equivalent to 100 Ether.

Sending coins between accounts

Since we are on Ganache, we can see this functionality without spending any real money.

Here, we will send 1 ether from the first account to the second account in the list. We’ll need to provide the gas price and gas limit, so it is important to know these values. The network has a gas limit that changes, but we need to keep our value lower than that and not so much that it can’t be mined.

If you see in the above image, Ganache is providing the following values.

  • Gas price: 20000000000
  • Gas limit: 6721975

In our transaction, we will set the limit as 6500000 and price as shown:

web3G.eth.sendTransaction({
    from: '0xF148563F7968cEcea44957aDa1A9aF795A880718',
    gasPrice: "20000000000",
    gas: "6500000",
    to: '0x0AF35629772CFDEb2DE80db66D7D5Bb855965b77',
    value: "1000000000000000000",
    data: ""
}, 'this|is|password').then(console.log);

This will return the transaction information:

{
  transactionHash: '0x7652e3cf6a4dcb522c897385d1a0acf092f890a4f38685d2141878a9a981e650',
  transactionIndex: 0,
  blockHash: '0x7bef068531cba78022cd01bd160663f27f2ccb2a2cebf94f03be08d09f19c111',
  blockNumber: 1,
  from: '0xf148563f7968cecea44957ada1a9af795a880718',
  to: '0x0af35629772cfdeb2de80db66d7d5bb855965b77',
  gasUsed: 21000,
  cumulativeGasUsed: 21000,
  contractAddress: null,
  logs: [],
  status: true,
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
}

As you can see, the gas used was 21,000 — although we put 6,500,000 because the network takes what is spent. So the total cost paid in gas = 21000 * 20000000000 wei = 0.00042 Ether.

Let’s check the balance of both accounts:

> web3G.eth.getBalance('0xF148563F7968cEcea44957aDa1A9aF795A880718').then(console.log)
98999580000000000000
> web3G.eth.getBalance('0x0AF35629772CFDEb2DE80db66D7D5Bb855965b77').then(console.log)
101000000000000000000

The first account has a balance of 98.99958 Ether because 1 eth is transferred and 0.00042 is spent in gas fees.

Spent Gas Fees

The same will be reflected on GUI:

Balance

You can get more information in the Blocks and Transactions tabs:

Blocks and Transactions Tabs

Transactions Tab in Ganache

Accessing tokens using Web3.js

Ether is a coin and everything else implemented on the chain using smart contracts are tokens. The tokens that are used for trading need to be implemented using the ERC-20 protocol. This defines the set of rules to follow.

If you check on Etherscan, you will find the list of all the ERC-20 tokens on the network.

Token Tracker

Let’s choose one token and perform the operations over it. For this tutorial, I am choosing BNB.

When you open it on Etherscan, you will find information such as contract address, total token supply, holders, etc.

BNB Token

If you click on the contract address (outlined in red), you will get useful information about it.

Contract Page

We didn’t talk about creating a smart contract because that’s outside the scope of this article. But to put it simply, it is the code of the application. If we’re talking about a token, then the smart contract is the compiled code. This code is written in Solidity language.

This contract page on Etherscan will show you the Solidity code of the BNB token:

BNB Solidity Code

When this code is compiled, a bytecode and a JSON object are created, which is known as contract ABI. This ABI file holds the function definitions, which were declared in the Solidity code.

You can also find the ABI for BNB on the following page on Etherscan:

Contract ABI

This Solidity code and ABI are not specific to the BNB token because these are standard functions defined in ERC-20 protocol.

You might be thinking that if the Solidity code is not specific to BNB, then how did it get its symbol, total supply, and all other properties? This information was passed as arguments to the constructor.

Here are the arguments:

Decoded View

But how are they passed to the constructor? By appending to the byte code of contract.

First, these arguments are converted to bytecodes:

Bytecodes

These different bytecodes are joined together into a single string. Let’s call the following argument bytecode:

Argument Bytecode

We know that the contract source code is compiled into bytecode and ABI. So its bytecode looks like this:

Content Creation Code

This bytecode is a low-level representation of Solidity code for smart contracts. It is created when Solidity code is compiled by Ethereum Virtual Machine (EVM). To pass the arguments to a Solidity constructor, the argument bytecode is appended to this contract bytecode.

The complete contract bytecode will look like this:

Argument Bytecode Appended

Working with contracts in Web3.js

To work with contracts in Web3.js, we need two things: the ABI and contract address. We already learned what ABI is and how to get it and we know the contract address too.

Web3.js provides a Contract property on the eth property. We need to instantiate it and store it in a variable. The format of the function is as follows:

> var newContract = new web3.eth.Contract(ABI, contractAddress)

Let’s store the ABI and contract address in their respective variables:

> var ABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdrawEther","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"burn","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"unfreeze","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"freezeOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"freeze","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"},{"name":"tokenName","type":"string"},{"name":"decimalUnits","type":"uint8"},{"name":"tokenSymbol","type":"string"}],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Freeze","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Unfreeze","type":"event"}]
> var contractAddress = '0xB8c77482e45F1F44dE1745F52C74426C631bDD52'

Newcontract Object

Now we can perform different operations over this newContract object. The various methods are defined in the newContract.methods branch. You can run it on the node and check the list:

Contract Methods

If you want to get the name of the token, run:

> newContract.methods.name().call().then(console.log)
BNB

To get the total supply, run:

> newContract.methods.totalSupply().call().then(console.log)
16579517055253348798759097

Similarly, we can perform all other operation related to a token.

Conclusion

In this tutorial, we covered the basics of Web3.js, blockchain, Ethereum, Infura, Ganache, smart contracts, tokens, and much more.

If you’re looking to enter the world of blockchain and DApp development, your next steps should include familiarizing yourself with Solidity and MetaMask and learning how to create and deploy smart contracts, build DeFi apps on the blockchain, and create tokens using ERC-20.

The blockchain world is massive, rapidly evolving, and full of possibilities. I hope this tutorial gave you the insight and motivation you need to move forward in your blockchain journey.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Akash Mittal I am a software engineer and a die-hard animal lover. My life's goal is to create a mini jungle with a dispensary for stray animals who get diseased or injured.

Leave a Reply