Skip to content
Learni
View all tutorials
Développement Web

How to Implement a Customer Review System in 2026

Lire en français

Introduction

Managing customer reviews is crucial for trust and SEO. In 2026, systems must include strict validation, automated moderation, and performant display. This tutorial guides you step by step in creating a complete solution with Next.js App Router, Prisma, and TypeScript. You will learn to secure submissions, calculate average ratings, and optimize queries for thousands of reviews.

Prerequisites

  • Next.js 15 with TypeScript
  • Node.js 20+
  • Prisma 5.20+
  • PostgreSQL database
  • Solid knowledge of React and API routes

Prisma Schema for Reviews

prisma/schema.prisma
model Review {
  id        Int      @id @default(autoincrement())
  rating    Int
  comment   String   @db.Text
  approved  Boolean  @default(false)
  userId    String
  productId String
  createdAt DateTime @default(now())
  user      User     @relation(fields: [userId], references: [id])
  product   Product  @relation(fields: [productId], references: [id])
}

This schema defines the Review model with rating validation, moderation, and relations. The approved field enables manual moderation before public display.

POST API Route for Submitting a Review

app/api/reviews/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';

const reviewSchema = z.object({
  rating: z.number().min(1).max(5),
  comment: z.string().min(10).max(500),
  productId: z.string(),
});

export async function POST(req: NextRequest) {
  const body = await req.json();
  const parsed = reviewSchema.safeParse(body);
  if (!parsed.success) return NextResponse.json({ error: 'Invalid data' }, { status: 400 });
  const { rating, comment, productId } = parsed.data;
  const review = await prisma.review.create({
    data: { rating, comment, productId, userId: 'user-123', approved: false }
  });
  return NextResponse.json(review, { status: 201 });
}

This route uses Zod to validate incoming data and creates the review in unapproved mode by default. It prevents invalid submissions and injections.

GET API Route for Approved Reviews

app/api/reviews/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const productId = searchParams.get('productId');
  if (!productId) return NextResponse.json({ error: 'Missing productId' }, { status: 400 });
  const reviews = await prisma.review.findMany({
    where: { productId, approved: true },
    orderBy: { createdAt: 'desc' },
    take: 20
  });
  return NextResponse.json(reviews);
}

This query optimizes performance by filtering only approved reviews and limiting results. Ideal for client-side display.

Review Stars Display Component

components/ReviewStars.tsx
import React from 'react';
interface Props { rating: number; }
export default function ReviewStars({ rating }: Props) {
  return (
    <div className="flex">
      {Array.from({ length: 5 }).map((_, i) => (
        <span key={i} className={i < rating ? 'text-yellow-500' : 'text-gray-300'}>★</span>
      ))}
    </div>
  );
}

Reusable and accessible component that visually displays the rating. It is optimized for server-side rendering and avoids unnecessary re-renders.

Average Rating Calculation Function

lib/reviewUtils.ts
import { prisma } from './prisma';
export async function getAverageRating(productId: string): Promise<number> {
  const result = await prisma.review.aggregate({
    where: { productId, approved: true },
    _avg: { rating: true }
  });
  return result._avg.rating || 0;
}

Uses Prisma aggregation to calculate the average in the database, avoiding loading all reviews into memory. Highly performant at scale.

Best Practices

  • Always validate inputs server-side with Zod
  • Moderate reviews before publication
  • Use indexes on productId and approved
  • Limit the number of reviews returned per query
  • Protect routes with authentication

Common Mistakes to Avoid

  • Forgetting validation and allowing invalid ratings
  • Not separating creation and moderation
  • Loading all reviews without pagination
  • Ignoring SQL injections through poorly constructed dynamic queries