Vercel Logo

Add Filtering and Details

When someone asks what students are saying about the knife skills course, the API should return that course's feedback instead of the full dataset.

You'll add query parameters to narrow the list and a route for fetching one entry by ID.

Outcome

Add query param filtering to GET /api/feedback and create a GET /api/feedback/:id route.

Fast Track

  1. Add courseSlug, lessonSlug, and minRating query param support to the GET handler
  2. Implement the GET handler in app/api/feedback/[id]/route.ts
  3. Return 404 for unknown IDs

Hands-on exercise

Part 1: Query parameters

Update the existing GET handler in app/api/feedback/route.ts to support three optional query parameters:

  • courseSlug: filter entries where courseSlug matches exactly
  • lessonSlug: filter entries where lessonSlug matches exactly
  • minRating: filter entries where rating is greater than or equal to the value

Pull these from request.nextUrl.searchParams. Each filter is optional. If none are provided, return everything (the current behavior). If multiple are provided, apply them all.

const { searchParams } = request.nextUrl;
const courseSlug = searchParams.get("courseSlug");

Part 2: Single feedback by ID

Open app/api/feedback/[id]/route.ts. The starter has this file with a stub handler. The brackets in [id] make this a dynamic segment. Next.js passes the value through the params prop.

In Next.js 16, params is a Promise. You need to await it:

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  // ...
}

Look up the feedback using getFeedbackById. If it doesn't exist, return a 404 with an error message that includes the requested ID. An agent can use that message to decide what to try next.

Async params in Next.js 16

If you forget to await params, TypeScript reports that id is a Promise. In Next.js 16, route params are asynchronous.

Parse minRating as an integer

Query parameters are always strings. If you compare fb.rating >= minRating without parsing, you're doing string comparison, not numeric comparison. "5" >= "4" happens to work, but "5" >= "10" does not. Always use parseInt(minRating, 10) before comparing.

If you see 'params.id is a Promise'

In Next.js 16, params is async. If your TypeScript shows an error like "Property 'id' does not exist on type 'Promise'", you forgot to await params. The signature needs { params }: { params: Promise<{ id: string }> } and you must const { id } = await params before using it.

If filters return all entries instead of filtering

Check that slug comparisons use strict equality (===) and minRating uses >= with the parsed integer. Each if block must also update the filtered result.

Try It

Filter by course:

curl "http://localhost:3000/api/feedback?courseSlug=knife-skills"

Should return only the knife-skills entries (5 in the seed data).

Filter by minimum rating:

curl "http://localhost:3000/api/feedback?minRating=5"

Should return only 5-star entries.

Combine filters:

curl "http://localhost:3000/api/feedback?courseSlug=bread-baking&minRating=4"

Only bread-baking entries rated 4 or above.

Fetch a single entry:

curl http://localhost:3000/api/feedback/fb-001
{
  "id": "fb-001",
  "courseSlug": "knife-skills",
  "lessonSlug": "the-claw-grip",
  "rating": 5,
  "comment": "Finally understand why my onion cuts were uneven. The claw grip changed everything.",
  "author": "Priya Sharma",
  "createdAt": "2026-03-01T10:30:00Z"
}

Fetch a nonexistent entry:

curl http://localhost:3000/api/feedback/fb-999
{
  "error": "Feedback with id \"fb-999\" not found"
}

Clients can now request feedback by course, minimum rating, or ID without reading the full dataset.

Commit

git add -A && git commit -m "feat(api): add query param filtering and single-feedback route"

Done-When

  • GET /api/feedback?courseSlug=knife-skills returns only knife-skills feedback
  • GET /api/feedback?minRating=5 returns only 5-star entries
  • Multiple query params combine (AND logic)
  • GET /api/feedback/fb-001 returns a single entry
  • GET /api/feedback/fb-999 returns 404 with an error message that includes the ID

Solution

app/api/feedback/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getAllFeedback, addFeedback } from "@/lib/data";
 
export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const courseSlug = searchParams.get("courseSlug");
  const lessonSlug = searchParams.get("lessonSlug");
  const minRating = searchParams.get("minRating");
 
  let feedback = await getAllFeedback();
 
  if (courseSlug) {
    feedback = feedback.filter((fb) => fb.courseSlug === courseSlug);
  }
 
  if (lessonSlug) {
    feedback = feedback.filter((fb) => fb.lessonSlug === lessonSlug);
  }
 
  if (minRating) {
    const min = parseInt(minRating, 10);
    feedback = feedback.filter((fb) => fb.rating >= min);
  }
 
  return NextResponse.json(feedback);
}
 
export async function POST(request: NextRequest) {
  const body = await request.json();
 
  const { courseSlug, lessonSlug, rating, comment, author } = body;
 
  if (!courseSlug || !lessonSlug || rating == null || !comment || !author) {
    return NextResponse.json(
      { error: "Missing required fields: courseSlug, lessonSlug, rating, comment, author" },
      { status: 400 }
    );
  }
 
  if (typeof rating !== "number" || rating < 1 || rating > 5) {
    return NextResponse.json(
      { error: "Rating must be a number between 1 and 5" },
      { status: 400 }
    );
  }
 
  const entry = await addFeedback({ courseSlug, lessonSlug, rating, comment, author });
  return NextResponse.json(entry, { status: 201 });
}
app/api/feedback/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getFeedbackById } from "@/lib/data";
 
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const feedback = await getFeedbackById(id);
 
  if (!feedback) {
    return NextResponse.json(
      { error: `Feedback with id "${id}" not found` },
      { status: 404 }
    );
  }
 
  return NextResponse.json(feedback);
}

Was this helpful?

supported.