Dictionary App with JavaScript by Cristian Villafane

Dictionary App: A Javascript Tutorial by Cristian Villafane

A practical guide to fetching data from a free dictionary API, handling complex JSON, and building a dynamic, real-world application.

Creating a dictionary app is a step above a simple weather app, presenting a fantastic challenge for handling more complex data structures. It’s a perfect project for mastering HTML for structure, CSS for styling, and the asynchronous capabilities of JavaScript. In this guide on Javascript by Cristian Villafane, we will build a functional and modern dictionary app with a crucial autocomplete feature.

Phase 1: The Structure – HTML for the Interface

Every web application starts with a solid HTML structure. For our dictionary app, we need an input field for the word, a search button, a container for autocomplete suggestions, and a display area for the results, which will include the word, phonetics, audio, definitions, and more.

Designing the User Interface

We’ll create a main container with a search area and a display card. The UI will consist of:

  • Word Input: An <input type="text"> that allows the user to type a word. It will trigger a search for suggestions as the user types.
  • Search Button: A <button> integrated into the input field for a clean look.
  • Autocomplete List: A <div> that will be dynamically populated with word suggestions.
  • Display Card: A dedicated <div> to hold all the dictionary information.
<!-- Search Container -->
<div class="search-container">
  <input type="text" id="word-input" placeholder="Enter a word...">
  <button id="search-btn">
    <!-- SVG Search Icon -->
  </button>
  <div class="autocomplete-suggestions" id="autocomplete-list"></div>
</div>

<!-- Dictionary Display -->
<div class="dictionary-display" id="dictionary-display">
  <!-- Content injected by JS -->
</div>

Phase 2: The Logic – Two APIs for a Better Experience

This is where our application comes to life. To provide a great user experience with autocomplete, we will use two different APIs: one for word suggestions and another for the full dictionary data.

  • Autocomplete: We’ll use the Datamuse API (api.datamuse.com) for fast word suggestions as the user types. It’s free and requires no API key.
  • Definitions: Once a word is selected, we’ll use the Free Dictionary API (dictionaryapi.dev) to fetch detailed information.

Understanding the Dictionary API Response (JSON)

When we request a word from dictionaryapi.dev, it sends back a complex JSON object, often an array. This structure is more nested than a typical weather API response. A simplified version for the word “hello” looks like this:

[
  {
    "word": "hello",
    "phonetic": "/həˈloʊ/",
    "phonetics": [
      {
        "text": "/həˈloʊ/",
        "audio": "https://api.dictionaryapi.dev/media/pronunciations/en/hello-us.mp3"
      }
    ],
    "meanings": [
      {
        "partOfSpeech": "exclamation",
        "definitions": [
          {
            "definition": "Used as a greeting or to begin a phone conversation.",
            "example": "hello there, Katie!"
          }
        ],
        "synonyms": ["hi", "greetings"],
        "antonyms": ["goodbye"]
      }
    ]
  }
]

Notice the nested arrays for phonetics and meanings. To get the audio URL, we would access data[0].phonetics[0].audio. To get the first definition, we would use data[0].meanings[0].definitions[0].definition. Our JavaScript needs to navigate this structure carefully.

Phase 3: Interactivity and Displaying the Data

A great app feels responsive and fast. We’ll connect our logic to user events and ensure the experience is smooth.

Event Listeners and User Experience

We have multiple event listeners: one for the input event on the text field for autocomplete, another for the click event on the search button, and a listener for the `Enter` key. We also handle clicks on the suggestion items. A default word is loaded on page open to provide an immediate example.

Dictionary App in Action

Below, you can try out the live dictionary app. Enter a word and see how the autocomplete suggests options. Select one to view its full definition, pronunciation, and more. This is the final result of applying the concepts from this tutorial.

The Complete Dictionary App Code

Here is the self-contained, performance-optimized code for the dictionary app. You can copy and paste this into your own project. It uses free APIs that don’t require keys.

<!-- HTML Structure -->
<div class="dictionary-container">
    <div class="search-container">
        <input type="text" id="word-input" placeholder="Enter a word..." aria-label="Word">
        <button id="search-btn" aria-label="Search">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
        </button>
        <div class="autocomplete-suggestions" id="autocomplete-list"></div>
    </div>
    <div id="dictionary-display" class="dictionary-display">
        <!-- Dictionary data will be dynamically inserted here -->
    </div>
</div>

