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

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}`));
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.
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.
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!