History & Walking Skeleton

Inspecting the Client and API


Learning Objectives

  • You can locate the practice application’s browser interaction and API route.
  • You can distinguish Svelte, SvelteKit, Hono, and Deno by responsibility.
  • You can make a small source change and predict which check might observe it.

We have run the application and looked at how Compose connects its services. Now let’s follow the counter and todo interaction into the source files, then try small changes of our own. Refer to the JavaScript Quick Reference for functions, objects, or async/await; Part 2 will explain the framework-specific behavior.

The page and its component

Open client/src/routes/+page.svelte. It imports two components:

import Counter from "#lib/Counter.svelte";
import TodoList from "#lib/TodoList.svelte";

The practice application’s package.json maps #lib/* to src/lib/*, so we can follow the first import to the counter file below. Keep this mapping when working with the practice application; tutorials for other versions may use different aliases.

Open client/src/lib/Counter.svelte:

<script lang="ts">
let count = $state(0);
</script>
<button type="button" onclick={() => (count += 1)}>Count: {count}</button>

Here is the counter we clicked earlier. It starts at zero, and clicking the button increases count; Svelte then updates the value shown on the button. The $state syntax and template expression belong to Svelte, rather than plain HTML or general JavaScript globals. If they are unfamiliar, focus for now on connecting the click, the changed value, and the visible result.

Make the counter count down

0 / 5 points

Task

Change the isolated src/lib/Counter.svelte component so each click decreases its count by one.

Requirements

  • Initially, the button shows Count: 0.
  • After one click it shows Count: -1, and after two clicks Count: -2.
  • Further clicks continue counting down by one. Keep the button available for the next click.

Any implementation that produces this behavior is acceptable. This is a separate experiment: keep the practice application’s counter incrementing for the later testing examples.

Submit

Edit and submit the component in the browser. The grader supplies the exercise environment and tests; this exercise has no live Run preview. Submit only src/lib/Counter.svelte, keeping that path if uploading files.

Showing Count: 0 and decreasing to Count: -1 on the first click earns 2 points. Continuing to decrement on further clicks earns another 3 points.

The exercise starter uses its own counter. Continue reading the practice application with its original incrementing counter and tests.

The todo interaction

Next, open client/src/lib/TodoList.svelte and find const loadTodos = async () => { ... }. The Load todos button calls this function to start the request, giving us one click whose network activity we can follow.

The function uses browser-safe configuration and checks the response before reading JSON:

