Build a Node.js Interactive Web-Based Simple Note-Taking App

Written by

in

In today’s fast-paced world, the ability to quickly jot down ideas, reminders, and important information is crucial. While numerous note-taking apps exist, building your own offers a unique opportunity to learn fundamental web development concepts using Node.js. This tutorial will guide you through creating a simple, interactive note-taking application from scratch. We’ll cover everything from setting up your project to implementing features like adding, deleting, and displaying notes. This project is ideal for beginners and intermediate developers looking to enhance their Node.js and web development skills.

Why Build a Note-Taking App?

Creating a note-taking app is an excellent learning experience for several reasons:

  • Practical Application: It’s a real-world project with immediate usability. You can use your app to manage your own notes.
  • Fundamental Concepts: It involves core web development concepts like handling user input, storing data, and dynamically updating the user interface.
  • Node.js Proficiency: It provides hands-on experience with Node.js, Express.js (a Node.js web application framework), and potentially a database (like JSON files for simplicity).
  • Customization: You have complete control over the design and features, tailoring the app to your specific needs.

Prerequisites

Before we begin, ensure you have the following installed on your system:

  • Node.js and npm (Node Package Manager): These are essential for running JavaScript on the server-side and managing project dependencies. You can download them from the official Node.js website (nodejs.org).
  • A Code Editor: Choose your preferred code editor (VS Code, Sublime Text, Atom, etc.).
  • Basic Knowledge of HTML, CSS, and JavaScript: Familiarity with these languages is necessary to understand the front-end aspects of the application.

Setting Up the Project

Let’s start by creating a new project directory and initializing it with npm. Open your terminal or command prompt and execute the following commands:

mkdir note-taking-app
cd note-taking-app
npm init -y

The npm init -y command creates a package.json file, which manages your project’s dependencies and metadata. Next, install the required dependencies: Express.js for creating the web server and body-parser for parsing request bodies.

npm install express body-parser

Project Structure

Create the following directory structure for your project:


note-taking-app/
├── app.js
├── public/
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── script.js
│   └── index.html
├── notes.json
└── package.json

Explanation:

  • app.js: The main server-side JavaScript file where the server logic resides.
  • public/: This directory will hold all the static assets for the client-side (HTML, CSS, JavaScript).
  • public/css/style.css: Your CSS stylesheet for styling the app.
  • public/js/script.js: Your JavaScript file for handling client-side interactions.
  • public/index.html: The main HTML file for the app’s user interface.
  • notes.json: A file to store your notes (we’ll use this as a simple database).

Building the Server (app.js)

Open app.js and add the following code:


const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');

const app = express();
const port = 3000;

app.use(express.static('public')); // Serve static files from the 'public' directory
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

const notesFilePath = path.join(__dirname, 'notes.json');

// Load notes from JSON file
let notes = [];

fs.readFile(notesFilePath, 'utf8', (err, data) => {
  if (err) {
    // If the file doesn't exist or there's an error, initialize an empty array
    console.error('Error reading notes.json:', err);
    notes = [];
  } else {
    try {
      notes = JSON.parse(data);
    } catch (parseError) {
      console.error('Error parsing notes.json:', parseError);
      notes = []; // Handle parsing errors
    }
  }
});

// Route to serve the index.html file
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// API endpoint to get all notes
app.get('/api/notes', (req, res) => {
  res.json(notes);
});

// API endpoint to add a new note
app.post('/api/notes', (req, res) => {
  const newNote = req.body;
  newNote.id = Date.now(); // Generate a unique ID
  notes.push(newNote);

  // Write notes to JSON file
  fs.writeFile(notesFilePath, JSON.stringify(notes), (err) => {
    if (err) {
      console.error('Error writing to notes.json:', err);
      return res.status(500).json({ message: 'Failed to save note' });
    }
    res.status(201).json(newNote);
  });
});

// API endpoint to delete a note
app.delete('/api/notes/:id', (req, res) => {
  const noteId = parseInt(req.params.id);
  notes = notes.filter(note => note.id !== noteId);

  // Write notes to JSON file
  fs.writeFile(notesFilePath, JSON.stringify(notes), (err) => {
    if (err) {
      console.error('Error writing to notes.json:', err);
      return res.status(500).json({ message: 'Failed to delete note' });
    }
    res.status(200).json({ message: 'Note deleted' });
  });
});

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

