Building a Word Counter App in Node.js Using Axios
Tags: node, webdev, beginners
Published: 2021-04-06 03:31:02

![Web](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gqogowyf7dd3mnji8lge.png)
Modern applications communicate with other servers to accomplish tasks like sending emails, upload/download images, and embedding live Twitter feed. That is when we need HTTP requests. There are multiple ways to make HTTP requests in Node.js. In this article, I am going to introduce the Axios library.

[Axios](https://github.com/axios/axios) is a JavaScript library that works in both Browser and Node.js platforms. Axios is promise-based, and this lets us perform requests asynchronously.

#Implementation#
Let's implement a feature in the Node.js-React application I created in [the last article](https://dev.to/junko911/how-to-create-a-react-node-js-application-m5b) that accepts a URL as an input from a user, load the content of the URL, and counts the number of word occurrence in its DOM. I am going to use a URL of a large `.txt` document of [The Mysterious Affair at Styles](http://www.gutenberg.org/files/863/863-0.txt) by my favorite mystery writer, Agatha.

Before we move on, let's create `api/app.js` and edit `api/server.js` to separate responsibilities:

```javascript
// api/app.js

const express = require("express")
const app = express()
const cors = require("cors")

app.use(cors())

app.post("/", function (req, res) {
 // fetch the content from the URL
 // clean the content
 // count the word occurrence and send it back
})

module.exports = app
```

```javascript
// api/server.js

const app = require("./app")

app.listen(3000, () => {
  console.log("app listening on port 3000")
})
```
Now, let's create a module for each task:
* Fetch the content from the URL
* Clean the content
* Count the word occurrence
***

###Fetch the content from the URL###
First, we need to install `axios`. Run:
```
$ cd api
$ npm install axios
```

Create `api/fetch-url-content.js` and write:
```javascript
// api/fetch-url-content

const axios = require('axios')

exports.fetchUrlContent = url => {
  return axios.get(url)
    .then(response => {
      return response.data
    })
    .catch(error => {
      console.log(error)
    })
}
```
`response.data` is the response that was provided by the server. Let's use `catch` for error handling.

###Clean the content###
To count the occurrence properly, we should:
* Remove numbers
* Remove special characters except for apostrophes that are part of the words
* Replace 2 or more spaces to 1
* Remove whitespace from both ends of a string
* Convert strings to lowercase

To do this, we need Regular Expression. Create `api/clean.js` and write:
```javascript
// api/clean.js

exports.clean = string => {
  const alphabet = string.replace(/[^A-Za-z']+/g, " ").trim()
  const lowerCase = alphabet.toLowerCase()
  return lowerCase
}
```
As the [replace()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) method searches a string for a specified value and returns a new string where the specified values are replaced, `.replace(/[^A-Za-z']+/g, " ")` replaces everything besides alphabets, and apostrophes that are neither ends of a string with one space.

The [`trim()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) method removes whitespace from both ends of a string.

The [toLowerCase()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) method returns the calling string value converted to lower case.

###Count the word occurrence###
Let's create `api/count.js` and implement a function to count the word occurrence of cleaned string:
```javascript
// api/count.js

exports.count = string => {
  let map = {}
  const words = string.split(" ").filter(word => word !== "")

  for (let i = 0; i < words.length; i++) {
    const item = words[i]
    map[item] = (map[item] + 1) || 1
  }

  return map
}
```

###Call the functions asyncronously###
Now in `api/app.js`, we can call functions provided by each module:
```javascript
// api/app.js

app.post("/", async function (req, res) {
  const url = req.body.url
  const content = await fetchUrlContent(url)
  const cleanedContent = clean(content)
  const result = count(cleanedContent)
  res.send(result)
})
```
We need `async` and `await` to wait for the `fetchUrlContent` function to finish executing and then run the rest of the `POST` method.

###Use body-parser to simplify the `POST` request###
Also, it is better to use the [body-parser Express middleware](http://expressjs.com/en/resources/middleware/body-parser.html) to read the body of incoming request data, and simplify it. This time we use [`express.json()`](http://expressjs.com/en/api.html#express.json) and [`express.urlencoded`](http://expressjs.com/en/api.html#express.urlencoded).

Now, `api/app.js` should look like this:
```javascript
// api/app.js

const express = require("express")
const app = express()
const cors = require("cors")
const { fetchUrlContent } = require("./fetch-url-content")
const { clean } = require("./clean")
const { count } = require("./count")

app.use(cors())
app.use(express.urlencoded(({ extended: true })))

app.post("/", async function (req, res) {
  const url = req.body.url
  const content = await fetchUrlContent(url)
  const cleanedContent = clean(content)
  const result = count(cleanedContent)
  res.send(result)
})

module.exports = app
```
###Build the client###
Lastly, let's build a form and a table in `client/App.js` for UI:
```javascript
// client/App.js

import React from "react"
import "./App.css" // Added some styling

class App extends React.Component {
  state = {
    url: "",
    result: {}
  }

  genRows = obj => {
    return Object.keys(obj).map(key => {
      return (
        <tr key={key}>
          <td>{key}</td>
          <td>{obj[key]}</td>
        </tr>
      )
    })
  }

  submitHandler = e => {
    e.preventDefault()
    const options = {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accepts: "application/json",
      },
      body: JSON.stringify({
        url: this.state.url,
      }),
    }
    fetch("http://localhost:3000/", options)
      .then(res => res.json())
      .then(data => {
        this.setState({ result: data, url: "" })
      })
  }

  render() {
    return (
      <>
        <h1>Word Counter</h1>
        <form onSubmit={e => this.submitHandler(e)}>
          <label>
            URL:
          <input
              type="url"
              name="url"
              onChange={e => this.setState({ url: e.target.value })}
              value={this.state.url} />
          </label>
          <input type="submit" value="Submit" />
        </form>
        <table>
          <thead>
            <tr>
              <th>Word</th>
              <th>Count</th>
            </tr>
          </thead>
          <tbody>
              {this.genRows(this.state.result)}
          </tbody>
        </table>
      </>
    )
  }
}

export default App
```
***

This is it! Let's see what we get from [The Mysterious Affair at Styles](http://www.gutenberg.org/files/863/863-0.txt):
![word counter demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8c297ayc47cdzshzv62m.png)