Low-Level Design: Polling System - Using Nodejs

WBOY
Release: 2024-08-31 13:03:07
Original
194 people have browsed it

Low-Level Design: Polling System - Using Nodejs

Table of Contents

  1. Database Setup
    • MySQL Database Schema
    • ERD for the Polling System
  2. Backend Setup
    • Step 1: Initialize the Project
    • Step 2: Project Structure
  3. API Implementation
    • Step 1: Database Connection (db/db.js)
    • Step 2: Environment Variables (.env)
    • Step 3: Poll Controller (controllers/pollController.js)
    • Step 4: Poll Routes (routes/pollRoutes.js)
    • Step 5: Server Entry Point (index.js)
  4. Error Handling
  5. Testing
  6. Conclusion

Please refer to the article Polling System Basic Low-Level Design - I

Let's break down the entire process into detailed steps, including the database setup, API implementation using Node.js with Express, and interaction with MySQL. We will cover:

Database Setup

First, we'll define the schema for the MySQL database and create the necessary tables.

MySQL Database Schema

CREATE DATABASE polling_system; USE polling_system; CREATE TABLE polls ( poll_id INT AUTO_INCREMENT PRIMARY KEY, question VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE options ( option_id INT AUTO_INCREMENT PRIMARY KEY, poll_id INT, option_text VARCHAR(255) NOT NULL, FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE ); CREATE TABLE votes ( vote_id INT AUTO_INCREMENT PRIMARY KEY, poll_id INT, user_id VARCHAR(255) NOT NULL, option_id INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE, FOREIGN KEY (option_id) REFERENCES options(option_id) ON DELETE CASCADE );
Copy after login
  • polls table: Stores poll information with a unique identifier, question, and creation timestamp.

  • options table: Stores the options associated with a poll, linked via poll_id.

  • votes table: Records each vote, linking to the poll, option, and user.

    ERD for the Polling System

Entities:

  1. Polls: Represents the poll itself, with attributes like poll_id and question.
  2. Options: Represents the options available for each poll, with attributes like option_id and option_text.
  3. Votes: Represents the votes cast by users, with attributes like vote_id, user_id, and timestamps.

Relationships:

  1. One-to-Manybetween Polls and Options: Each poll can have multiple options.
  2. Many-to-Onebetween Votes and Options: Each vote is associated with one option.
  3. Many-to-Onebetween Votes and Polls: Each vote is linked to a specific poll.

Here’s a description of the ERD:

  1. Polls Table:

    • poll_id(Primary Key)
    • question
    • created_at
  2. Options Table:

    • option_id(Primary Key)
    • poll_id(Foreign Key referencing polls.poll_id)
    • option_text
  3. Votes Table:

    • vote_id(Primary Key)
    • poll_id(Foreign Key referencing polls.poll_id)
    • option_id(Foreign Key referencing options.option_id)
    • user_id
    • created_at

The relationships would be represented with lines between the entities:

  • PollsOptions: One poll can have many options.
  • OptionsVotes: One option can have many votes.
  • PollsVotes: One poll can have many votes.

Backend Setup

Let's set up a Node.js project using Express and MySQL.

Step 1: Initialize the Project

mkdir polling-system cd polling-system npm init -y npm install express mysql2 dotenv
Copy after login
  • express: A web framework for Node.js.
  • mysql2: A MySQL client for Node.js.
  • dotenv: For managing environment variables.

Step 2: Project Structure

Create a basic structure for the project:

polling-system/ │ ├── .env ├── index.js ├── db/ │ └── db.js ├── routes/ │ └── pollRoutes.js └── controllers/ └── pollController.js
Copy after login

API Implementation

Step 1: Database Connection

File - db/db.js

const mysql = require('mysql2/promise'); require('dotenv').config(); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); module.exports = pool;
Copy after login

Step 2: Environment Variables

File - .env

DB_HOST=localhost DB_USER=root DB_PASSWORD=yourpassword DB_NAME=polling_system PORT=3000
Copy after login

Step 3: Poll Controller

File - controllers/pollController.js

This file will implement all the necessary CRUD operations for the polling system.