Explanation:

  • Import Modules: We import necessary modules like express, body-parser, fs (file system), and path.
  • Initialize Express App: Create an Express application instance and set the port.
  • Middleware:
    • express.static('public'): Serves static files (HTML, CSS, JavaScript) from the public directory.
    • body-parser.urlencoded({ extended: true }): Parses URL-encoded request bodies, typically used for form submissions.
    • body-parser.json(): Parses JSON request bodies.
  • Load Notes: Loads notes from notes.json when the server starts. Handles potential errors if the file doesn’t exist or is invalid.
  • Routes:
    • GET /: Serves the index.html file.
    • GET /api/notes: Returns all notes in JSON format.
    • POST /api/notes: Adds a new note to the notes array and saves it to notes.json.
    • DELETE /api/notes/:id: Deletes a note with the specified ID and updates notes.json.
  • Start Server: Starts the server and listens on the specified port (3000 in this case).

Building the Front-End (index.html)

Create public/index.html and add the following HTML structure:





    
    
    <title>Simple Note-Taking App</title>
    


    <div class="container">
        <h1>Note-Taking App</h1>
        <div class="note-form">
            <textarea id="note-input"></textarea>
            <button id="add-note-button">Add Note</button>
        </div>
        <div id="note-list"></div>
    </div>
    


Explanation:

  • Basic HTML Structure: Sets up the basic HTML structure, including the head and body sections.
  • Title and Stylesheet: Includes the title and links to the style.css stylesheet.
  • Container: A div with the class “container” to hold the entire app.
  • Heading: An h1 heading for the app’s title.
  • Note Form: A div with the class “note-form” that contains a textarea for note input and an “Add Note” button.
  • Note List: A div with the id “note-list” where the notes will be displayed.
  • JavaScript: Includes the script.js file to handle the app’s functionality.

Styling the App (style.css)

Create public/css/style.css and add some basic styling to make the app visually appealing:


body {
    font-family: sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

.container {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    width: 80%;
    max-width: 600px;
}

h1 {
    text-align: center;
    color: #333;
}

.note-form {
    margin-bottom: 20px;
}

textarea {
    width: 100%;
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    resize: vertical;
}

button {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
}

button:hover {
    background-color: #3e8e41;
}

.note-item {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    margin-bottom: 10px;
    background-color: #f9f9f9;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.note-item p {
    margin: 0;
    word-break: break-word; /* Prevents long words from breaking the layout */
    flex-grow: 1; /* Allows the paragraph to take up available space */
    padding-right: 10px; /* Adds some space between the text and the delete button */
}

.delete-button {
    background-color: #f44336;
    color: white;
    border: none;
    padding: 5px 10px;
    border-radius: 4px;
    cursor: pointer;
}

.delete-button:hover {
    background-color: #da190b;
}

This CSS provides basic styling for the layout, text input, buttons, and note display.

Implementing the Client-Side Logic (script.js)

Create public/js/script.js and add the following JavaScript code to handle user interactions and display notes:


const noteInput = document.getElementById('note-input');
const addNoteButton = document.getElementById('add-note-button');
const noteList = document.getElementById('note-list');

// Function to fetch and display notes
async function getNotes() {
    try {
        const response = await fetch('/api/notes');
        const notes = await response.json();
        displayNotes(notes);
    } catch (error) {
        console.error('Error fetching notes:', error);
    }
}

// Function to display notes
function displayNotes(notes) {
    noteList.innerHTML = ''; // Clear existing notes
    notes.forEach(note => {
        const noteItem = document.createElement('div');
        noteItem.classList.add('note-item');
        noteItem.innerHTML = `
            <p>${note.text}</p>
            <button class="delete-button" data-id="${note.id}">Delete</button>
        `;
        noteList.appendChild(noteItem);

        // Add event listener to delete button
        const deleteButton = noteItem.querySelector('.delete-button');
        deleteButton.addEventListener('click', () => deleteNote(note.id));
    });
}

// Function to add a new note
async function addNote() {
    const noteText = noteInput.value.trim();
    if (noteText === '') return; // Don't add empty notes

    try {
        const response = await fetch('/api/notes', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: noteText })
        });

        if (response.ok) {
            noteInput.value = ''; // Clear the input field
            getNotes(); // Refresh the note list
        } else {
            console.error('Failed to add note');
        }
    } catch (error) {
        console.error('Error adding note:', error);
    }
}

