How to build a Dictionary App with JavaScript

Why Build Dictionary JavaScript Dictionary Apps?

RapidAPI Team
The Era of APIs
16 min readFeb 18, 2020

--

There are many easy ways to look up words these days. However, what can separate a normal app from a great dictionary app can depend on the completeness of the information it provides.

A single definition can be misleading and, without context, unhelpful. Different dictionary and word APIs can have their own special niche of information that they provide. Targeting the right audience with an app — perhaps by exploring the API yourself — can open up the possibility of creating a dictionary application that someone had only dreamed could exist.

A great example of a dictionary API that provides some of the uncommon information, as well as all the basic word information, is the WordsAPI.

If this is your first time using APIs, or want some more background, you can check out this article before we dig any deeper.

Read the full article on the RapidAPI Blog

WordsAPI

I will describe the API briefly, however, you can also view the first part of this great post that explains the API and has a few more images. The WordsAPI allows programs to retrieve varied, and sometimes extensive, information on English words. When a single word is retrieved it may contain information including:

  • Definitions
  • Synonyms
  • Example usage
  • Part of speech
  • Rhymes

The more interesting endpoints explore how a word relates to other words in the real world. Some of this data could be:

  • Region of use (i.e Canada)
  • Is in the category
  • Has categories
  • Is similar to
  • Has parts (“house” may return a list with ‘roofing’, ‘masonry’, ‘plumbing’, etc.)
  • Member of (family name if the biological taxonomic rank is relevant)

The pricing hints at perceived use cases. The API is meant to be used a lot. The basic plan for WordsAPI covers 2500 requests a day, then $0.004 per request after that. The monthly plan is only $10 and kicks the quota up to 25,000 per day. The average latency, as of the time of writing, is 134ms and which is low. You can also check out some of the other dictionary APIs that appear when searching on RapidAPI to see how they compare.

We will use the API as a one-off word search, but it could also be used as an embedded tool inside another word processing application.

View the code on Github.

The app will combine some old school Javascript — libraries like jQuery and Bootstrap — with some of the new school serverless function options.

I chose to use jQuery (instead of ReactJS) to show how little needs to be done to get a useful app up and running on the internet with this API. We will only need to write two functions to achieve our end goal.

Furthermore, we want to use a serverless function because we will have a RapidAPI key that needs to be kept secret. This key is not safe on the front-end, but we will add it later to our Netlify account after deployment.

Netlify is self-described as,

“…everything you need to build fast, modern websites: continuous deployment, serverless functions, and so much more.”

To reiterate, the app can still be built and tested locally without deploying in case you did not want to set up a Netlify account. If deployed, the app is live on the internet and anyone can use its features. We will deactivate it after deployment to make sure that no one is abusing our API quota.

The app will have a front-end (code that is executed and used by the browser) that calls a serverless function to grab our word data.

Serverless functions are in fact, run on a server but we do not have to manage the production server, which can save us a lot of time and project complexity.

The serverless function will be an AWS Lambda Function. However, we will be deploying to Netlify and they have an easy way of making our function accessible without having to create our own AWS account. It’s a pretty cool set-up. Let’s get started.

Prerequisites

There are a few prerequisites for the application as well as some preferences that I will suggest so we are using the same tooling, you will need:

  • Some Javascript experience and understanding of how HTML, CSS, and JS work together to form a web app.
  • Some experience using a command-line tool.
  • NPM and NodeJS installed locally. Having a long-term-support version installed is generally a good idea. I will be using version 12.13.1 and NPM version 6.13.7. Some popular projects, like Create-React-App, use the package runner npx that is available with NPM versions above 5.2, so, if it’s feasible, having a version above 5.2 may save you time in the future.
  • RapidAPI account
  • Subscription to the WordsAPI on RapidAPI
  • If you wish to deploy, an account on Netlify and Github
  • An internet connection
  • Reputable code editor (I will be using Visual Studio Code)

1. Set-Up Project

First, make a new project folder called dictionary_app. Open up a new terminal and navigate to the project (in most cases the command is cd path/to/folder).

Initialize a new npm module with the command npm init -y. The -y flag fills out the package metadata for us. It’s not supremely relevant in the scope of this example to have this information perfect.

We only need to install two modules.

  1. axios — to make promise based HTTP requests
  2. netlify-lambdabuilds our serverless functions locally and serves them locally for development

Install these packages with npm install --save axios netlify-lambda.

In order to utilize the netlify-lambda module add two scripts the package.json file that was created when we initialized the project. The scripts section of your package.json should now look like:

// package.json{
...
"scripts": {
"lambda-serve": "netlify-lambda serve functions",
"lambda-build": "netlify-lambda build functions",
"test": "echo \"Error: no test specified\" && exit 1",
},
...
}

