”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 底层设计:轮询系统 - 使用 Nodejs

底层设计:轮询系统 - 使用 Nodejs

发布于2024-09-16
浏览:542

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
);
  • 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-Many between Polls and Options: Each poll can have multiple options.
  2. Many-to-One between Votes and Options: Each vote is associated with one option.
  3. Many-to-One between 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

  • 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

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;

Step 2: Environment Variables

File - .env

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=polling_system
PORT=3000

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  {
            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  {
            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." });
    }
};

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;

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

Error Handling

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

  • Input Validation: Checks are performed to ensure that the inputs are valid, such as checking if the required fields are present and properly formatted.
  • Transaction Management: For operations involving multiple queries (e.g., creating or updating polls), transactions are used to ensure consistency.

Testing

Test each endpoint using tools like Postman or curl.

  • Create Poll: POST /api/polls with a JSON body containing question and an array of options.
  • Update Poll: PUT /api/polls/:pollId with updated question and options.
  • Delete Poll: DELETE /api/polls/:pollId.
  • Vote in Poll: POST /api/polls/:pollId/vote with userId and option.
  • View Poll Results: GET /api/polls/:pollId/results.

Conclusion

This is a comprehensive modular implementation of an online polling system using Node.js, Express, and MySQL. It handles the basic CRUD operations and ensures data consistency with transactions. It also includes basic error handling to make the API more robust and user-friendly.

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

More Details:

Get all articles related to system design
Hastag: SystemDesignWithZeeshanAli

systemdesignwithzeeshanali

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

