FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
lightweight
lightweightglimpse2-runbook-view-glimpse-save-login-react
Remix of lightweight/glimpse2-runbook-view-glimpse-save-login
Public
Like
glimpse2-runbook-view-glimpse-save-login-react
Home
Code
8
_townie
13
backend
7
frontend
9
shared
3
.vtignore
README.md
deno.json
H
main.tsx
Branches
2
Pull requests
Remixes
History
Environment variables
6
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
_townie
/
04-view.md
Code
/
_townie
/
04-view.md
Search
9/5/2025
Viewing readonly version of main branch: v23
View latest version
04-view.md

Instructions for Adding /views/glimpse/:id Endpoint

Here are the step-by-step instructions that would lead directly to the correct, clean implementation:

1. Analyze the Requirements

  • 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

2. Examine Current Architecture

Check existing services to understand data access patterns

cat /backend/services/notion.service.ts

Check existing routes structure

cat /backend/routes/views/\_views.routes.ts

Understand authentication flow

cat /main.tsx # Look for auth middleware setup

3. Identify Missing Service Method

  • Current service has getDatabaseById() for database metadata
  • Need getPageById() for individual page data
  • Service should return full Notion response for flexibility

4. Add Service Method First

// 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(),
    };
  }
}

5. Add Database Pages Service Method

// Add to /backend/services/notion.service.ts
export async function getDatabasePages(databaseId: string) {
  try {
    const response = await notion.databases.query({
      database_id: databaseId,
    });
    return {
      success: true,
      data: response,
      timestamp: new Date().toISOString(),
    };
  } catch (error) {
    return {
      success: false,
      error: error.message,
      timestamp: new Date().toISOString(),
    };
  }
}

6. Create Controller with Minimal Processing

// 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: "Page ID is required" }, 400);
}

const result = await getPageById(id);

if (!result.success) {
  return c.json({ error: "Failed to fetch page 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);
}

7. Add Route

// 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;

8. Update Documentation

Add to /backend/routes/views/README.md

GET /views/glimpse/:id

  • 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

9. Key Design Principles

✅ 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

10. Response Structure

{ "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" }

11. Dashboard Integration for Real Page ID Testing

Add demo page links to dashboard for user-friendly testing with real Notion page IDs

11a. Update Dashboard Route Data Fetching

// Update /backend/routes/views/dashboard.tsx imports and data fetching
import { getHealthStatus } from "../../controllers/health.controller.ts";
import { getDatabasePages } from "../../services/notion.service.ts";

export default async (c) => {
  const userEmail = c.get("userEmail");
  
  // Fetch health status for display
  const healthData = await getHealthStatus();

  // Fetch demo database pages for glimpse endpoints
  const demoDatabaseId = Deno.env.get("GLANCE_DEMOS_DB_ID");
  let demoPages = [];
  let demoPagesError = null;
  
  if (demoDatabaseId) {
    const demoPagesResult = await getDatabasePages(demoDatabaseId);
    if (demoPagesResult.success) {
      demoPages = demoPagesResult.data.results || [];
    } else {
      demoPagesError = demoPagesResult.error;
    }
  }

11b. Add Demo API Endpoints Section to Dashboard HTML

Add this section after the webhook section closes, before the main content div closes:

<div class="webhook-section">
  <h3>🔍 Demo API Endpoints</h3>
  
  ${demoDatabaseId ? `
    ${demoPages.length > 0 ? `
      <p>Test the <code>/views/glimpse/:id</code> endpoint with these demo pages:</p>
      <div style="margin-bottom: 20px;">
        ${demoPages.map(page => {
          const pageTitle = page.properties?.Name?.title?.[0]?.plain_text || 
                           page.properties?.Title?.title?.[0]?.plain_text || 
                           'Untitled Page';
          const glimpseUrl = `${c.req.url.split('/')[0]}//${c.req.url.split('/')[2]}/views/glimpse/${page.id}`;
          return `
            <div style="margin-bottom: 10px;">
              <a href="${glimpseUrl}" target="_blank" style="
                display: inline-block;
                background: #007bff;
                color: white;
                text-decoration: none;
                padding: 8px 12px;
                border-radius: 4px;
                font-size: 14px;
                margin-right: 10px;
              ">📄 ${pageTitle}</a>
              <code style="font-size: 12px; color: #6c757d;">${glimpseUrl}</code>
            </div>
          `;
        }).join('')}
      </div>
    ` : `
      ${demoPagesError ? `
        <p style="color: #dc3545;">❌ Error loading demo pages: ${demoPagesError}</p>
      ` : `
        <p style="color: #6c757d;">No demo pages found in the configured database.</p>
      `}
    `}
  ` : `
    <p style="color: #6c757d;">Demo database not configured. Set <code>GLANCE_DEMOS_DB_ID</code> environment variable.</p>
  `}
</div>

12. Key Decision Points

✅ 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

13. Benefits of This Approach

  • 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
  • User-Friendly Testing: Dashboard provides real page IDs for immediate testing

14. When to Add Controller Processing

✅ 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:

  1. Briefly summarize the work that was just completed and why it's important
  2. Provide the URL to the dashboard where users can see the live demo API endpoint links. Tell the user that they can copy and paste that URL into a new tab to see it live.
  3. Explain that users can click on the demo page links in the dashboard to test the /views/glimpse/:id endpoint with real Notion page data

Key Benefits Achieved:

  • Users get real Notion page IDs for testing (not fake /test-id)
  • One-click access to live API responses from the dashboard
  • Self-documenting API with automatically updated demo links
  • Proper architectural separation maintained

Next step: create an endpoint at /glimpse

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

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.