We can run commands from the terminal, using npm, that is defined in the package.json file with the syntax npm run [name of command]. The two commands will do the following;

  • lambda-build will compile our functions (that we will create in functions folder) and place the compiled function into a new folder.
  • lambda-serve will allow our function to be accessed, via HTTP requests, on our local computer. It will automatically recompile when we make changes.

Create the file netlify.toml in the root of the project and add the code:

[build]
functions = "lambda"

This is required to run the commands that we added. For more information on the netlify.toml configuration file, you can check out Netlify's documentation.

We are going to scaffold the file structure. First, we need to create the functions folder in the root of our directory. Create the file getWord.js in that folder.

Next, create the public folder at the project root. Inside the folder add:

  • new directory css with the file styles.css
  • new directory js with the file main.js
  • a new file called index.html

Your project structure should look like the following image (there should also be a node_modules folder at the project root).

2. Add HTML and CSS

Add the below HTML to index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dictionary App</title>
<!-- load our style sheet -->
<link rel="stylesheet" href="css/styles.css">
<!-- load Bootstrap library into our HTML -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<h1 class="mt-4 display-4">Welcome To the Dictonary App!</h1>
<main class="container-fluid">
<div class="row justify-content-center align-items-center">
<div id="word-search" class="col-md-7">
<h2>Comprehensive Word Information</h2>
<p class="text-center">Here you can get information on words. Learn about different definitions, synonyms, types of speech, categories, get examples and more!</p>
</div>
</div>
<!-- create a form for user input -->
<!-- 'sticky-top' allows the div to stay at the top of page on scroll -->
<div id="form-row" class="row sticky-top">
<div class="col">
<form id="main-form">
<fieldset>
<label htmlFor="word">Enter Word</label>
<!-- create text input -->
<input id='word-input' type="text" class="form-control" name="word" placeholder="automobile">
<!-- create submit button -->
<input value="Submit" type="submit" style="margin: 10px" class="btn btn-primary" />
</fieldset>
</form>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-10">
<!-- empty list to place our word data -->
<ul id="word-info" class="list-unstyled"></ul>
</div>
</div>
</main>
<!-- import jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- import our javascript file -->
<script src="js/main.js"></script>
</body>
</html>

That may seem like a lot of code, but the important sections have comments explaining what the specific tags are doing. The structure of the app relies heavily on the Bootstrap library. It is imported at the top of the page with our own style sheet.

We import jQuery at the end of the <body> tag, as well as our own javascript file that we created a bit earlier.

Next, we will add some styling. There are not that many styles in our styles.css because the majority is coming from Bootstrap. Add the below CSS to styles.css.

h1, h2, h3 {
text-align: center;
}
body {
display: flex;
flex-direction: column;
height: 100vh;
background: black!important;
color: white!important;
}
form {
width: 200px;
margin: auto;
text-align: center;
}
#form-row {
background: black;
}
main {
flex-grow: 1;
margin-bottom: 20vh;
}
#word-search {
margin-top: 50px;
}
.list-item {
background: white;
color:black;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
-o-border-radius: 5px;
}
.definition {
font-size: 2.5rem;
font-weight: 300;
line-height: 1.2;
}

Finally, I am going to start the development server. This will be different if you are not using VSCode. If you are using VSCode, install the plugin ‘Live Server’. It will have over 4 million downloads.

If you are not using VSCode there is probably a great plugin for the editor you are using that starts a live server.

At the bottom of VSCode there will be a button (“Go Live”) that will start the development server.

Now, we can view the page and see our changes live. My development server started up and began running on http://localhost:5500

When you start it up and visit the URL, you should see the makings of our app!

3. Build a Serverless Function

The serverless function is going to use NodeJS. We will have to import axios, export an async function, and build our API call to WordsAPI inside that function.

We will only hit one Words API endpoint, and we can inspect/test the endpoint on RapidAPI.

The reason we are only hitting one endpoint is that it gives us much of the same information we could get from other endpoints. For example, there are endpoints for synonyms, definitions, examples, types of, etc. but many of this data is returned as part of the ‘Word’ endpoint.

We will take the response and iterate over the results. Different words will have different data categories. Some may return the derivation, examples, categories and some may not. Later we will build the function to handle those kinds of variations. For now, add the following code to getWord.js. Notice, it is very similar to the code snippet generated on RapidAPI.

