Part 1 : Contact Management System with Node.js, Express and ReactJS

0
37

In the modern world of web development, creating a contact management system is one of the most common and useful projects for any developer. This type of system allows users to store, edit, delete, and even export contact data efficiently. In this article, we’ll walk through how to build a simple yet powerful contact management system using Node.js and Express.

Our focus will be on the backend API that serves as the core of our contact management application. By the end of this article, you will have a working API that can manage contacts, allowing for the addition, modification, deletion, and even the exporting of contacts into a downloadable zip file. This system uses a JSON file as a simple storage medium, making it perfect for quick development and learning.

Go To : Part 2 : Contact Management System with Node.js, Express and ReactJS

Follow this video for complete guidance :

Source Code – Contact Management System API

import express, { Request, Response } from 'express';
import path from 'path';
import fs, { appendFile } from 'fs';
import { v4 as uuidv4 } from 'uuid';
import { z } from 'zod';
import cors from 'cors';
import archiver from 'archiver';

interface Contact {
    id: string,
    name: string,
    phone: string,
    email: string,
    bookmark: boolean
};

const contactSchema = z.object({
    name: z.string().min(1, "Name is required"),
    phone: z.string().min(1, "phone is required"),
    email: z.string().email("Invalid email"),
    bookmark: z.boolean().optional()
})


const PORT = 3000;
const app = express();
app.use(cors());
app.use(express.json());

const contactsFilePath = path.join(__dirname, "contacts.json");
if (!fs.existsSync(contactsFilePath)) {
    fs.writeFileSync(contactsFilePath, JSON.stringify([]));
}

const readContactsFromFile = () => {
    const fileData = fs.readFileSync(contactsFilePath, "utf-8");
    return JSON.parse(fileData);
}
const writeContactsToFile = (contacts) => {
    fs.writeFileSync(contactsFilePath, JSON.stringify(contacts, null, 2));
}

app.get('/contacts', (req: Request, res: Response) => {
    const contacts = readContactsFromFile();
    res.json(contacts);
});

app.post('/contacts', (req: Request, res: Response) => {
    try {
        const contact = contactSchema.parse(req.body);
        const newContact = {
            id: uuidv4(),
            ...contact,
            bookmark: contact.bookmark ?? false
        };
        const contacts = readContactsFromFile();
        contacts.push(newContact);
        writeContactsToFile(contacts);
        res.status(201).json(newContact);
    } catch (error) {
        if (error instanceof z.ZodError) {
            res.status(400).json({ errors: error.errors });
        } else {
            res.status(500).json({ message: "Server Error" });
        }
    }

});

app.patch('/contacts/:id', (req: Request, res: Response) => {
    try {
        const contactId = req.params.id;
        const contact = contactSchema.parse(req.body);
        const contacts = readContactsFromFile();
        const index = contacts.findIndex((c) => c.id === contactId);
        if (index === -1) {
            return res.status(404).json({ message: "Contact Not Found" });
        }
        contacts[index] = { ...contacts[index], ...contact };
        writeContactsToFile(contacts);
        res.json(contacts[index]);
    } catch (error) {
        if (error instanceof z.ZodError) {
            res.status(400).json({ errors: error.errors });
        } else {
            res.status(500).json({ message: "Server Error" });
        }
    }
});

app.delete('/contacts/:id', (req: Request, res: Response) => {
    const contactId = req.params.id;
    const contacts = readContactsFromFile();
    const updatedContacts = contacts.filter((contact) => contact.id !== contactId);
    if (updatedContacts.length === contacts.length) {
        return res.json({ message: "Contact not found" });
    }
    writeContactsToFile(updatedContacts);
    res.status(204).send();
});

app.get('/contacts/export', async (req: Request, res: Response) => {
    try {
        const contacts = readContactsFromFile();
        const vcfDir = path.join(__dirname, "exports");
        if (!fs.existsSync(vcfDir)) {
            fs.mkdirSync(vcfDir);
        }

        res.setHeader('Content-Type', 'application/zip');
        res.setHeader('Content-Disposition', 'attachment; filename=contacts.zip');

        const archive = archiver('zip', {
            zlib: { level: 9 }
        });
        archive.pipe(res);
        archive.on('error', (err) => {
            throw err;
        });

        for (const contact of contacts) {
            const vcfContact = `BEGIN:VCARD
            VERSION:3.0
            FN:${contact.name}
            TEL:${contact.phone}
            EMAIL:${contact.email}
            END:VCARD`;
            archive.append(vcfContact, { name: `${contact.id}.vcf` });
        }

        await archive.finalize();
    }
    catch (error) {
        console.log("Export error:" + error);
        if (!res.headersSent) {
            res.status(500).json({ message: "Error exporting contacts" });
        }
    }
    finally {
        if (fs.existsSync(path.join(__dirname, "exports"))) {
            fs.rmSync(path.join(__dirname, "exports"), { recursive: true });
        }
    }
});