// Function to delete a note
async function deleteNote(id) {
    try {
        const response = await fetch(`/api/notes/${id}`, {
            method: 'DELETE'
        });

        if (response.ok) {
            getNotes(); // Refresh the note list
        } else {
            console.error('Failed to delete note');
        }
    } catch (error) {
        console.error('Error deleting note:', error);
    }
}

// Event listeners
addNoteButton.addEventListener('click', addNote);

// Initial load of notes
getNotes();

Explanation:

  • Get DOM Elements: Selects the necessary HTML elements using their IDs.
  • getNotes(): Fetches notes from the server (/api/notes) using the fetch API and calls displayNotes() to render them.
  • displayNotes(notes): Clears the existing note list and dynamically creates HTML elements for each note. It then attaches a click event listener to each delete button.
  • addNote(): Gets the note text from the input field, sends a POST request to /api/notes to add the note, clears the input field, and refreshes the note list.
  • deleteNote(id): Sends a DELETE request to /api/notes/:id to delete the note with the specified ID and refreshes the note list.
  • Event Listeners: Attaches an event listener to the “Add Note” button.
  • Initial Load: Calls getNotes() when the page loads to display existing notes.

Running the Application

To run your application, open your terminal or command prompt, navigate to your project directory (note-taking-app), and run the following command:


node app.js

This will start the Node.js server. Open your web browser and go to http://localhost:3000. You should see your note-taking app. You can now add notes, and they will be displayed on the screen. You should also be able to delete them.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • Server Not Running: Make sure the Node.js server (app.js) is running in your terminal. You should see a message like “Server listening on port 3000” in the console.
  • CORS Errors: If you’re encountering CORS (Cross-Origin Resource Sharing) errors, it means the browser is blocking requests from your front-end to your back-end (which is on the same origin in this case). This is less likely in this simple setup, but if it happens, ensure your server configuration allows requests from your origin (http://localhost:3000). You might need to install and use the cors middleware in your server (npm install cors, then app.use(cors()) in app.js).
  • File Paths: Double-check your file paths in both your server-side (app.js) and client-side (script.js) code to ensure they point to the correct locations of your files.
  • Typographical Errors: Carefully review your code for any typos, especially in variable names, function names, and HTML element IDs. These can cause unexpected behavior.
  • Incorrect Data Handling: Make sure your server-side code correctly parses and saves the data from your client-side requests. Use console.log() statements to inspect the data being sent and received.
  • JSON Parsing Errors: If you’re having trouble reading or writing to your notes.json file, check for errors in the JSON format. Use a JSON validator to validate your file.
  • Network Errors: Use your browser’s developer tools (usually accessed by pressing F12) to check the “Network” tab for any errors related to the API requests. This can help you identify issues with your server-side routes or data transmission.

Enhancements and Next Steps

Once you have the basic note-taking app working, consider adding these features to expand its functionality:

  • Note Editing: Implement the ability to edit existing notes.
  • Rich Text Editor: Integrate a rich text editor (like TinyMCE or Quill) for formatting notes.
  • Note Categories/Tags: Add the ability to categorize or tag notes for better organization.
  • Search Functionality: Allow users to search for notes by keyword.
  • User Authentication: Implement user accounts and authentication to allow multiple users to use the app.
  • Database Integration: Instead of using notes.json, use a database like MongoDB or PostgreSQL to store the notes. This allows for scalability and more robust data management.
  • Deployment: Deploy your application to a platform like Heroku or Netlify to make it accessible online.

Key Takeaways

This tutorial has provided a practical introduction to building a simple note-taking application using Node.js, Express.js, and basic HTML, CSS, and JavaScript. You’ve learned how to set up a Node.js project, create a server, handle API requests, and interact with the file system to store and retrieve data. You’ve also gained experience with front-end development by building the user interface and handling user interactions. This project serves as a solid foundation for further exploring web development and Node.js. By understanding these fundamentals, you can build more complex web applications and continue to expand your skills.

The journey of building your own applications is a continuous learning process. Each feature you add, each bug you fix, and each challenge you overcome strengthens your understanding and expands your capabilities. Embrace the learning process, experiment with new technologies, and don’t be afraid to make mistakes. The more you build, the more confident and skilled you will become. Take the knowledge you’ve gained from this project and apply it to your future coding endeavors. The possibilities are endless, and the only limit is your imagination.