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
- Open
app/api/feedback/route.ts(stub provided in starter) - Implement the
GEThandler to return all feedback as JSON - Implement the
POSThandler 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:
- Call
getAllFeedback()from your data utility - 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:
- Parse the JSON body from the request
- Validate that all required fields are present:
courseSlug,lessonSlug,rating,comment,author - Validate that
ratingis a number between 1 and 5 - Call
addFeedback()with the validated data - Return the new entry with a
201status
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).
import { NextRequest, NextResponse } from "next/server";
import { getAllFeedback, addFeedback } from "@/lib/data";Start with those imports, then implement the two handlers.
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:
curl http://localhost:3000/api/feedbackYou should see all 10 seed entries. The first one will look like this:
[
{
"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:
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:
{
"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:
curl -X POST http://localhost:3000/api/feedback \
-H "Content-Type: application/json" \
-d '{}'{
"error": "Missing required fields: courseSlug, lessonSlug, rating, comment, author"
}Bad rating test:
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"
}'{
"error": "Rating must be a number between 1 and 5"
}Every successful POST appends to data/feedback.json. If your test data gets messy, reset it with git checkout data/feedback.json.
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.
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
git add -A && git commit -m "feat(api): add GET and POST handlers for /api/feedback"Done-When
GET /api/feedbackreturns all entries from the JSON filePOST /api/feedbackcreates 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
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 });
}Was this helpful?