const baseUrl = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
const response = await fetch(`${baseUrl}/api/todos`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
todos = await response.json();

Read the surrounding function and markup together, looking for what the user sees at each stage. While the request is loading, the button is disabled and the page displays progress. A successful request stores the returned rows; if the array is empty, the page shows No todos yet. instead of the initial invitation to load items. A failed request displays an alert and lets the user click again to retry. This gives us a way to distinguish an empty result from a request that failed.

The type Todo describes the expected row shape for tooling. It does not cause response.json() to validate arbitrary network data. Here we are inspecting the response from our supplied API. We will return to functions, state, components, and network outcomes step by step in Part 2.

Try making the waiting state visible in the small page below. The request and state changes are provided; your task is to add the loading message to its markup.

Show when a todo request is loading

0 / 10 points

Task

This isolated Svelte page contains a counter and a Load todos button. The supplied function requests http://localhost:8000/api/todos and keeps loading true until that request succeeds or fails. This page focuses on the request state; it does not render the returned todo rows.

Add a paragraph showing the imported loadingMessage while a request is pending.

Requirements

  • Render the paragraph only while loading is true. It must be absent before a request and disappear after success, an HTTP error, or a network failure. It must work again on later requests.
  • Keep the Load todos button disabled while its request is pending and available again when the request finishes. Keep the supplied request function and error display working.
  • Keep the imported heading visible. The counter must still change from Count: 0 to Count: 1, including while the request is pending.
  • Use the imported values rather than copying their text. The tests supply different heading and loading-message values.

In Svelte markup, {#if condition} and {/if} enclose content that is shown only when the condition is true. For example:

{#if showHint}
<p>{hint}</p>
{/if}

The editor contains the complete page, src/routes/+page.svelte. Its support module, src/lib/todo-ui.ts, is supplied during grading:

export const pageHeading = "Todo request";
export const loadingMessage = "Loading todos...";

Submit

Submit only src/routes/+page.svelte in the browser, keeping that path if uploading files. Do not submit the support module or exercise environment configuration; the grader supplies them and controls the request responses. No running API is needed for grading.

The complete loading behavior earns 7 points. Preserving the heading earns 1 point and the counter 2 points, only when the loading paragraph also appears and disappears during a successful request. Returning the unchanged exercise starter therefore does not earn those preservation points. A page that does not compile earns nothing, so check the reported compiler message first.

Routes, repositories, and the database client

Let’s look at the other end of the request. Open api/src/app.ts, where we can start with the health handler:

app.get("/health", (c) => c.json({ status: "ok" }));

The todo handler calls a repository function:

app.get("/api/todos", async (c) => {
const todos = await findAll();
return c.json(todos);
});

For a request to /api/todos, Hono selects this handler, which calls findAll and returns the rows as a JSON response. We can follow findAll to todoRepository.ts, where the SQL query is written. The file database.ts creates a shared Postgres.js client from DATABASE_URL; repositories reuse that client rather than opening and closing a new one for each request. We will follow the complete query in Chapter 1.7.

These imports explain a detail you will encounter in tests: importing app.ts also imports the repository and database module, so DATABASE_URL must be configured even for a direct /health test. Postgres.js waits until a query runs to open a connection. Since the health handler only returns JSON, we need a request that actually queries PostgreSQL to check whether the database is reachable.

Open api/src/app-run.ts:

import { app } from "./app.ts";
import { sql } from "./database.ts";
const server = Deno.serve({ port: 8000 }, app.fetch);
const shutdown = async () => {
await server.shutdown();
await sql.end();
};
Deno.addSignalListener("SIGINT", shutdown);
Deno.addSignalListener("SIGTERM", shutdown);

The routes in app.ts describe how the application responds. This separate entry point starts listening on port 8000 and closes the shared database client during shutdown. Keep sql.end() in the shutdown code rather than a request handler, because later requests need the same client. This separation also helps us test: importing app.ts does not import the entry point that starts the listener. We will build on this structure in Part 3.

The API’s import map defines "@src/": "./src/". Tests therefore use import { app } from "@src/app.ts" instead of counting directories in a relative path. The client uses its separate #lib mapping; neither alias changes where code executes.

Use the health handler as a model for one more response. The next activity supplies a small API; add the route described in its handout, leaving its existing routes working.

Add an about route to an exercise starter API

0 / 10 points

Task

Add an about route to the supplied api/src/app.ts module.

Requirements

  • GET /api/about returns status 200 and exactly the JSON object { "course": "Web Software Development" }.
  • Keep GET /health returning status 200 and { "status": "ok" }.
  • Keep GET /api/todos returning status 200 and the current rows obtained from the supplied findAll() repository function.
  • Export the Hono application as app. Do not start a listening server in this module. Keep the existing middleware and imports working.

Submit

Edit and submit the supplied file in the browser, or upload only api/src/app.ts with that directory structure. Do not submit a repository, database module, or server entry point.

The grader supplies dependencies and a controlled todo repository. It type-checks the module (1 point), checks the about status (3 points), checks the about JSON (5 points), and checks both preserved routes (1 point). No running API or database is needed for grading.

Where Deno runs the code

Deno appears in both Dockerfiles, which can make it hard to tell where the application code runs at first. In the API container, Deno executes the server program. In the client container, it runs SvelteKit and Vite tooling. The resulting browser application runs in the user’s browser, with browser APIs.

SvelteKit also supports server-side features, but the practice application sets ssr = false in client/src/routes/+layout.ts and configures a static adapter in vite.config.ts. Here, browser code sends API requests directly to Hono. The client and API therefore still have separate roles, even though both development services use Deno.

The Deno Node/npm compatibility documentation explains how Deno can use npm packages. This lets our client use those packages without requiring you to install the npm command-line tool on your host.

A library-status application

0 / 5 points

A library-status application has a Svelte component, a SvelteKit page, a Hono application in api/src/app.ts, and a Deno server entry in api/src/app-run.ts. Its client and API are separate services, each started using Deno. Which description assigns each responsibility correctly?

Keep the framework shell intact

Alongside the components, you will find src/app.html, vite.config.ts, tsconfig.json, and package.json. These files supply the framework setup that lets us work on the components. The supplied tasks also run svelte-kit sync before development, builds, and tests to keep the generated setup in sync.

Keep these supplied files in place while making the source changes in this part. In particular, app.html is the framework’s HTML shell; the standalone HTML example in the quick reference is not a replacement for it. Keep the supplied TypeScript configuration too, since an older scaffold may expect a different setup. You can learn how the application behaves without understanding every configuration option yet.

Six requested source changes

0 / 5 points

Consider this source tree and its relevant behavior:

client/src/lib/AvailabilityBadge.svelte
owns a button that shows and hides local details
client/src/lib/BranchStatus.svelte
reads VITE_LIBRARY_API, fetches /branch-status, and renders returned data
client/src/routes/+page.svelte
composes the components and renders the page caption
api/src/app.ts
defines GET /branch-status and its JSON response
api/src/branchRepository.ts
queries stored branch rows through the shared database client
api/src/database.ts
creates the shared Postgres.js client from DATABASE_URL
api/src/app-run.ts
starts Deno.serve on port 8000 and closes the shared client at shutdown

Which mapping correctly locates the page caption, local click state, browser API-base value, JSON field and route, SQL query, and listening port?

Check a local source change

You can also try the about route in your practice application. This is optional and separate from the browser-editor submission. Add the handler to the existing api/src/app.ts, keeping its imports and other routes. After saving, review the changed source and the API watcher output, then request the new route:

Terminal window
docker compose logs --tail=20 api
curl -i http://localhost:8000/api/about

Compare the response with the handout and check that the existing health and todo interactions still work. Once these checks succeed, you have added an API response while keeping the earlier behavior working. The watcher reloads source changes; adding or changing dependencies requires the steps described in Chapter 1.4.

Check Your Understanding

  1. Which file places the counter on the page, and which file defines what happens when it is clicked?
  2. What changes in the todo component while a request is pending, and how does an empty result differ from a failed request?
  3. What work belongs in a Hono route, and what work belongs in a repository function?
  4. Why does the application close its shared database client during shutdown rather than after each request?
  5. Where does Deno run in this setup, and where does the code responding to a counter click run?