app.listen(PORT, () => console.log(`Server is running at http://localhost:${PORT}`));

 

ALSO READ  Design Full Page Overlay Advertisement using HTML, CSS and JavaScript

Project Overview

The contact management system we will be building will have the following features:

  • CRUD Operations (Create, Read, Update, Delete) for managing contact data.
  • Validation of the data sent to the API to ensure consistency and correctness.
  • Export Feature to allow the user to export their contacts as .vcf (vCard) files, which will then be zipped for easy download.

Setting up the Project

Before diving into the code, let’s look at what tools we’ll be using for this backend project:

  • Node.js and Express: To set up the web server and handle HTTP requests.
  • UUID: For generating unique identifiers for each contact.
  • Zod: A schema validation library to ensure the correctness of user inputs.
  • Archiver: For zipping files and making the contact export functionality a breeze.
  • File System (fs): To read and write contact data stored in a simple JSON file.

Now, let’s discuss the core parts of the API.

Contact Data Model

For simplicity, we’ll store contacts in a basic JSON file (contacts.json) that holds an array of objects. Each contact will have the following fields:

  • id: A unique identifier for each contact (generated with UUID).
  • name: The name of the contact.
  • phone: The phone number of the contact.
  • email: The email address of the contact.
  • bookmark: An optional boolean to mark a contact as bookmarked.

This data will be stored in a flat file, making it easy to access and modify without the overhead of a database. However, for production systems, it’s recommended to use a proper database solution.

API Endpoints

  • GET /contacts – This endpoint retrieves all contacts from the JSON file and returns them in the response.
  • POST /contacts – This endpoint accepts contact data in the request body, validates it using Zod, and then adds the new contact to the JSON file.
  • PATCH /contacts/:id – This endpoint allows updating an existing contact by ID. It ensures that only valid fields are modified.
  • DELETE /contacts/:id – This endpoint deletes a contact by ID from the JSON file.
  • GET /contacts/export – This endpoint generates a downloadable .zip file containing all contacts in .vcf format. Each contact is saved as a vCard file, which is a widely used format for contact information.
ALSO READ  Part 2 : Creating a Dynamic File Manager with PHP

Key Features Explained

Schema Validation with Zod

One of the main challenges when building any API is ensuring that the data being sent by users is valid. We’ve used Zod to enforce validation rules for contact fields such as name, phone, and email. This ensures that users can’t send empty or malformed data to the API.

For example, the email field must be a valid email format, and the name and phone fields must not be empty. By using Zod, we can quickly catch errors and return meaningful feedback to the user.

Exporting Contacts

One standout feature of this contact management system is the ability to export contacts. The API provides an endpoint that creates a .zip file of .vcf (vCard) files. Each contact is exported in the widely accepted vCard format, which can easily be imported into other contact management systems or email clients.

To achieve this, we use the Archiver library to create the zip file dynamically and stream it to the user for download. This is a great way to make data sharing simple and efficient for users.

File Handling with fs

The contact data is stored in a local file (contacts.json) for simplicity. The file is read and written each time the data is modified. This allows us to persist contact data even when the server restarts. However, for larger applications with a high volume of data, a database would be a more scalable solution.

Error Handling

We’ve made sure to implement proper error handling throughout the API. Whether it’s a failed schema validation, a contact not being found, or an issue with file handling, the system gracefully handles errors and returns appropriate responses to the user.

ALSO READ  Unleash Your Creativity: Build a Stunning Colour Generator with HTML, CSS, and JavaScript

For example, if a user tries to delete a contact that doesn’t exist, the server will respond with a 404 status and a message saying “Contact Not Found.”

Next Steps: Building the Frontend

Once you have this backend API running, the next logical step is to build a frontend to interact with it. In the next article, we’ll be diving into how you can use React.js to create a user interface that consumes these APIs and provides an intuitive and dynamic experience for managing contacts.

With React, you’ll be able to build forms for adding and updating contacts, display the contact list dynamically, and even trigger the export functionality—all while interacting with the backend API.

In this article, we’ve built a basic yet functional contact management system using Node.js and Express. We’ve created endpoints to manage contacts, validated user input with Zod, and implemented a feature to export contacts as vCard files inside a zip archive.

This project serves as a great starting point for anyone looking to understand the basics of API development, data validation, file handling, and exporting data in different formats. It’s also the perfect foundation for further extending functionality and integrating with a frontend framework like React.js.

Stay tuned for the next article where we’ll cover how to build a frontend using React.js to consume these APIs and bring our contact management system to life!

Comments are closed.