const axios = require('axios')// 'event' and 'context' are automatically passed to the function
// event contains the query parameters that we will be passing with the API call
export async function handler(event, context) {
// extract the word query parameter from the HTTP request
const word = event.queryStringParameters.word || "automobile";
try {// send request to the WordsAPI
const response = await axios({
"method":"GET",
"url":`https://wordsapiv1.p.rapidapi.com/words/${word}`,
"headers":{
"content-type":"application/octet-stream",
"x-rapidapi-host":"wordsapiv1.p.rapidapi.com",
"x-rapidapi-key":"[insert your rapidapi key]"
}
})
return {
statusCode: 200,
body: JSON.stringify(response.data),
headers: {
'Access-Control-Allow-Origin': '*'
}
}
} catch (err) {
console.log(err)
return { statusCode: 500, body: err.toString() }
}
}

Test Function in Browser

We can test this function using the commands that we set up earlier.

In the terminal, at the root of the directory, run the command npm run lambda-build. The command should create a new folder lambda and - inside that folder - a new file getWord.js in our project. It will be a compiled version of our function, so it may look very cryptic.

Run npm run lambda-serve to start up a development server for our function.

Lambda server is listening on 9000 will be logged to the terminal.

Paste the following URL into a new browser tab:

http://localhost:9000/.netlify/functions/getWord?word=automobile

Our function should return data for the word ‘automobile’ and display it on the page. By entering the URL, we are sending a GET request to our function (getWord) with the parameter specified in the URL (word=automobile). The function is told to read the query parameter, request information from the WordsAPI, and then return the JSON data it receives. This is how we will call the function.

We are going to dynamically format the response to save the front-end some work. Add the new code block to the function and make a minor change to the return body. The file should now look like:

const axios = require('axios')

export async function handler(event, context) {

// extract the word query parameter from the HTTP request
const word = event.queryStringParameters.word || "automobile";

try {

// send request to the WordsAPI
const response = await axios({
"method":"GET",
"url":`https://wordsapiv1.p.rapidapi.com/words/${word}`,
"headers":{
"content-type":"application/octet-stream",
"x-rapidapi-host":"wordsapiv1.p.rapidapi.com",
"x-rapidapi-key":"apikey"
}
})

// START OF NEW CODE
// create new array to push data to
let results = [];

response.data.results.map(def => {
let definitionArray = [];

// creates array of keys in object
const keys = Object.keys(def);
keys.map(key => {

// builds regex that looks for capital letters
// The response parameters are in camelCase, so we need to ID
// the capital letters and add spaces instead so the
// front end can easily display the parameter labels
const regex = /(?=[A-Z])/;

// creates presentable label
const label = key.split(regex).join(' ').toLowerCase();

// grabs the value for the parameter from the
// definition object in response
const value = def[key];

// constructs new object to send to frontend
let newObj = {
label,
value,
isString: typeof value === 'string' ? true : false
};

definitionArray.push(newObj)
})

results.push(definitionArray)
})
// END NEW CODE

return {
statusCode: 200,
body: JSON.stringify(response.data), // modified
headers: {
'Access-Control-Allow-Origin': '*'
}
}
} catch (err) {
console.log(err)
return { statusCode: 500, body: err.toString() }
}
}

To see the difference in the response you can enter the URL above into the browser again. The data object that the front-end code can expect is structured like:

[ // array
[ // array
{ // object
"label":"definition",
"value":"a motor vehicle with four wheels; usually propelled by an internal combustion engine",
"isString":true
},
{
"label":"part of speech",
"value":"noun",
"isString":true
},
...
]
]

4. Create a Front-End Javascript Function

We are going to use jQuery to select the form by it’s id= and add a submit listener. When the form is submitted, we will have a function that executes.

The front-end function will call our serverless function. After the data is returned, the front-end function:

  • Loops over the data to create list items <li>
  • Creates a <div> that will have the formatted word data inside

This may seem strange if you are unfamiliar with jQuery, but you can read more about the functions used in the code in the jQuery documentation.

Again, this will be a lot of code (and a lot of comments), but read through it line-by-line and use my comments to understand what is being done at each stage.

