Guide To Creating POST /api/events/[id]/save Endpoint
In this comprehensive guide, we will walk you through the process of creating a POST /api/events/[id]/save endpoint. This endpoint allows authenticated users to save or bookmark events to their profiles. This is a crucial feature for any event-driven application, as it enhances user engagement and provides a personalized experience. We'll cover everything from setting up the API route file to implementing the POST handler, testing edge cases, and documenting the endpoint. By the end of this guide, you'll have a solid understanding of how to implement this functionality in your own projects.
1. Setting Up the API Route File
The first step in creating our endpoint is to set up the API route file. This involves creating a new file within your project's directory structure. We will be using Next.js 13+ App Router dynamic routes for this purpose. This approach offers several advantages, including improved performance and simplified routing. Make sure you have a basic understanding of Next.js routing before proceeding.
- File Location:
src/app/api/events/[id]/save/route.ts
This file will contain the logic for handling POST requests to our /api/events/[id]/save endpoint. The [id] part of the path indicates a dynamic route segment, which allows us to capture the event ID from the URL. This ID will be used to identify the event that the user wants to save. When creating the file, ensure that your directory structure matches the specified path to avoid any routing issues. This is a common mistake that can lead to unexpected behavior, so double-check your file placement before moving on.
Once the file is created, we can move on to implementing the POST handler, which will contain the core logic for saving events. This is where we'll parse the request body, validate inputs, interact with the database, and return appropriate responses. The following section will provide a detailed walkthrough of this process, including code examples and explanations.
2. Implementing the POST Handler
The heart of our endpoint lies in the POST handler. This function will handle incoming POST requests, parse the request body, validate the inputs, and interact with the database to save the event. We'll be using the Supabase client for database interactions. Supabase is an open-source Firebase alternative that provides a powerful and flexible platform for building scalable applications. Familiarity with Supabase or a similar database service is essential for understanding this section.
Here’s a detailed breakdown of the implementation:
import { NextResponse } from 'next/server';
import { supabase } from '@/lib/supabase/client';
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) {
try {
// 1. Parse the request body
const body = await request.json();
const { user_id } = body;
// 2. Validate inputs
if (!user_id) {
return NextResponse.json(
{ error: 'user_id is required' },
{ status: 400 }
);
}
const event_id = params.id;
// 3. Check if event exists
const { data: eventExists, error: eventError } = await supabase
.from('events')
.select('id')
.eq('id', event_id)
.single();
if (eventError || !eventExists) {
return NextResponse.json(
{ error: 'Event not found' },
{ status: 404 }
);
}
// 4. Check if already saved (prevent duplicates)
const { data: existing } = await supabase
.from('user_saved_events')
.select('*')
.eq('user_id', user_id)
.eq('event_id', event_id)
.single();
if (existing) {
return NextResponse.json(
{ message: 'Event already saved', saved_at: existing.saved_at },
{ status: 200 }
);
}
// 5. Insert into user_saved_events table
const { data, error } = await supabase
.from('user_saved_events')
.insert({
user_id,
event_id,
saved_at: new Date().toISOString()
})
.select()
.single();
if (error) {
console.error('Save event error:', error);
return NextResponse.json(
{ error: 'Failed to save event' },
{ status: 500 }
);
}
// 6. Return success response
return NextResponse.json({
success: true,
saved_at: data.saved_at
});
} catch (error) {
console.error('Unexpected error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Step-by-Step Explanation
- Parse the Request Body: We start by parsing the request body using
await request.json(). This allows us to extract theuser_idfrom the body, which is necessary for identifying the user saving the event. - Validate Inputs: Next, we validate the inputs to ensure that all required data is present. In this case, we check if
user_idis provided. If it's missing, we return a 400 error with a clear message indicating thatuser_idis required. Input validation is crucial for preventing errors and ensuring data integrity. - Check if Event Exists: Before saving the event, we need to verify that it exists in the database. We use the Supabase client to query the
eventstable and check if an event with the givenevent_idexists. If the event is not found, we return a 404 error. - Check if Already Saved: To prevent duplicate saves, we check if the user has already saved the event. We query the
user_saved_eventstable to see if there's an existing record with the givenuser_idandevent_id. If a record exists, we return a 200 status with a message indicating that the event is already saved. - Insert into
user_saved_eventsTable: If the event exists and is not already saved, we proceed to insert a new record into theuser_saved_eventstable. This record includes theuser_id,event_id, and a timestamp indicating when the event was saved. We usenew Date().toISOString()to generate the timestamp in ISO format. - Return Success Response: Finally, if the event is saved successfully, we return a 200 status with a success message and the
saved_attimestamp. This provides confirmation to the client that the operation was successful.
Error Handling
Throughout the POST handler, we include error handling to gracefully manage potential issues. We use try-catch blocks to catch exceptions and return appropriate error responses. Additionally, we log errors to the console for debugging purposes. Effective error handling is essential for building robust and reliable applications.
3. Testing the Endpoint
Once the POST handler is implemented, it's crucial to test the endpoint thoroughly. This ensures that it functions correctly and handles various scenarios, including edge cases. We can use tools like Postman or curl to send requests to our endpoint and verify the responses. Testing is a critical step in the development process, as it helps identify and fix bugs early on.
Using curl for Testing
Here are some example curl commands for testing the endpoint:
# Test: Save an event
curl -X POST http://localhost:3000/api/events/[some-event-id]/save \
-H "Content-Type: application/json" \
-d '{"user_id": "test-user-uuid"}'
# Expected response:
# {"success": true, "saved_at": "2025-11-24T12:00:00.000Z"}
Replace [some-event-id] with a valid event ID from your database and test-user-uuid with a valid user ID. This command sends a POST request to the endpoint with the user_id in the request body.
Testing Edge Cases
It's essential to test various edge cases to ensure that the endpoint behaves as expected under different conditions. Here are some edge cases to consider:
- Missing
user_id: Send a request without theuser_idin the body. The endpoint should return a 400 error. - Invalid
event_id: Send a request with an invalidevent_id. The endpoint should return a 404 error. - Duplicate Save: Send multiple requests to save the same event for the same user. The endpoint should return a message indicating that the event is already saved.
- Valid Save: Send a request with a valid
user_idandevent_id. The endpoint should return a success response with thesaved_attimestamp.
By testing these edge cases, we can ensure that our endpoint is robust and handles various scenarios gracefully.
4. Documenting the Endpoint
Documentation is a crucial aspect of API development. It provides developers with the information they need to understand how to use the endpoint. Clear and concise documentation can save time and reduce confusion. Good documentation is an investment that pays off in the long run.
JSDoc Comments
We can use JSDoc comments to document our POST function. JSDoc is a popular documentation generator for JavaScript, and it allows us to add comments directly in our code that can be used to generate API documentation.
Here’s an example of how to add JSDoc comments to our POST function:
/**
* @POST Saves an event to a user's profile.
* @param {Request} request The incoming request object.
* @param {object} params An object containing the route parameters.
* @param {string} params.id The ID of the event to save.
* @returns {NextResponse} A JSON response indicating the success or failure of the operation.
*/
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) { ... }
These comments provide a brief description of the function, its parameters, and its return value. This information can be used to generate API documentation automatically.
API Documentation File
In addition to JSDoc comments, it's also a good practice to maintain a separate API documentation file. This file can provide more detailed information about the endpoint, including request and response formats, error codes, and usage examples.
- File:
docs/api-endpoints.md(create if it doesn't exist)
This file can be written in Markdown format, which is a simple and readable markup language. It should include the following information:
- Endpoint URL:
/api/events/[id]/save - Method: POST
- Description: Saves an event to a user's profile.
- Request Body:
{ "user_id": "string" // The ID of the user saving the event } - Response Codes:
- 200: Success
{ "success": true, "saved_at": "string" // The timestamp when the event was saved } - 400: Bad Request
{ "error": "string" // Error message indicating the issue } - 404: Not Found
{ "error": "string" // Error message indicating the event was not found } - 500: Internal Server Error
{ "error": "string" // Generic error message }
- 200: Success
By documenting our endpoint in this way, we provide developers with all the information they need to use it effectively.
5. Acceptance Criteria
To ensure that our endpoint meets the required standards, we need to define clear acceptance criteria. These criteria serve as a checklist for verifying that the endpoint is implemented correctly and functions as expected. Clear acceptance criteria are essential for ensuring quality and consistency.
Here are the acceptance criteria for our POST /api/events/[id]/save endpoint:
- [ ] API route file created at correct location
- [ ] POST handler implemented with all validation logic
- [ ] Prevents duplicate saves (idempotent operation)
- [ ] Returns proper HTTP status codes (200, 400, 404, 500)
- [ ] Tested successfully with Postman/curl
- [ ] All edge cases tested and handled
- [ ] Error messages are clear and helpful
- [ ] Code includes error logging for debugging
By systematically checking each of these criteria, we can be confident that our endpoint is well-implemented and meets the requirements.
6. Database Schema Reference
Understanding the database schema is crucial for working with the endpoint. The schema defines the structure of the tables and the relationships between them. A clear understanding of the database schema is essential for writing efficient queries and ensuring data integrity.
We'll be working with the user_saved_events table, which has the following schema:
CREATE TABLE user_saved_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id),
event_id UUID NOT NULL REFERENCES events(id),
saved_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, event_id)
);
Table Columns
id: A UUID (Universally Unique Identifier) that serves as the primary key for the table. It's generated automatically usinguuid_generate_v4(). UUIDs are used to ensure uniqueness across different systems and databases.user_id: A UUID that references theidcolumn in theuserstable. This establishes a foreign key relationship, linking saved events to specific users.event_id: A UUID that references theidcolumn in theeventstable. This establishes a foreign key relationship, linking saved events to specific events.saved_at: A timestamp indicating when the event was saved. It defaults to the current timestamp usingNOW(). Timestamps are useful for tracking when events were saved and for sorting saved events by time.UNIQUE(user_id, event_id): This constraint ensures that a user can only save an event once. It prevents duplicate entries in the table and helps maintain data integrity. The UNIQUE constraint is crucial for implementing the idempotent behavior of our endpoint.
Conclusion
Creating a POST /api/events/[id]/save endpoint involves several key steps, from setting up the API route file to implementing the POST handler, testing edge cases, and documenting the endpoint. By following the guidelines and best practices outlined in this guide, you can build a robust and reliable endpoint that enhances user engagement and provides a personalized experience. Remember to prioritize input validation, error handling, and thorough testing to ensure the quality of your implementation. Proper documentation is also essential for making your endpoint easy to use and maintain.
For more information on API development and best practices, check out REST API Tutorial.