Complete Implementation Instructions: Enhanced /glimpse/:id Endpoint with Related Content and Nested Blocks
Enhance the existing /glimpse/:id
endpoint to fetch and attach related pages from the "Glimpse content" relation property, sorted by "Order" property, including full page blocks/content with all nested blocks recursively fetched to support callouts, code blocks, lists, tables, columns, and other rich content structures.
Services return consistent structure:
// Success: {success: true, data: response, timestamp: string}
// Failure: {success: false, error: string, timestamp: string}
Controllers check result.success
and handle accordingly:
- If
!result.success
: Return 500 error with details - If
result.success
: Return full service response (includes success/data/timestamp)
"All or nothing" - operations either succeed completely or fail completely. No partial success states.
Add to /backend/services/notion.service.ts
before the updatePageUrl
function:
async function getAllBlocksRecursively(blockId: string): Promise<any[]> {
const response = await notion.blocks.children.list({
block_id: blockId,
page_size: 100 // Get more blocks per request
});
const blocks = response.results;
// For each block, check if it has children and fetch them recursively
for (const block of blocks) {
if (block.has_children) {
try {
block.children = await getAllBlocksRecursively(block.id);
} catch (error) {
// If fetching children fails, log but don't fail the entire operation
console.warn(`Failed to fetch children for block ${block.id}:`, error.message);
block.children = [];
}
}
}
return blocks;
}
export async function getPageBlocks(pageId: string) {
try {
const blocks = await getAllBlocksRecursively(pageId);
return {
success: true,
data: { results: blocks },
timestamp: new Date().toISOString(),
};
} catch (error) {
return {
success: false,
error: error.message,
timestamp: new Date().toISOString(),
};
}
}
export async function getMultiplePages(pageIds: string[]) {
try {
// Fetch all pages concurrently
const pagePromises = pageIds.map(async (pageId) => {
const [pageResult, blocksResult] = await Promise.all([
notion.pages.retrieve({ page_id: pageId }),
getAllBlocksRecursively(pageId)
]);
return {
id: pageId,
page: pageResult,
blocks: { results: blocksResult } // Maintain same structure as before
};
});
const results = await Promise.all(pagePromises);
return {
success: true,
data: results,
timestamp: new Date().toISOString(),
};
} catch (error) {
return {
success: false,
error: error.message,
timestamp: new Date().toISOString(),
};
}
}
In /backend/controllers/glimpse.controller.ts
, update the import:
import { getPageById, getMultiplePages } from "../services/notion.service.ts";
Replace the entire glimpseHandler
function in /backend/controllers/glimpse.controller.ts
:
export async function glimpseHandler(c: Context) {
const id = c.req.param("id");
if (!id) {
return c.json({ error: "Demo ID is required" }, 400);
}
// Fetch the main page
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 main page
if (result.data?.properties) {
const filteredProperties = Object.fromEntries(
Object.entries(result.data.properties).filter(([key, value]) => value?.type !== "button")
);
result.data.properties = filteredProperties;
}
// Extract related page IDs from "Glimpse content" relation property
const glimpseContentProperty = result.data?.properties?.["Glimpse content"];
let glimpseContent = [];
if (glimpseContentProperty?.type === "relation" && glimpseContentProperty.relation?.length > 0) {
const relatedPageIds = glimpseContentProperty.relation.map((rel: any) => rel.id);
// Fetch all related pages with their content
const relatedPagesResult = await getMultiplePages(relatedPageIds);
if (!relatedPagesResult.success) {
return c.json({
error: "Failed to fetch related pages",
details: relatedPagesResult.error
}, 500);
}
// Process and sort the related pages
glimpseContent = relatedPagesResult.data
.map((item: any) => {
// Filter out button properties from related pages too
const filteredProperties = item.page.properties ?
Object.fromEntries(
Object.entries(item.page.properties).filter(([key, value]) => (value as any)?.type !== "button")
) : {};
return {
id: item.id,
properties: filteredProperties,
blocks: item.blocks.results
};
})
// Sort by Order property
.sort((a: any, b: any) => {
const orderA = a.properties?.Order?.number || 0;
const orderB = b.properties?.Order?.number || 0;
return orderA - orderB;
});
}
// Add the glimpse content to the response
result.data.glimpseContent = glimpseContent;
return c.json(result);
}
# Glimpse Routes
This directory contains routes for the glimpse functionality.
## Routes
### GET /:id
- **Purpose**: Returns Notion page data as JSON with related content (filtered for data consumption)
- **Authentication**: Required (Google OAuth)
- **Parameters**: `id` - Notion page ID
- **Response**: Service response object with Notion page data and related content
- **Filtering**: Removes UI-specific properties (type: "button") for cleaner data consumption
#### Enhanced Functionality
The endpoint now fetches and includes related pages from the "Glimpse content" relation property:
1. **Main Page**: Returns the requested page with filtered properties
2. **Related Content**: Fetches all pages referenced in the "Glimpse content" relation
3. **Content Blocks**: Includes full page blocks/content for each related page with nested blocks recursively fetched
4. **Nested Content**: Supports callouts, code blocks, lists, tables, columns, and other nested block structures
5. **Sorting**: Related pages are sorted by their "Order" property (ascending)
6. **Error Handling**: Returns failure if main page OR any related page fails to fetch
#### Response Structure
```json
{
"success": true,
"data": {
"id": "main-page-id",
"properties": { ... }, // Main page properties (buttons filtered out)
"glimpseContent": [
{
"id": "related-page-1",
"properties": { "Order": { "number": 1 }, ... }, // Related page properties (buttons filtered out)
"blocks": [ ... ] // Full page content blocks with nested structures (callouts, lists, tables, etc.)
},
{
"id": "related-page-2",
"properties": { "Order": { "number": 2 }, ... },
"blocks": [ ... ] // Full page content blocks with nested structures
}
]
},
"timestamp": "2025-07-27T12:00:00.000Z"
}
Blocks with children will have a children
property containing their nested blocks:
{
"type": "bulleted_list_item",
"bulleted_list_item": { ... },
"has_children": true,
"children": [
{
"type": "bulleted_list_item",
"bulleted_list_item": { ... },
"has_children": false
}
]
}
- Main page not found: Returns
{success: false, error: "...", timestamp: "..."}
- Related page fetch fails: Returns
{success: false, error: "Failed to fetch related pages", timestamp: "..."}
- Individual block children fail: Logs warning, continues with empty children array
- No related content: Returns successful response with empty
glimpseContent
array
This is the same functionality as /views/glimpse/:id
but mounted at /glimpse/:id
for convenience.
#### Update main `/README.md` Views section:
Replace:
```markdown
### Views
- `GET /views/glimpse/:id` - Get Notion page data as JSON (filtered for data consumption)
With:
### Views
- `GET /views/glimpse/:id` - Get Notion page data with related content as JSON (filtered for data consumption)
- `GET /glimpse/:id` - Same as above, alternative endpoint
Replace:
## Examples
- `getDatabaseById(id)` - retrieve specific database from Notion
- `searchNotionDatabases()` - search for all databases
- `updatePageUrl(pageId, url)` - update a Notion page's URL property
With:
## Examples
- `getDatabaseById(id)` - retrieve specific database from Notion
- `getPageById(id)` - retrieve specific page from Notion
- `getPageBlocks(id)` - retrieve page content blocks from Notion (with nested blocks recursively)
- `getMultiplePages(ids)` - batch retrieve multiple pages with their blocks from Notion (with nested blocks recursively)
- `searchNotionDatabases()` - search for all databases
- `updatePageUrl(pageId, url)` - update a Notion page's URL property
- Relation property: "Glimpse content" (exact match required)
- Sort property: "Order" (exact match required)
- Recursive fetching: Uses
getAllBlocksRecursively()
to fetch all nested blocks - Error resilience: Individual block failures don't break the entire operation
- Performance optimization: Uses
page_size: 100
for fewer API calls - Complete structure: Supports all Notion block types with children (callouts, lists, tables, columns, toggles, etc.)
- All or nothing: If any related page fails, entire request fails
- Individual block resilience: If fetching children of a specific block fails, logs warning but continues
- Consistent responses: Always use service response pattern
- Detailed errors: Include specific error details in failure responses
- Concurrent fetching: Use
Promise.all()
for parallel page fetching - Recursive API calls: More requests due to nested block fetching
- Batch operations: Single service call handles multiple pages
- Reasonable limits: Designed for up to 10 related pages
- Graceful degradation: Individual block failures don't break entire response
- Button filtering: Remove
type: "button"
properties from all pages - Sorting: Sort by
Order.number
property (ascending, default 0 for missing) - Block extraction: Use
blocks.results
from recursive fetch - Nested structure: Blocks with children have
children
array property
- Backward compatible: Existing clients continue to work
- Additive: New
glimpseContent
array added to existing response - Consistent: Maintains service response pattern with success/data/timestamp
- Rich content: Full nested block structures for complete content representation
- Lists: Bulleted, numbered, to-do items with nested items
- Callouts: With nested content blocks
- Code blocks: With syntax highlighting
- Tables: With cell content
- Columns: With nested blocks in each column
- Toggles: With hidden/expandable content
- Quotes: With nested formatting
- Synced blocks: With synchronized content
- Any other block type: That supports children
- Test with no related content: Should return empty
glimpseContent
array - Test with related content: Should return sorted pages with complete nested blocks
- Test nested structures: Verify callouts, lists, tables render with full hierarchy
- Test error cases: Invalid page IDs should return failure responses
- Test individual block failures: Should continue with warnings, not fail entirely
- Test authentication: Unauthenticated requests should show login page
- Test performance: Monitor response times with deeply nested content
{
"type": "bulleted_list_item",
"bulleted_list_item": {
"rich_text": [{"plain_text": "Parent item"}]
},
"has_children": true,
"children": [
{
"type": "bulleted_list_item",
"bulleted_list_item": {
"rich_text": [{"plain_text": "Child item"}]
},
"has_children": false
}
]
}
{
"type": "callout",
"callout": {
"rich_text": [{"plain_text": "Important note"}],
"icon": {"emoji": "š”"}
},
"has_children": true,
"children": [
{
"type": "paragraph",
"paragraph": {
"rich_text": [{"plain_text": "Additional details inside callout"}]
}
}
]
}
This implementation provides complete nested block structures that will properly represent all rich content types including callouts, code blocks, lists, tables, columns, and any other nested Notion block structures.