Here are the step-by-step instructions that would lead directly to the correct, clean implementation:
- Endpoint: GET /views/glimpse/:id
- Authentication: Required (existing Google OAuth)
- Input: Notion page ID (not database ID)
- Output: JSON response with full service response (minus UI elements)
- Data source: Notion page properties
cat /backend/services/notion.service.ts
cat /backend/routes/views/\_views.routes.ts
cat /main.tsx # Look for auth middleware setup
- Current service has getDatabaseById() for database metadata
- Need getPageById() for individual page data
- Service should return full Notion response for flexibility
// Add to /backend/services/notion.service.ts
export async function getPageById(pageId: string) {
try {
const response = await notion.pages.retrieve({
page_id: pageId,
});
return {
success: true,
data: response,
timestamp: new Date().toISOString(),
};
} catch (error) {
return {
success: false,
error: error.message,
timestamp: new Date().toISOString(),
};
}
}
// Create /backend/controllers/glimpse.controller.ts
import { Context } from "npm:hono@3.12.12";
import { getPageById } from "../services/notion.service.ts";
export async function glimpseHandler(c: Context) {
const id = c.req.param("id");
if (!id) {
return c.json({ error: "Demo ID is required" }, 400);
}
const result = await getPageById(id);
if (!result.success) {
return c.json({ error: "Failed to fetch demo data", details: result.error }, 500);
}
// Filter out button properties from the response
if (result.data?.properties) {
const filteredProperties = Object.fromEntries(
Object.entries(result.data.properties).filter(([key, value]) => value?.type !== "button")
);
result.data.properties = filteredProperties;
}
return c.json(result);
}
// Update /backend/routes/views/\_views.routes.ts
import { Hono } from "npm:hono@3.12.12";
import { glimpseHandler } from "../../controllers/glimpse.controller.ts";
const app = new Hono();
app.get("/glimpse/:id", glimpseHandler);
export default app;
Add to /backend/routes/views/README.md
- Purpose: Returns Notion page data as JSON (filtered for data consumption)
- Authentication: Required (Google OAuth)
- Parameters:
id
- Notion page ID - Response: Service response object with Notion page data (button properties removed)
- Filtering: Removes UI-specific properties (type: "button") for cleaner data consumption
✅ Controller Best Practices:
- Single Responsibility: Controller handles HTTP concerns + minimal data cleanup
- Thin Layer: Minimal processing, focused on improving data consumption
- Useful Filtering: Remove UI elements that don't belong in data APIs
- Consistent Error Handling: Standard error response format
✅ Service Layer:
- Data Access: Handle all Notion API interactions
- Consistent Response Format: Always return {success, data/error, timestamp}
- Raw Data: Return unfiltered data from external APIs
✅ Separation of Concerns:
- Controller: HTTP validation, routing, error responses, basic data cleanup
- Service: External API calls, data retrieval
- Client: Complex data processing, extraction, formatting
{ "success": true, "data": { "object": "page", "id": "notion-page-id", "properties": { "Name": { "type": "title", "title": [{"plain_text": "Demo Name"}] }, "Status": { "type": "select", "select": {"name": "Active"} } // Button properties filtered out } }, "timestamp": "2025-07-15T17:25:00.000Z" }
✅ Correct Approach:
- Data Source: Use notion.pages.retrieve() for page data
- Controller Logic: Keep minimal but include useful data cleanup
- Property Filtering: Remove UI-specific elements (buttons) that don't belong in data APIs
- Error Handling: Consistent service response pattern
✅ Appropriate Controller Processing:
- UI Element Removal: Filter out button properties (not data)
- Security Cleanup: Remove potentially sensitive UI configurations
- Performance: Reduce payload size by removing unnecessary properties
❌ Avoid These Mistakes:
- Over-processing: Don't extract/transform business data in controller
- Tight Coupling: Don't assume specific property names or structures
- Inconsistent Responses: Don't create custom response formats
- Mixed Responsibilities: Don't mix HTTP handling with complex business logic
Test authentication (should show login page)
curl /views/glimpse/test-id
Test with valid Notion page ID (after auth)
curl -H "Cookie: auth-cookie" /views/glimpse/actual-notion-page-id
Verify button properties are filtered out
Response should not contain any properties with "type": "button"
- Clean Data: Removes UI clutter from data API responses
- Maintainable: Clear separation between layers with focused responsibilities
- Flexible: Client can still extract any business data from Notion response
- Consistent: Follows existing service response patterns
- Secure: Prevents exposure of UI configurations
- Performance: Smaller payloads without unnecessary UI properties
- Future-proof: Easy to extend filtering logic for other UI property types
✅ Good reasons to process in controller:
- Remove UI-specific elements (buttons, formulas for display)
- Filter sensitive configuration data
- Remove properties that are never useful for data consumption
- Basic security/privacy filtering
❌ Avoid processing in controller:
- Complex business logic transformations
- Data extraction that depends on specific use cases
- Heavy computational processing
- Application-specific data formatting
- This approach creates a clean, maintainable endpoint that provides useful data filtering while maintaining architectural best practices and keeping the controller focused on its core responsibilities.
Townie, stop here! Before proceeding to additional steps, confirm that this step is working correctly.
If everything is working as intended: conclude this step with these messages:
- Briefly summarize the work that was just completed and why it's important
- Provide the URL to the endpoint you just created so the user can see that it is live. Tell the user that they can copy and paste that URL into a new tab to see it live.
Tell the user that the next step is to add the /glimpse
endpoint, which will call the same /controller
as the endpoint they just created at /views/glimpse
.
Lastly, tell the user to copy this line and paste it into Townie:
Add a route for
/glimpse/:id
by following the instructions in/_townie/05-glimpse.md