Interactive Comment Section
React (Vite), Tailwind CSS, Node.js, Express.js, PostgreSQL (via Prisma ORM), Google Perspective API, Docker + Docker Compose
This project was a personal portfolio project designed to simulate a professional-grade interactive comments feature, the kind you'd see on blog platforms, forums, or product reviews. I built it from the ground up to demonstrate my ability to architect a scalable full-stack app, focusing on clean UI/UX, structured state management, API development, form validation, and backend moderation using AI sentiment analysis.
All work was done by me, from frontend to backend, database schema, and deployment on Render.
Most comment systems today lack real-time moderation, structured replies, or safe UX practices. This project solves that by creating a fully interactive, nested commenting system with built-in sentiment analysis, toxicity flagging, and modular voting, editing, and reply logic. The goal was to show not just technical execution, but strong product thinking, where user-generated content needs structure, empathy, and resilience.
Vite + React
I chose Vite to scaffold the frontend because of its lightning-fast development server, native ESM support, and minimal configuration overhead. For the UI layer, React felt like the natural fit given its reusable component model and my prior familiarity. I intentionally avoided Next.js since server-side rendering wasn’t necessary; the app was fully client-rendered and paired with a separate backend.Node.js + Express
On the backend, I used Node.js with Express to keep things lightweight and flexible. Express allowed me to quickly spin up custom API routes and easily integrate external services like the Perspective API. I briefly looked into Fastify for performance benefits, but Express had a lower barrier to entry and broader ecosystem support for the type of app I was building.Prisma ORM + PostgreSQL
For data modeling, I used Prisma with PostgreSQL. Prisma gave me type-safe database queries and a seamless development workflow, while PostgreSQL offered solid relational data handling—crucial for managing nested comment threads. I considered Sequelize and TypeORM but found Prisma much easier to work with, especially with its intuitive schema syntax and strong TypeScript integration.Google Perspective API
To flag harmful or toxic comments, I integrated Google’s Perspective API. It let me offload the complexity of NLP and content moderation while still delivering AI-level analysis. I did consider open-source sentiment models but ruled them out to avoid the overhead of training, maintaining, or hosting them myself.Docker + Docker Compose
I containerized both the backend server and database using Docker, and used Docker Compose to manage them together during development. This gave me a consistent local environment and simplified deployment by eliminating “it works on my machine” bugs. I appreciated being able to version-control the full infrastructure setup.Render
For hosting, I chose Render because it let me deploy the frontend, backend, and PostgreSQL database from one dashboard. It handled my GitHub auto-deploys, managed my environment variables, and provided persistent storage for the database; all without needing to configure a CI/CD pipeline from scratch. I briefly compared Heroku and Railway, but Render offered better database performance and a more modern UI for managing services.Zod
I used Zod to handle backend form validation. Its schema-first design fit naturally into my workflow and made error handling cleaner when validating comment inputs. I initially considered Joi, but I preferred Zod’s syntax, native TypeScript support, and how well it integrated with the rest of my stack.
Nested Replies
Users can reply to any comment, and replies are automatically nested beneath their parent. Each comment maintains full context and hierarchy, improving readability and flow in discussions.
Comment Posting with Moderation
Users can post comments in real time. Before any comment is saved, it passes through Google’s Perspective API to check for toxicity. Harmful content is rejected with a clear, user-facing error, no moderation team needed.
Inline Editing & Deletion
Users can edit or delete their own comments. Comments that already have replies cannot be edited to preserve conversation integrity. I built a custom UX state to keep the form open when errors occur (e.g., failed validation or flagged toxicity).
Voting System
Each comment supports upvotes and downvotes. Users can increment or decrement votes to signal agreement or disagreement; vote counts are stored and updated via backend routes using Prisma.
UX-Safe Form Handling
Every form (comment, reply, edit) is fully validated both on the frontend and backend. If an error occurs, forms stay open with helpful inline messages. This prevents user confusion and maintains flow.Error Handling & Modals
Custom modals handle confirmation for deletions and global error messages. State is managed via hooks and conditionally rendered overlays, ensuring a clean, non-jarring UI.
Frontend Folder Structure
I organized the frontend using a modified atomic design structure to keep components modular and scalable. Reusable base components like buttons, overlays, and modals lived inside a shared /components
folder, grouped by function (e.g., overlay
, icons
). More complex, context-specific components like comments and forms were separated into a /pages/comments
directory. This helped me isolate logic, avoid prop drilling, and reuse elements like the delete modal and voting controls across parent and nested comment threads. I kept styles mostly Tailwind-based for consistency and speed, relying on utility classes rather than custom stylesheets.
API Route Strategy
On the backend, I used a clean RESTful API design with Express. Each route was defined under /api/comment
, handling different concerns like fetching, creating, editing, deleting, and voting. I separated POST
, PATCH
, PUT
, and DELETE
routes by HTTP verb and endpoint for clarity. Sentiment analysis was integrated into the POST
and PUT
routes, so any new or updated comment was automatically screened before being saved. I kept all route logic in a single comment.routes.ts
file to maintain visibility across operations during this MVP stage but modularized utility functions like analyzeComment
and validation schemas for cleaner logic separation.