<!-- JavaScript Logic -->
<script>
document.addEventListener('DOMContentLoaded', () => {
    const container = document.querySelector('.dictionary-container:not(:has(#word-input-live))');
    if (!container) return;

    const wordInput = container.querySelector('#word-input');
    const searchBtn = container.querySelector('#search-btn');
    const dictionaryDisplay = container.querySelector('#dictionary-display');
    const autocompleteList = container.querySelector('#autocomplete-list');

    const fetchWordData = async (word) => {
        dictionaryDisplay.innerHTML = '<p style="text-align:center; padding: 2rem;">Loading...</p>';
        const apiUrl = `https://api.dictionaryapi.dev/api/v2/entries/en/${word}`;

        try {
            const response = await fetch(apiUrl);
            if (!response.ok) {
                const errorData = await response.json();
                throw new Error(errorData.title || 'Word not found. Please try again.');
            }
            const data = await response.json();
            displayWordData(data, dictionaryDisplay);
        } catch (error) {
            displayError(error.message, dictionaryDisplay);
        }
    };

    const fetchAutocomplete = async (query, listElement) => {
        if (query.length < 2) {
            listElement.innerHTML = '';
            listElement.style.display = 'none';
            return;
        }
        const apiUrl = `https://api.datamuse.com/sug?s=${query}`;
        try {
            const response = await fetch(apiUrl);
            if (!response.ok) return;
            const suggestions = await response.json();
            displayAutocomplete(suggestions, listElement);
        } catch (error) {
            console.error('Autocomplete error:', error);
            listElement.style.display = 'none';
        }
    };
    
    const displayWordData = (data, displayElement) => {
        const entry = data[0];
        const phoneticInfo = entry.phonetics.find(p => p.audio) || entry.phonetics[0];
        
        let meaningsHtml = entry.meanings.map(meaning => `
            <div class="meaning-block">
                <h3 class="part-of-speech">${meaning.partOfSpeech}</h3>
                <ol class="definition-list">
                    ${meaning.definitions.map(def => `
                        <li class="definition-item">
                            ${def.definition}
                            ${def.example ? `<p class="definition-example">e.g., "${def.example}"</p>` : ''}
                        </li>
                    `).join('')}
                </ol>
                ${meaning.synonyms && meaning.synonyms.length > 0 ? `
                    <div class="synonyms-antonyms">
                        <h4>Synonyms:</h4>
                        ${meaning.synonyms.map(s => `<span>${s}</span>`).join('')}
                    </div>
                ` : ''}
                ${meaning.antonyms && meaning.antonyms.length > 0 ? `
                    <div class="synonyms-antonyms">
                        <h4>Antonyms:</h4>
                        ${meaning.antonyms.map(a => `<span>${a}</span>`).join('')}
                    </div>
                ` : ''}
            </div>
        `).join('');

        displayElement.innerHTML = `
            <div class="word-header">
                <div>
                    <h2 class="word-title">${entry.word}</h2>
                    <p class="phonetic">${phoneticInfo ? phoneticInfo.text : 'N/A'}</p>
                </div>
                ${phoneticInfo && phoneticInfo.audio ? `
                    <button class="audio-btn" onclick="this.querySelector('audio').play()" aria-label="Play audio">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
                        <audio src="${phoneticInfo.audio}"></audio>
                    </button>
                ` : ''}
            </div>
            ${meaningsHtml}
        `;
    };

    const displayAutocomplete = (suggestions, listElement) => {
        if (!suggestions || suggestions.length === 0) {
            listElement.style.display = 'none';
            return;
        }
        listElement.innerHTML = suggestions.map(s => 
            `<div class="suggestion-item" data-word="${s.word}">${s.word}</div>`
        ).join('');
        listElement.style.display = 'block';
    };

    const displayError = (message, displayElement) => {
        displayElement.innerHTML = `<p id="error-message">Error: ${message}</p>`;
    };
    
    const performSearch = (word) => {
        if (word) {
            fetchWordData(word);
            wordInput.value = word;
            autocompleteList.style.display = 'none';
        }
    };

    searchBtn.addEventListener('click', () => performSearch(wordInput.value));
    wordInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') performSearch(wordInput.value);
    });
    wordInput.addEventListener('input', () => fetchAutocomplete(wordInput.value, autocompleteList));

    autocompleteList.addEventListener('click', (e) => {
        if (e.target.classList.contains('suggestion-item')) {
            const word = e.target.dataset.word;
            performSearch(word);
        }
    });
    
    document.addEventListener('click', (e) => {
        if (!e.target.closest('.search-container')) {
            autocompleteList.style.display = 'none';
        }
    });

    fetchWordData('keyboard');
});
</script>

I hope you found this tutorial on Javascript by Cristian Villafane useful. Building a dictionary app is an excellent way to practice handling more complex API responses and creating a more sophisticated UI. Use this project as a starting point to explore and add new features, like saving favorite words or displaying usage source URLs. The only limit is your curiosity! 📚

Happy coding, Cristian Villafane