A comprehensive backend service for a real-time polling application built with Node.js, Express, PostgreSQL, Prisma, and WebSockets.
- RESTful API for complete CRUD operations on Users, Polls, and Votes
- Real-time updates using WebSockets for live poll results
- Secure authentication with JWT tokens and bcrypt password hashing
- Database relationships properly modeled with Prisma ORM
- Input validation and error handling middleware
- Rate limiting and security headers
- Comprehensive API documentation with example requests
The application uses PostgreSQL with the following entities and relationships:
- User:
id,name,email,passwordHash,createdAt,updatedAt - Poll:
id,question,isPublished,createdAt,updatedAt,creatorId - PollOption:
id,text,pollId - Vote:
id,userId,pollOptionId,createdAt
- One-to-Many: User β Polls (A user can create many polls)
- One-to-Many: Poll β PollOptions (A poll can have multiple options)
- Many-to-Many: User β PollOptions (Through Vote entity)
- Node.js (v16 or higher)
- PostgreSQL (v12 or higher)
- npm or yarn package manager
-
Clone the repository
git clone <repository-url> cd real-time-polling-api
-
Install dependencies
npm install
-
Environment Setup
cp .env.example .env
Update the
.envfile with your configuration:DATABASE_URL="postgresql://username:password@localhost:5432/polling_app" JWT_SECRET="your-super-secret-jwt-key-here" PORT=3000 NODE_ENV=development CORS_ORIGINS="http://localhost:3000,http://localhost:3001"
-
Database Setup
# Generate Prisma client npm run db:generate # Push schema to database (for development) npm run db:push # Or run migrations (for production) npm run db:migrate
-
Start the server
# Development mode with auto-reload npm run dev # Production mode npm start
POST /api/users/register
Content-Type: application/json
{
"name": "John Doe",
"email": "[email protected]",
"password": "securepassword"
}POST /api/users/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "securepassword"
}GET /api/users/profile
Authorization: Bearer <jwt_token>GET /api/users?page=1&limit=10GET /api/users/:idPOST /api/polls
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"question": "What's your favorite programming language?",
"options": ["JavaScript", "Python", "Java", "Go"],
"isPublished": true
}GET /api/polls?page=1&limit=10&search=programmingGET /api/polls/my
Authorization: Bearer <jwt_token>GET /api/polls/:idPUT /api/polls/:id
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"question": "Updated question",
"isPublished": true
}DELETE /api/polls/:id
Authorization: Bearer <jwt_token>GET /api/polls/:id/resultsPOST /api/votes
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"pollOptionId": "poll_option_id_here"
}DELETE /api/votes/:pollId
Authorization: Bearer <jwt_token>GET /api/votes/my
Authorization: Bearer <jwt_token>GET /api/votes/poll/:pollId
Authorization: Bearer <jwt_token>socket.emit('joinPoll', { pollId: 'poll_id_here' });socket.emit('leavePoll', { pollId: 'poll_id_here' });socket.emit('getPollStats', { pollId: 'poll_id_here' });socket.emit('ping');socket.on('pollJoined', (data) => {
console.log('Joined poll:', data.pollId);
console.log('Current results:', data.results);
});socket.on('voteUpdate', (results) => {
console.log('Live poll results:', results);
// Update UI with new vote counts and percentages
});socket.on('pollStats', (stats) => {
console.log('Poll statistics:', stats);
});socket.on('error', (error) => {
console.error('WebSocket error:', error.message);
});# View database in Prisma Studio
npm run db:studio
# Reset database (development only)
npx prisma db push --force-reset
# Generate Prisma client after schema changes
npm run db:generatesrc/
βββ middleware/
β βββ auth.js # JWT authentication middleware
β βββ validation.js # Request validation middleware
βββ routes/
β βββ users.js # User authentication and management
β βββ polls.js # Poll CRUD operations
β βββ votes.js # Voting functionality
βββ websocket/
β βββ socketHandler.js # WebSocket event handling
βββ server.js # Main application entry point
prisma/
βββ schema.prisma # Database schema definition
- JWT Authentication with secure token generation
- Password Hashing using bcrypt with salt rounds
- Rate Limiting to prevent API abuse
- CORS Configuration for cross-origin requests
- Input Validation using express-validator
- SQL Injection Protection through Prisma ORM
- Security Headers via Helmet middleware
# Register
curl -X POST http://localhost:3000/api/users/register \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"[email protected]","password":"password123"}'
# Login
curl -X POST http://localhost:3000/api/users/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"password123"}'# Create poll (replace TOKEN with actual JWT)
curl -X POST http://localhost:3000/api/polls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"question":"Favorite color?","options":["Red","Blue","Green"],"isPublished":true}'
# Vote on poll (replace POLL_OPTION_ID with actual ID)
curl -X POST http://localhost:3000/api/votes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"pollOptionId":"POLL_OPTION_ID"}'You can test WebSocket functionality using a simple HTML client:
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
</head>
<body>
<script>
const socket = io('http://localhost:3000', {
auth: {
token: 'YOUR_JWT_TOKEN_HERE'
}
});
socket.on('connect', () => {
console.log('Connected to server');
// Join a poll room
socket.emit('joinPoll', { pollId: 'POLL_ID_HERE' });
});
socket.on('voteUpdate', (data) => {
console.log('Live vote update:', data);
});
socket.on('error', (error) => {
console.error('Error:', error);
});
</script>
</body>
</html>NODE_ENV=production
DATABASE_URL="postgresql://user:pass@host:5432/dbname"
JWT_SECRET="your-production-jwt-secret"
PORT=3000
CORS_ORIGINS="https://yourdomain.com"Create a Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npx prisma generate
EXPOSE 3000
CMD ["npm", "start"]- Database Indexing: Ensure proper indexes on foreign keys and frequently queried fields
- Connection Pooling: Configure PostgreSQL connection pooling for production
- Rate Limiting: Adjust rate limits based on expected traffic
- WebSocket Scaling: Consider using Redis adapter for multi-server WebSocket scaling
-
Database Connection Error
- Verify PostgreSQL is running
- Check DATABASE_URL format
- Ensure database exists
-
JWT Token Issues
- Verify JWT_SECRET is set
- Check token expiration
- Ensure proper Authorization header format
-
WebSocket Connection Issues
- Check CORS configuration
- Verify client is using correct URL and port
- Check firewall settings
MIT License - see LICENSE file for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
For issues and questions:
- Create an issue in the repository
- Check existing documentation
- Review the troubleshooting section