const pool = require('../db/db'); // Create Poll exports.createPoll = async (req, res) => { const { question, options } = req.body; if (!question || !options || !Array.isArray(options) || options.length < 2) { return res.status(400).json({ message: "Invalid input data. Question and at least two options are required." }); } try { const connection = await pool.getConnection(); await connection.beginTransaction(); const [result] = await connection.execute( 'INSERT INTO polls (question) VALUES (?)', [question] ); const pollId = result.insertId; const optionQueries = options.map(option => { return connection.execute( 'INSERT INTO options (poll_id, option_text) VALUES (?, ?)', [pollId, option] ); }); await Promise.all(optionQueries); await connection.commit(); connection.release(); res.status(201).json({ pollId, message: "Poll created successfully." }); } catch (error) { console.error("Error creating poll:", error.message); res.status(500).json({ message: "Error creating poll." }); } }; // Update Poll exports.updatePoll = async (req, res) => { const { pollId } = req.params; const { question, options } = req.body; if (!pollId || !question || !options || !Array.isArray(options) || options.length < 2) { return res.status(400).json({ message: "Invalid input data. Question and at least two options are required." }); } try { const connection = await pool.getConnection(); await connection.beginTransaction(); const [pollResult] = await connection.execute( 'UPDATE polls SET question = ? WHERE poll_id = ?', [question, pollId] ); if (pollResult.affectedRows === 0) { await connection.rollback(); connection.release(); return res.status(404).json({ message: "Poll not found." }); } await connection.execute('DELETE FROM options WHERE poll_id = ?', [pollId]); const optionQueries = options.map(option => { return connection.execute( 'INSERT INTO options (poll_id, option_text) VALUES (?, ?)', [pollId, option] ); }); await Promise.all(optionQueries); await connection.commit(); connection.release(); res.status(200).json({ message: "Poll updated successfully." }); } catch (error) { console.error("Error updating poll:", error.message); res.status(500).json({ message: "Error updating poll." }); } }; // Delete Poll exports.deletePoll = async (req, res) => { const { pollId } = req.params; try { const connection = await pool.getConnection(); const [result] = await connection.execute( 'DELETE FROM polls WHERE poll_id = ?', [pollId] ); connection.release(); if (result.affectedRows === 0) { return res.status(404).json({ message: "Poll not found." }); } res.status(200).json({ message: "Poll deleted successfully." }); } catch (error) { console.error("Error deleting poll:", error.message); res.status(500).json({ message: "Error deleting poll." }); } }; // Vote in Poll exports.voteInPoll = async (req, res) => { const { pollId } = req.params; const { userId, option } = req.body; if (!userId || !option) { return res.status(400).json({ message: "User ID and option are required." }); } try { const connection = await pool.getConnection(); const [userVote] = await connection.execute( 'SELECT * FROM votes WHERE poll_id = ? AND user_id = ?', [pollId, userId] ); if (userVote.length > 0) { connection.release(); return res.status(400).json({ message: "User has already voted." }); } const [optionResult] = await connection.execute( 'SELECT option_id FROM options WHERE poll_id = ? AND option_text = ?', [pollId, option] ); if (optionResult.length === 0) { connection.release(); return res.status(404).json({ message: "Option not found." }); } const optionId = optionResult[0].option_id; await connection.execute( 'INSERT INTO votes (poll_id, user_id, option_id) VALUES (?, ?, ?)', [pollId, userId, optionId] ); connection.release(); res.status(200).json({ message: "Vote cast successfully." }); } catch (error) { console.error("Error casting vote:", error.message); res.status(500).json({ message: "Error casting vote." }); } }; // View Poll Results exports.viewPollResults = async (req, res) => { const { pollId } = req.params; try { const connection = await pool.getConnection(); const [poll] = await connection.execute( 'SELECT * FROM polls WHERE poll_id = ?', [pollId] ); if (poll.length === 0) { connection.release(); return res.status(404).json({ message: "Poll not found." }); } const [options] = await connection.execute( 'SELECT option_text, COUNT(votes.option_id) as vote_count FROM options ' + 'LEFT JOIN votes ON options.option_id = votes.option_id ' + 'WHERE options.poll_id = ? GROUP BY options.option_id', [pollId] ); connection.release(); res.status(200).json({ pollId: poll[0].poll_id, question: poll[0].question, results: options.reduce((acc, option) => { acc[option.option_text] = option.vote_count; return acc; }, {}) }); } catch (error) { console.error("Error viewing poll results:", error.message); res.status(500).json({ message: "Error viewing poll results." }); } };
Copy after login

Step 4: Poll Routes

File - routes/pollRoutes.js
Define the routes for each API endpoint:

const express = require('express'); const router = express.Router(); const pollController = require('../controllers/pollController'); // Routes router.post('/polls', pollController.createPoll); router.put('/polls/:pollId', pollController.updatePoll); router.delete('/polls/:pollId', pollController.deletePoll); router.post('/polls/:pollId/vote', pollController.voteInPoll); router.get('/polls/:pollId/results', pollController.viewPollResults); module.exports = router;
Copy after login

Step 5: Server Entry Point

File - index.js
Finally, set up the server:

const express = require('express'); const pollRoutes = require('./routes/pollRoutes'); require('dotenv').config(); const app = express(); app.use(express.json()); // Routes app.use('/api', pollRoutes); // Error Handling Middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ message: "Internal server error" }); }); // Start Server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Copy after login

Error Handling

Each method includes error handling for common issues like invalid input, duplicate votes, missing poll or option, and server errors.

  • Validation des entrées: des vérifications sont effectuées pour garantir que les entrées sont valides, par exemple en vérifiant si les champs obligatoires sont présents et correctement formatés.
  • Gestion des transactions: pour les opérations impliquant plusieurs requêtes (par exemple, création ou mise à jour de sondages), les transactions sont utilisées pour garantir la cohérence.

Essai

Testez chaque point de terminaison à l'aide d'outils comme Postman ou curl.

  • Créer un sondage: POST /api/polls avec un corps JSON contenant une question et un tableau d'options.
  • Mettre à jour le sondage: PUT /api/polls/:pollId avec la question et les options mises à jour.
  • Supprimer le sondage: DELETE /api/polls/:pollId.
  • Votez dans le sondage: POST /api/polls/:pollId/vote avec userId et option.
  • Afficher les résultats du sondage: GET /api/polls/:pollId/results.

Conclusion

Il s'agit d'une implémentation modulaire complète d'un système de sondage en ligne utilisant Node.js, Express et MySQL. Il gère les opérations CRUD de base et garantit la cohérence des données avec les transactions. Il inclut également une gestion des erreurs de base pour rendre l'API plus robuste et plus conviviale.

Veuillez vous référer à l'article Conception de base de bas niveau du système d'interrogation - I

Plus de détails :

Obtenez tous les articles liés à la conception de systèmes
Hastag : SystemDesignWithZeeshanAli

conception du système aveczeeshanali

Git : https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli

The above is the detailed content of Low-Level Design: Polling System - Using Nodejs. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!