版本声明 本文转载于:https://dev.to/zeeshanali0704/low-level-design-polling-system-2-2j21?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Java 字符串实习初学者指南
    Java 字符串实习初学者指南
    Java String Interning 引入了通过在共享池中存储唯一字符串来优化内存的概念,减少重复对象。它解释了 Java 如何自动实习字符串文字以及开发人员如何使用 intern() 方法手动将字符串添加到池中。 通过掌握字符串驻留,您可以提高 Java 应用程序的性能和内存效率。要深入...
    编程 发布于2024-11-06
  • 如何在 GUI 应用程序中的不同页面之间共享变量数据?
    如何在 GUI 应用程序中的不同页面之间共享变量数据?
    如何从类中获取变量数据在 GUI 编程环境中,单个应用程序窗口中包含多个页面是很常见的。每个页面可能包含各种小部件,例如输入字段、按钮或标签。当与这些小部件交互时,用户提供输入或做出需要在不同页面之间共享的选择。这就提出了如何从一个类访问另一个类的变量数据的问题,特别是当这些类代表不同的页面时。利用...
    编程 发布于2024-11-06
  • React 中的动态路由
    React 中的动态路由
    React 中的动态路由允许您基于动态数据或参数创建路由,从而在应用程序中实现更灵活、更强大的导航。这对于需要根据用户输入或其他动态因素呈现不同组件的应用程序特别有用。 使用 React Router 设置动态路由 您通常会使用react-router-dom库在React中实现动态路由。这是分步指...
    编程 发布于2024-11-06
  • 大批
    大批
    方法是可以在对象上调用的 fns 数组是对象,因此它们在 JS 中也有方法。 slice(begin):将数组的一部分提取到新数组中,而不改变原始数组。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index p...
    编程 发布于2024-11-06
  • WPF中延迟操作时如何避免UI冻结?
    WPF中延迟操作时如何避免UI冻结?
    WPF 中的延迟操作WPF 中的延迟操作对于增强用户体验和确保平滑过渡至关重要。一种常见的情况是在导航到新窗口之前添加延迟。为了实现此目的,经常使用 Thread.Sleep,如提供的代码片段中所示。但是,在延迟过程中,使用 Thread.Sleep 阻塞 UI 线程会导致 UI 无响应。这表现为在...
    编程 发布于2024-11-06
  • 利用 Java 进行实时数据流和处理
    利用 Java 进行实时数据流和处理
    In today's data-driven world, the ability to process and analyze data in real-time is crucial for businesses to make informed decisions swiftly. Java...
    编程 发布于2024-11-06
  • 如何修复损坏的 InnoDB 表?
    如何修复损坏的 InnoDB 表?
    从 InnoDB 表损坏中恢复灾难性事件可能会导致数据库表严重损坏,特别是 InnoDB 表。遇到这种情况时,了解可用的修复选项就变得至关重要。InnoDB Table Corruption Symptoms查询中描述的症状,包括事务日志中的时间戳错误InnoDB 表的修复策略虽然已经有修复 MyI...
    编程 发布于2024-11-06
  • JavaScript 数组和对象中是否正式允许使用尾随逗号?
    JavaScript 数组和对象中是否正式允许使用尾随逗号?
    数组和对象中的尾随逗号:标准还是容忍?数组和对象中尾随逗号的存在引发了一些关于它们的争论JavaScript 的标准化。这个问题源于在不同浏览器中观察到的不一致行为,特别是旧版本的 Internet Explorer。规范状态根据 ECMAScript 5 规范(第 11.1.5 节) ),对象字面...
    编程 发布于2024-11-06
  • 最佳引导模板生成器
    最佳引导模板生成器
    在当今快速发展的数字环境中,速度和效率是关键,网页设计师和开发人员越来越依赖 Bootstrap 构建器来简化他们的工作流程。这些工具可以快速创建响应灵敏、具有视觉吸引力的网站,使团队能够比以往更快地将他们的想法变为现实。 Bootstrap 构建器真正改变了网站的构建方式,使该过程更加易于访问和高...
    编程 发布于2024-11-06
  • 简化 NestJS 中的文件上传:无需磁盘存储即可高效内存中解析 CSV 和 XLSX
    简化 NestJS 中的文件上传:无需磁盘存储即可高效内存中解析 CSV 和 XLSX
    Effortless File Parsing in NestJS: Manage CSV and XLSX Uploads in Memory for Speed, Security, and Scalability Introduction Handling file uploa...
    编程 发布于2024-11-06
  • 使用 SubDomainRadar.io 和 Python 轻松发现隐藏子域
    使用 SubDomainRadar.io 和 Python 轻松发现隐藏子域
    作为网络安全专业人员、漏洞赏金猎人或渗透测试人员,发现隐藏的子域对于识别至关重要域中的潜在漏洞。子域通常托管可能容易受到攻击的被遗忘的服务或测试环境。 在这篇文章中,我将向您介绍 SubDomainRadar.io 及其 Python API 包装器 — 自动化子域枚举的终极工具 和 安全工作流程...
    编程 发布于2024-11-06
  • Python 中的 HackerRank 问题 - 基本数据类型列表
    Python 中的 HackerRank 问题 - 基本数据类型列表
    此 Python 代码旨在根据用户提供的命令对列表执行一系列操作。让我们一步步分析代码,了解其工作原理: if __name__ == '__main__': N = int(input()) l = [] while(N>0): cmd_l = inp...
    编程 发布于2024-11-06
  • ust-Know 高级 Tailwind CSS 实用程序可增强开发体验
    ust-Know 高级 Tailwind CSS 实用程序可增强开发体验
    Tailwind CSS 以其实用性优先的方法而闻名,使开发人员能够直接在 HTML 中创建高度可定制的设计。除了基础知识之外,掌握高级实用程序还可以显着增强您的开发工作流程,使其更快、更高效。在这里,我们将探索每个开发人员都应该知道的六个高级 Tailwind CSS 实用程序,并通过并排代码编辑...
    编程 发布于2024-11-06
  • Qt Signals 中的 DirectConnection 与 QueuedConnection:何时应该选择哪个?
    Qt Signals 中的 DirectConnection 与 QueuedConnection:何时应该选择哪个?
    Qt 信号:深入研究 DirectConnection 和 QueuedConnectionQt 信号在 Qt 应用程序中组件之间的通信中发挥着至关重要的作用。然而,选择 DirectConnection 和 QueuedConnection 作为连接方法可能会产生重大影响,尤其是在处理多线程应用程...
    编程 发布于2024-11-06
  • 如何访问 pytz 库中的所有时区?
    如何访问 pytz 库中的所有时区?
    访问 Pytz 时区的综合列表在 Python 的 pytz 库中,时区对于处理全球不同时区至关重要。为了有效地使用它们,必须有一个全面的列表。如何获取时区列表:pytz 提供了两种方便的方法来访问所有可能的时区值:pytz.all_timezones: 返回所有可用时区的列表,包括稀有或过时的时区...
    编程 发布于2024-11-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3