DEV Community

Junko T.
Junko T.

Posted on • Updated on

Building a Word Counter App in Node.js Using Axios

Web
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 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 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 by my favorite mystery writer, Agatha.

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

// 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
Enter fullscreen mode Exit fullscreen mode
// api/server.js

const app = require("./app")

app.listen(3000, () => {
  console.log("app listening on port 3000")
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

// 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)
    })
}
Enter fullscreen mode Exit fullscreen mode

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:

// api/clean.js

exports.clean = string => {
  const alphabet = string.replace(/[^A-Za-z']+/g, " ").trim()
  const lowerCase = alphabet.toLowerCase()
  return lowerCase
}
Enter fullscreen mode Exit fullscreen mode

As the 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() method removes whitespace from both ends of a string.

The 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:

// 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
}
Enter fullscreen mode Exit fullscreen mode

Call the functions asyncronously

Now in api/app.js, we can call functions provided by each module:

// 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)
})
Enter fullscreen mode Exit fullscreen mode

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 to read the body of incoming request data, and simplify it. This time we use express.json() and express.urlencoded.

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

// 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
Enter fullscreen mode Exit fullscreen mode

Build the client

Lastly, let's build a form and a table in client/App.js for UI:

// 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
Enter fullscreen mode Exit fullscreen mode

This is it! Let's see what we get from The Mysterious Affair at Styles:
word counter demo

Top comments (2)

Collapse
 
parvans profile image
Parvan S

Hey did u get this output from backend
{
"object": 1,
"promise": 1
}

when i tried to pass the url from the frontend and i for an error from the backend that is,
TypeError [ERR_INVALID_URL]: Invalid URL
but i directly pass the url from backend , i got the above object as output

This is the frontend code
could u please check
`import './App.css';
import { useState } from 'react';

function App() {
const [url, setUrl] = useState("");
const [result, setResult] = useState({});

const genRows=obj=>{
return Object.keys(obj).map(key=>{
return(


{key}
{obj[key]}

)

})
}

const submitHandler=e=>{
e.preventDefault()
const options={
method:'POST',
headers:{
"content-type":"application/json",
Accepts:"application/json",
},
body:JSON.stringify({url:url}),
}
fetch('localhost:5000/',options)
.then(res=>res.json()).then(data=>{
setResult({result:data})
// setUrl('')
})
}
return (


WORD COUNTER


submitHandler(e)}>
type="url"
name="url"
placeholder='Enter The URL'
onChange={e=>setUrl(e.target.value)}
value={url}
/>










{genRows(result)}

Word Count


);
}

export default App;
`

Collapse
 
alex2303 profile image
Alex Schwartzman

Thanks for the tutorial!
Quick note - I had to add the following line in server.js to make it work:

app.use(express.json())