FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
lightweight
lightweightglimpse2-runbook-test
Remix of lightweight/glimpse2-runbook
Public
Like
glimpse2-runbook-test
Home
Code
7
_townie
13
backend
7
frontend
1
shared
1
.vtignore
deno.json
H
main.tsx
Branches
3
Pull requests
Remixes
History
Environment variables
5
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
/
01-scaffold.md
Code
/
_townie
/
01-scaffold.md
Search
9/3/2025
Viewing readonly version of main branch: v28
View latest version
01-scaffold.md

Val Scaffolding Instructions

Read through all of these instructions in order to understand the architecture of this Val, and implementation details important to this Val, and then once you have it all in your memory you should get to work.

If anything is unclear, you should stop and ask me for clarification before proceeding. Do not write code if you are unsure of what is expected.

Important: there are checkpoints in these instructions where you are directed to stop and ask permission before proceeding. Follow those instructions and do not proceed without permission.

Architecture Overview (Read This First!)

This Val follows a strict layered architecture. Understanding these layers is critical before writing any code:

Layer Responsibilities

Services (/backend/services/):

  • Single-purpose functions that make direct API calls to external systems
  • Pure functions that handle HTTP transport layer (response.ok)
  • Example: getDatabaseById(id), searchNotionDatabases()
  • Should NOT handle environment variables, business logic, or orchestrate multiple calls
  • Return consistent { success: boolean, data?: any, error?: string } format

Controllers (/backend/controllers/):

  • Business logic and orchestration between services
  • Handle environment variables and configuration
  • Coordinate multiple service calls
  • Implement business rules and validation
  • Handle service business logic layer (result.success)
  • Example: getHealthStatus(), validateConfiguredDatabases()

Routes (/backend/routes/):

  • HTTP request/response handling only
  • Call controllers, never services directly
  • Minimal logic - just HTTP concerns (parsing, validation, response formatting)
  • Example: parsing request params, calling controller, returning JSON

Service Response Pattern

We use a consistent two-layer response handling strategy:

Layer 1: HTTP Transport Layer (response.ok)

  • Used when checking responses from external APIs (Notion, etc.)
  • Validates HTTP request succeeded (status 200-299)
  • Handles network failures, auth issues, server errors

Layer 2: Service Business Logic (result.success)

  • Used when checking our ServiceResponse objects
  • Validates business operation succeeded
  • Handles application-level failures, data validation, business rule violations

Implementation Steps

Step 1: Create Directory Structure

Create this directory structure with README.md files:

  • main.tsx - entrypoint for this Val, where the Hono app is initialized
  • /backend - directory for backend code
    • /routes - HTTP route handlers organized by functionality
    • /api - JSON API endpoints for frontend/backend communication
    • /tasks - Webhook handlers for Notion integrations
    • /views - Server-side rendered views for frontend
    • /services - External API integrations (pure API calls only)
    • /controllers - Business logic coordination between routes and services
    • /crons - Scheduled task handlers
    • /types - Shared TypeScript type definitions
    • /utils - Common utility functions
  • /frontend - Client-side files that render in the browser
  • /shared - Files used by both backend and frontend

Each route subdirectory should have a _[directory_name].routes.ts file that mounts routes for that section.


Townie, stop here! Ask for permission to proceed.


Step 2: Set Up Main App and Route Structure

main.tsx Set up a Hono app like this:

import { Hono } from "npm:hono@3.12.12";

// Import route modules
import apiRoutes from "./backend/routes/api/\_api.routes.ts";
import taskRoutes from "./backend/routes/tasks/\_tasks.routes.ts";
import viewRoutes from "./backend/routes/views/\_views.routes.ts";

const app = new Hono();

// Unwrap Hono errors to see original error details
app.onError((err, c) => {
  throw err;
});

// Mount route modules
app.route("/api", apiRoutes);
app.route("/tasks", taskRoutes);
app.route("/views", viewRoutes);

export default app.fetch;

Route Module Template Each _[directory].routes.ts file should follow this pattern:

import { Hono } from "npm:hono@3.12.12";

const app = new Hono();

// TODO: Add routes here

export default app;

Townie, stop here! Ask for permission to proceed.


Step 3: Create Service Layer (API Calls Only)

Create /backend/services/notion.service.ts with ONLY direct Notion API calls:

