---
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-08-26T21:03:36.398Z"
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

The feedback data needs two operations: reading existing entries and submitting a new one.

Both handlers will live on one route: GET to read and 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()`

Return every entry for now. The next lesson adds filtering.

**The POST handler** needs 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 an integer 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?\*\*

Clients, including agents, will POST directly to this endpoint. Validate input at the server boundary.

## 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 an integer 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 rejects the request body\*\*

Include `-H "Content-Type: application/json"` and send valid JSON. The handler catches JSON parse failures and returns a descriptive `400` response.

\*\*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.

The route can now list and create feedback. Next, you'll add filters so clients can request a smaller result set.

## 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) {
  let body;

  try {
    body = await request.json();
  } catch {
    return NextResponse.json(
      { error: "Request body must be valid JSON" },
      { status: 400 }
    );
  }

  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 (!Number.isInteger(rating) || rating < 1 || rating > 5) {
    return NextResponse.json(
      { error: "Rating must be an integer 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)
