---
title: "Feedback Endpoint"
description: "Create the main feedback API route that lists all entries with GET and accepts new submissions with POST, including validation for required fields and rating range."
canonical_url: "https://vercel.com/academy/agent-friendly-apis/feedback-endpoint"
md_url: "https://vercel.com/academy/agent-friendly-apis/feedback-endpoint.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-11T05:05:38.976Z"
content_type: "lesson"
course: "agent-friendly-apis"
course_title: "Agent-Friendly APIs"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Feedback Endpoint

# Build the Feedback Endpoint

Every restaurant has a comment card. Some wedge them between the salt shaker and the menu. Some follow up with an email. But the card itself is useless without two things: a way to read what people wrote, and a way to submit a new one.

That's what we're building. Two handlers on a single route: GET to read, POST to write.

## Outcome

Build a `/api/feedback` route that returns all feedback entries on GET and creates new ones on POST.

## Fast Track

1. Open `app/api/feedback/route.ts` (stub provided in starter)
2. Implement the `GET` handler to return all feedback as JSON
3. Implement the `POST` handler to validate the body and add the entry

## Hands-on exercise

Open `app/api/feedback/route.ts`. The starter has this file with stub handlers. In Next.js App Router, the file path determines the URL. A file at `app/api/feedback/route.ts` handles requests to `/api/feedback`.

**The GET handler** should:

1. Call `getAllFeedback()` from your data utility
2. Return the results as JSON with `NextResponse.json()`

That's it for now. No filtering yet. We'll add that in the next lesson.

**The POST handler** is where it gets interesting. We need to:

1. Parse the JSON body from the request
2. Validate that all required fields are present: `courseSlug`, `lessonSlug`, `rating`, `comment`, `author`
3. Validate that `rating` is a number between 1 and 5
4. Call `addFeedback()` with the validated data
5. Return the new entry with a `201` status

For validation errors, return a `400` status with a JSON body containing an `error` field. Be specific about what went wrong. Vague error messages like `"Bad request"` are unhelpful for humans and even worse for agents (we'll come back to this point in Section 2).

```ts title="app/api/feedback/route.ts"
import { NextRequest, NextResponse } from "next/server";
import { getAllFeedback, addFeedback } from "@/lib/data";
```

Start with those imports, then implement the two handlers.

\*\*Note: Why validate on the server?\*\*

We don't have a frontend form in this course, but clients (including agents) will POST to this endpoint. Validation at the boundary is the only validation that matters.

## Try It

Start the dev server and test both handlers with curl.

**List all feedback:**

```bash
curl http://localhost:3000/api/feedback
```

You should see all 10 seed entries. The first one will look like this:

```json
[
  {
    "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"
  },
  ...
]
```

**Submit new feedback:**

```bash
curl -X POST http://localhost:3000/api/feedback \
  -H "Content-Type: application/json" \
  -d '{
    "courseSlug": "bread-baking",
    "lessonSlug": "scoring-dough",
    "rating": 5,
    "comment": "The lame technique demo was incredibly helpful.",
    "author": "Alex Turner"
  }'
```

You should get back the new entry with a generated `id` and `createdAt`:

```json
{
  "id": "fb-011",
  "courseSlug": "bread-baking",
  "lessonSlug": "scoring-dough",
  "rating": 5,
  "comment": "The lame technique demo was incredibly helpful.",
  "author": "Alex Turner",
  "createdAt": "2026-03-06T12:00:00Z"
}
```

**Missing fields test:**

```bash
curl -X POST http://localhost:3000/api/feedback \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "error": "Missing required fields: courseSlug, lessonSlug, rating, comment, author"
}
```

**Bad rating test:**

```bash
curl -X POST http://localhost:3000/api/feedback \
  -H "Content-Type: application/json" \
  -d '{
    "courseSlug": "bread-baking",
    "lessonSlug": "scoring-dough",
    "rating": 11,
    "comment": "Off the charts",
    "author": "Alex Turner"
  }'
```

```json
{
  "error": "Rating must be a number between 1 and 5"
}
```

\*\*Warning: POST modifies your seed data\*\*

Every successful POST appends to `data/feedback.json`. If your test data gets messy, reset it with `git checkout data/feedback.json`.

\*\*Warning: If POST returns an empty body or 500\*\*

Check that you're including `-H "Content-Type: application/json"` in your curl command. Without it, `request.json()` can't parse the body and throws an unhandled error. If you see `SyntaxError: Unexpected token` in the terminal, that's the cause.

\*\*Warning: If GET returns an empty array\*\*

Your seed data file might have been overwritten by a bad POST. Open `data/feedback.json` and check that it still has the original 10 entries. If it's empty or malformed, copy it fresh from the starter repo.

Two handlers, one route. The comment card has a box to drop it in and a stack to read from. Next we'll add filtering so you're not reading every card in the pile every time.

## Commit

```bash
git add -A && git commit -m "feat(api): add GET and POST handlers for /api/feedback"
```

## Done-When

- [ ] `GET /api/feedback` returns all entries from the JSON file
- [ ] `POST /api/feedback` creates a new entry and returns it with status 201
- [ ] Missing fields return a 400 with a descriptive error message
- [ ] Invalid rating returns a 400 with a descriptive error message

## Solution

```ts title="app/api/feedback/route.ts"
import { NextRequest, NextResponse } from "next/server";
import { getAllFeedback, addFeedback } from "@/lib/data";

export async function GET(request: NextRequest) {
  const feedback = await getAllFeedback();
  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 });
}
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