Notion Service Requirements:

  • Import Notion: import { Client } from "npm:@notionhq/client";
  • Initialize: const notion = new Client({ auth: Deno.env.get("NOTION_API_KEY") });
  • Create these functions (API calls only, no business logic):
    • getDatabases() - search for all databases
    • getDatabaseById(databaseId: string) - retrieve specific database

Service Function Pattern:

export async function getDatabaseById(databaseId: string) {
  try {
    const response = await notion.databases.retrieve({
      database_id: databaseId,
    });
    return {
      success: true,
      data: response,
      timestamp: new Date().toISOString(),
    };
  } catch (error) {
    return {
      success: false,
      error: error.message,
      timestamp: new Date().toISOString(),
    };
  }
}

Important: Services should NOT handle environment variables for database IDs or orchestrate multiple calls. That's controller responsibility.


Townie, stop here! Ask for permission to proceed.


Step 4: Create Controller Layer (Business Logic)

Create /backend/controllers/health.controller.ts that orchestrates services:

Controller Requirements:

  • Import service functions: import { getDatabaseById } from "../services/notion.service.ts";
  • Handle environment variables (GLANCE_DEMOS_DB_ID, GLANCE_CONTENT_DB_ID, GLANCE_INTERACTIONS_DB_ID)
  • Orchestrate multiple service calls
  • Implement business logic for health checking

Required Controller Functions:

  • getConfiguredDatabases() - Check all configured databases using environment variables
  • getHealthStatus() - Generate formatted health response for API

Controller Function Pattern:

export async function getConfiguredDatabases() {
  const demosDbId = Deno.env.get("GLANCE_DEMOS_DB_ID");
  // ... get other DB IDs

  const databases = { demos: null, content: null, interactions: null };
  const errors: string[] = [];

  // For each configured database:
  if (demosDbId) {
    const result = await getDatabaseById(demosDbId); // Call service
    if (result.success) {
      databases.demos = result.data;
    } else {
      errors.push(`Demos DB: ${result.error}`);
    }
  } else {
    errors.push("GLANCE_DEMOS_DB_ID not configured");
  }

  // ... repeat for other databases

  return {
    success: errors.length === 0,
    data: databases,
    errors: errors.length > 0 ? errors : undefined,
    timestamp: new Date().toISOString(),
  };
}

Townie, stop here! Ask for permission to proceed.


Step 5: Create Route Layer (HTTP Handling)

Add Health Endpoint

In /backend/routes/api/_api.routes.ts:

import { Hono } from "npm:hono@3.12.12";
import { getHealthStatus } from "../../controllers/health.controller.ts";

const app = new Hono();

app.get("/health", async (c) => {
  const healthStatus = await getHealthStatus();
  return c.json(healthStatus);
});

export default app;

Add Root Route

In main.tsx, add a root route:

// Add this before mounting route modules
app.get("/", async (c) => {
  return c.html(`<h1>Stay tuned...</h1>`);
});

Townie, stop here! Ask for permission to proceed.


Architecture Validation

Before proceeding, verify your implementation follows the architecture:

  • Services only make direct API calls (no environment variables, no orchestration)
  • Controllers handle business logic and coordinate services
  • Routes only handle HTTP concerns and call controllers
  • No service calls directly from routes
  • Environment variables handled in controllers, not services
  • Consistent response patterns across all layers

Expected Behavior

The health endpoint should return:

{
  "status": "ok",
  "timestamp": "2025-07-14T18:26:28.270Z",
  "service": "glance-demos",
  "databases": {
    "configured": true,
    "demos": "connected",
    "content": "connected",
    "interactions": "connected"
  }
}

This architecture is intended to create clear separation of concerns and should make the codebase maintainable and testable.


Townie, stop here! Before proceeding to additional steps, confirm that this step is working correctly.

If there was an error regarding environment variables: remind the user to upload vars.env to the Environment Variables section of this Val, and to then let you know when that step is complete so you can retry.

If everything is working as intended: conclude this step with these messages:

  1. Briefly summarize the scaffolding that was just completed and why it's important
  2. Provide the URL to the health endpoint so the user can see that the endpoint is live. (Tell the user that they can copy and paste that URL into a new tab to see it live.)

Next step: web route authentication

Tell the user that the next step is to add authentication to the web routes of this Val, and that they should copy this line and paste it into Townie:

Add auth to this Val with what you see in /_townie/02-auth.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.