// Specifies a function to execute when the DOM is fully loaded.
$(document).ready(function(){

// adds a submit listened to our <form> element
$("form").submit(async (event) => {

// prevents the page from reloading on subject
event.preventDefault();

// adds the text 'Loading...' to our word
// data container for UX purposes
$('#word-info').html('Loading...');

// collects the value in the input form element
// by the id on the element
const word = $("#word-input").val();

// creates a variable that represents our
// word info container
let wordInfoList = document.querySelector('#word-info');

try {

// asynchronously calls our custome function
const data = await (await fetch(`http://localhost:9000/.netlify/functions/getWord?word=${word}`, { mode: 'cors'})).json();

// logs no results if word data is not found
if (data.length < 1) {
return wordInfoList.appendChild(document.createTextNode('No results matched.'));
}

// clears the word container if it had
// previous data
$('#word-info').empty();

data.map(val => {
// creates parent li element
const li = document.createElement('li');
li.classList.add('my-4', 'p-4', 'list-item');

// loops over the values for each definition
val.map(property => {
if (property.label === 'definition') {
// creates new heading-3 element
const def = document.createElement('h3');

// adds text to the element
def.innerText = property.value;

// appends class value for styling
def.classList.add(['definition']);

// adds the element to our list item
li.appendChild(def);
} else if (property.isString) {
const partOfSpeech = document.createElement('small');
partOfSpeech.innerText = property.value;
partOfSpeech.classList.add('lead','font-italic');
li.appendChild(partOfSpeech);
} else {
const characteristic = document.createElement('dl');
characteristic.className = 'row';
const label = document.createElement('dt');
label.innerText = property.label;
label.className = 'col-sm-3';
const value = document.createElement('dd');
value.innerText = property.value.join(', ');
value.className = 'col-sm-9';
characteristic.appendChild(label);
characteristic.appendChild(value);
li.appendChild(characteristic);
}
})

// appends the list item fully formed to
// the word data container
wordInfoList.appendChild(li);
})
} catch (e) {
// logs the error if one exists
console.log(e);

// displays message to user if there is an error
$('#word-info').html('There was an error fetching the word data');
}
});
});

This function is not fully optimized, and creating the HTML elements could be broken down into smaller functions, but this function will go a long way.

Now if you were to test different words in the RapidAPI dashboard you might notice that the keys change with different definitions. Due to the way we wrote our serverless function — combined with our front-end function — we avoided having to code/destructure all the different parameters that could end up on the individual objects. Personally, I am a big fan of this set-up.

There are some optimizations that could be made, but our app is small and the words that we look up will typically not have more than ten definitions. This mitigates some of the potential downsides.

Our functions are complete! Entering a word in the input section, and pressing submit, will return word data if the word exists!

5. Push to Github

Before we push the code to Github we want to make sure that we hide our API key and set up a .gitignore file. To hide the API key, replace the value with process.env.RAPIDAPI_KEY. This will break our development setup so for now, you can stop the lambda function running on port 9000.

Your function should now have the modified line:

"x-rapidapi-key":process.env.RAPIDAPI_KEY

In main.js, change the URL for the HTTP request. Remove the localhost part because that will not work when we have an actual URL. The modified line will now be:

const data = await (await fetch(`/.netlify/functions/getWord?word=${word}`, { mode: 'cors'})).json();

Initialize a new git repository by running git init at the project root.

Create the .gitignore file at the root of the project. Add the two folders that we want to ignore in the file:

(if you are using VSCode you can add .vscode/* to the list as well.

functions 
node_modules

Execute npm run lambda-build just to be sure that our serverless function is up to date.

Stage the files for the new repository by running (in the terminal):

then

git commit -m "initial commit"

Log in to your Github account and create a new repository.

Add the remote to the repository:

git remote add origin https://github.com/youruser/nameofrepo.git

Push the code to the new repo:

git push -u origin master

6. Deploy to Netlify (optional)

Log in to Netlify.

Click the button on the page that says New Site from Git.

You will have to authorize Netlify to access your Github repositories.

After authorization, there will be a list of your Github repositories. Select the one that we just created.

On the next page, it will ask for a build step and publish directory. We do not have a build step so leave that blank.

Add public to the publish directory input. Deploy the site.

It will build quickly and there will be a link for your new site!

If you click on the link you should see our app, but it won’t work yet because we didn’t add our API key to the environment variables.

In Site, Settings select the Build & Deploy tab. Scroll towards the bottom and add our key to the environment variables section.

Remember, it needs to equal the value that we placed in the serverless function.

After adding the environment variable you can trigger a new deploy from the Deploys tab.

Finally, visit your new site and test it out!

We now have a secure application (notice the HTTPS) that anyone with an internet connection could visit and use. The final application uses minimal Javascript, serverless functions, and RapidAPI to quickly retrieve word information.

7. Delete Site (optional)

You can choose to keep your site up and monitor API usage on the RapidAPI dashboard, but if you do not plan to use the site I would recommend deleting it off of Netlify. This can be done in Site Settings. Scroll to the bottom and select the button Delete Site.

Conclusion

You can view my final code base on Github.

I have a few suggestions if you want to take the app a step further.

  • Try to refactor the jQuery function in the front-end
  • Extend the serverless function to include an API call to the ‘rhymes’ endpoint.

Remember, there are still other great dictionary APIs to explore that can offer their own recipe for useful data. You could also try to replicate this application with one of the other API options.

Thanks for joining me on this little adventure and I hope you feel accomplished!

Originally published at https://rapidapi.com on February 18, 2020.

--

--