Deno Fresh Getting Started: Islands, APIs & Testing
Tags: webdev, deno, typescript
Published: 2023-02-03 19:51:18

---
title: "Deno Fresh Getting Started: Islands, APIs & Testing"
published: "true"
description: "Deno Fresh Getting Started 🦕 in this second part of the getting started with Deno Fresh series, we look at Islands of interactivity & testing."
tags: "webdev, deno, typescript"
canonical_url: "https://rodneylab.com/deno-fresh-getting-started/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3l9r2d4544eo0tce9fdo.png"
---

## 🎬 Getting Started with Deno Fresh: Part II

In this second post on Deno Fresh getting started, we see how Fresh **islands of interactivity** work. As well as that we look how you can use Deno&rsquo;s in-built testing in your Fresh app.

In the first part of the series we saw how you can add web pages to your Fresh app using its file-based routing system.  We extend on what we saw there, in this follow-up, to see how you can also add **API routes** as well as **resource routes** (serving an XML **RSS feed**, for example).

We started the first post looking at why you might consider Deno Fresh and saw how you can set up Deno on your system and **spin up your first Fresh app**.  If you are new here, please do skim through the previous post if something here does not click.

We will start with a quick introduction to **partial hydration** and the Islands architecture. Then we will see how Deno implements it&rsquo;s opt-in Island components philosophy.  Then we move on to look at **testing** and API routes.

## 🤔 Why Deno Fresh for Content Sites?

Deno Fresh is a fantastic choice for **content sites**. Here we are talking about blog sites, documentation sites and such like. In the near-recent past statically generated sites were the most popular choice for content sites, with something like GatsbyJS a common choice.  The reasoning behind choosing static generators over server-side rendered (SSR) ones were principally **speed and security**.  There was a Developer Experience trade-off though, which lay in the fact that sites took long to build (as the static content was generated). However, the user got the benefit when the static content could be served quickly from a **global <abbr>CDN<tool-tip inert role="tooltip">Content Delivery Network: global system of servers distributed close to users</tool-tip></abbr>**.

Deno Fresh is fast despite being a server-side rendered generator.  Designed for the modern, **serverless world**, using Preact it generates pages on the server adding JavaScript only where needed. That last little trick &mdash; shipping **zero JavaScript by default** &mdash; lets Deno apps load faster on the user device. On top, because fewer bytes need to be shipped to the user&rsquo;s device, the device receives the page data quicker.  This pattern, especially with the performance benefits of using **Preact** over React for rendering, is what makes Fresh a good choice for content sites. Typically the parts of content site page which are JavaScript-heavy are distinct and isolated islands of interactivity.  This is what we look at next.

## 🧵 10 More Deno Fresh Getting Started Tips

Following on from the previous <a href="https://rodneylab.com/getting-started-deno-fresh/">Getting Started with Deno Fresh post</a> here are ten more Deno Fresh getting started tips.

### 1. 🏝️ Islands
Deno Fresh ships zero JavaScript by default but makes islands available for when you need interactivity.  A great example of an Island of Interactivity on a blog post page might be the social sharing buttons.  Most of the page will be text (no JavaScript there).  There might be a bit of JavaScript to control opening a hamburger menu or for a newsletter sign-up form.

Those instances will typically be isolated to small parts of the page and in fact, by using the platform, we can remove JavaScript completely from the sign-up form.  The Deno Fresh philosophy is only to ship JavaScript for those parts of the page which need it, rather than for the whole page.  This means we ship less hydration code &mdash; the code which makes sure state is consistent for interactive parts of the page.

For this to work, we put our component which have state or interactivity in the `islands` directory of the project, rather than the `components` directory.  So returning to the social share buttons example, we can add that code to a `ShareButtons.tsx` file, which looks just like any other React component:

```tsx
export default function ShareButton({
  siteUrl,
  title,
}: ShareButtonProps) {
  const [webShareAPISupported, setWebShareAPISupported] = useState<boolean>(
    true,
  );

  useEffect(() => {
    if (typeof navigator.share === "undefined") setWebShareAPISupported(false);
  }, [webShareAPISupported]);

  const handleClick = () => {
    if (webShareAPISupported) {
      try {
        navigator.share({
          title,
          text: `Rodney Lab Newsletter ${title}`,
          url,
        });
      } catch (error: unknown) {
        setWebShareAPISupported(false);
      }
    }
  };

  return (
    <div class="share">
      {webShareAPISupported
        ? (
          <button onClick={handleClick}>
            <span class="screen-reader-text">Share</span>{" "}
            <ShareIcon width={24} />
          </button>
        )
        : (
          <div class="share-fallback">
            SHARE:
            <div class="share-buttons">
              <TelegramShareButton />
              <TwitterShareButton />
            </div>
          </div>
        )}
    </div>
  );
}
```

Here, we are using the WebShare API with **graceful degradation** for devices which do not yet support it.  To achieve that we:
- have a `useState` hook, assuming initially that the device supports the WebShare API,
- add a `useEffect` hook to feature detect the **WebShare API** and update are support assumption is needed,
- show the WebShare icon when the API is supported but show manual Telegram and Twitter share button otherwise.

All three of those points need JavaScript to work with to track state, to add interactivity or both.  This is definitely an island and to let Deno Fresh know it has to manage interactivity for us, we have to **put it in the Islands directory**.

![Deno Fresh Stack: two adjacent screen captures show the same site, one with a Web Share icon, the other with separate Twitter, Whats App and Telegram icons.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eki00rleqkywuwjxdxpp.png)

### 2. 🚉 Use the Platform

Deno Fresh leans heavily on **Web APIs**. Using these in your project you can work with Deno, limiting the use of islands and keeping your app **lightning fast**. For example returning to a blog site, we can add a newsletter sign-up form without needing to ship JavaScript to the browser:

```tsx
export const Subscribe: FunctionComponent<SubscribeProps> = function Subscribe({
  pathname,
}) {
return (
  <form
    action={pathname}
    method="post"
  >
    <h2>Subscribe to the newsletter</h2>
    <label for="email" class="screen-reader-text">
      Email
    </label>
      <input
        id="email"
        type="text"
        name="email"
        required
      />
      <button type="submit">Subscribe</button>
  </form>
  );
};
```

Here the opening `form` tag includes a method attribute set to `POST` as well as an action. The action can be the pathname for the current page, fed in to this component as a prop.

When the form is submitted, the device now sends a `POST` request to the same route. We just need to add a `POST` handler in the Fresh code for this page (much like we had in the <a href="https://rodneylab.com/getting-started-deno-fresh/#deno-fresh-route-request-handler">Tweet Queue Deno Fresh route file example</a>).

### 3. 🥊 Static Assets & Cache Busting

You will have files like **favicons** and perhaps **web manifests for <abbr>PWAs<tool-tip inert role="tooltip">Progressive Web Apps</tool-tip></abbr>** which do not need any processing. You can put them in your project&rsquo;s `static` folder. Deno will serve them for you from there.  As an example, `static/favicon.ico` will be served on your built site from `https://example.com/favicon.ico`.

That&rsquo;s not all Deno does for you though! There is a handy **cache busting feature**, even for these **static assets**!  What it does is add a hash to the filename (under the hood). How is this helpful? If you change a favicon (lets say you go for a different colour) and you keep the same file name as before.  Deno will recompute the file hash, this will be different to the previous one. 

This means browsers and CDNs will know to download the new coloured favicon instead of serving the cached one.  To make it work:

1. import the `asset` named import function from `$fresh/runtime.ts`,
2. when you include the favicon, image, CSS or other asset in a link tag, wrap it in a call to the `asset` function:

```tsx
import { asset } from "$fresh/runtime.ts";

export const Layout: FunctionComponent<LayoutProps> =
  function Layout() {
    return (
      <Fragment>
        <Head>
          <link rel="icon" href={asset("/favicon.ico")} sizes="any" />
          <link rel="icon" href={asset("/icon.svg")} type="image/svg+xml" />
          <link rel="apple-touch-icon" href={asset("/apple-touch-icon.png")} />
          <link rel="manifest" href={asset("/manifest.webmanifest")} />
        </Head>
        {children}
      </Fragment>
    );
  };
```

### 4. 💄 Styling

You can **choose Tailwind** for styling in the interactive prompts when you initialise your project.  If you **prefer vanilla CSS** then, of course, Fresh handles that too!  You can even **self-host fonts**! Add the CSS in the `static` folder (like we mentioned for favicons above). Then just remember to include it in the `Head` for your page or layout component markup (again just like for favicons):

```css
@font-face {
  font-family: Lato;
  font-style: normal;
  font-weight: 400;
  src: local(""), url("/fonts/lato-v23-latin-regular.woff2") format("woff2"),
    url("/fonts/lato-v23-latin-regular.woff") format("woff");
  font-display: swap;
}
```

If you do want to self-host the fonts, there is a <a href="https://gwfh.mranftl.com/">handy Web Fonts helper</a> which generates the CSS and lets you download the `.woff2` files you&rsquo;ll need.

```css
*,
:after,
:before {
  box-sizing: border-box;
}

* {
  margin: 0;
}

/* TRUNCATED ...*/
```

```tsx
import { asset } from "$fresh/runtime.ts";

export const Layout: FunctionComponent<LayoutProps> =
  function Layout() {
    return (
      <Fragment>
        <Head>
          <link rel="icon" href={asset("/favicon.ico")} sizes="any" />
          <link rel="icon" href={asset("/icon.svg")} type="image/svg+xml" />
          <link rel="apple-touch-icon" href={asset("/apple-touch-icon.png")} />
          <link rel="manifest" href={asset("/manifest.webmanifest")} />
          <link rel="stylesheet" href={asset("/styles/global.css")} />
          <link rel="stylesheet" href={asset("/styles/fonts.css")} />
        </Head>
        {children}
      </Fragment>
    );
  };
```

### 5. 🪚 Tooling

We mentioned earlier that there is no need to spend time configuring ESLint or Prettier in each Deno Fresh project you start.  Deno comes with its own linter and formatter all with sensible defaults.  To run these from the command line, just use:

```shell
deno fmt
 
deno lint
```
To have VSCode format on save see the <a href="https://rodneylab.com/getting-started-deno-fresh/#vscode">VSCode config in the quick tips in the previous Getting Started with Deno Fresh post</a>.  If you are a Vim person, <a href="https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#denols">try the denols LSP plugin for Neovim</a>.

#### 👀 TypeScript

Nothing to see here. TypeScript support come out with Deno out-of-the-box: no need to add a `tsconfig.json` or `typescript-eslint` config. Just start coding in TypeScript.

### 6. ☑️ Testing

Just like linting and formatting, Deno has testing built in &mdash; there is `0` test runner config. That said, you might want to set up a test script, just to fire off tests quicker.  Update your `deno.json` file to do this:

```json
{
  "tasks": {
    "start": "deno run -A --watch=static/,routes/ dev.ts",
    "test": "deno test -A",
  },
  "importMap": "./import_map.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}
```

Then to rattle off tests, in the Terminal type:

```shell
deno task test
```

If you have already used Jest or Vite, then there are no surprises when it comes to writing the tests themselves.  Import `assert` and friends from `std/testing/asserts` and you are already at the races!

```typescript
import { assert, assertEquals } from "$std/testing/asserts.ts";
import { markdown_to_html } from "@/lib/rs_lib.generated.js";

Deno.test("it parses markdown to html", async () => {
  // arrange
  const markdown = `
## 👋🏽 Hello You

* alpha
* beta
`;

  // act
  const { errors, html, headings, statistics } = await markdown_to_html(
    markdown,
  );

  // assert
  assert(typeof markdown_to_html === "function");
  assertEquals(
    html,
    `<h2 id="wave-skin-tone-4-hello-you">👋🏽 Hello You <a href="#wave-skin-tone-4-hello-you" class="heading-anchor">#</a></h2>
<ul>
<li>alpha</li>
<li>beta</li>
</ul>
`,
  );
});
```

### 7. ☎️ API Routes

You might use <abbr>API<tool-tip inert role="tooltip">Application Programming Interface</tool-tip></abbr> routes to handle back end operations.  This is another advantage of using Deno Fresh over a static site generator: API route handling is built in. You effectively get Serverless functions woven into your project.

The actual API route file is essentially just a regular route file handler.  You can name the file with a `.ts` extension in the API case though.  Here is an example where we send SMS message via the Twilio API from a Deno Fresh API route:

```typescript
import { HandlerContext } from "$fresh/server.ts";
import { encode as base64Encode } from "$std/encoding/base64.ts";

const TWILIO_SID = Deno.env.get("TWILIO_SID");
const TWILIO_AUTH_TOKEN = Deno.env.get("TWILIO_AUTH_TOKEN");

export const handler = async (
  _request: Request,
  _ctx: HandlerContext,
): Promise<Response> => {
  if (TWILIO_SID === undefined) {
  throw new Error("env `TWILIO_SID` must be set");
  }
  if (TWILIO_AUTH_TOKEN === undefined) {
    throw new Error("env `TWILIO_AUTH_TOKEN` must be set");
  }

  const authorisationToken = base64Encode(`${TWILIO_SID}:${TWILIO_AUTH_TOKEN}`);
  const body = new URLSearchParams({
    Body: "Hello from Twilio",
    From: "+4412345",
    To: "+4412345",
  });
  const response = await fetch(
    `https://api.twilio.com/2010-04-01/Accounts/${TWILIO_SID}/Messages.json`,
    {
      method: "POST",
      headers: {
        Authorization: `Basic ${authorisationToken}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body,
    },
  );
  const data = await response.json();
  console.log({ data });
  return new Response("Thanks!");
};
```
Although we put the file in an `api` subdirectory this is by choice and **not necessary**.

Notice the Deno way of Base64 encoding **BasicAuth parameters**. We import the encoder in line `2`, then use it in line `18` to generate Base64 string which we need to send in the Basic authorisation header in line `29`.

The rest just uses the same JavaScript APIs you already learnt from **<abbr>MDN<tool-tip inert role="tooltip">Mozilla Developer Network: popular documentation resource for Web Developers</tool-tip></abbr>** and use in **Remix, Astro or SvelteKit**.  Notice we return with a `Response` object.  You can just as easily return a redirect or server error. 

```typescript
return new Response("Auth failed", {
  status: 502,
  statusText: "Bad Gateway",
});
```

For convenience there are also `Response.redirect` and `Response.json`, providing a spot of syntactic sugar:

```typescript
return Response.redirect(`https://example.com/home`, 301);
```

```typescript
return Response.json({ message, data });
```

That last one adds extra convenience, saving you having to construct the customary headers manually.

### 8. 💰 Resource Routes

You might use resource routes to serve PDF files, JSON data or even an <abbr>RSS<tool-tip inert role="tooltip">RDF Site Summary: standard for web feeds allowing subscribers to receive news of updated content</tool-tip></abbr> feed like this example shows:

```typescript
import { Handlers } from "$fresh/server.ts";
import { getDomainUrl } from "@/utils/network.ts";
import { lastIssueUpdateDate, loadLatestIssue } from "@/utils/issue.ts";
 
export const handler: Handlers = {
  async GET(request, _context) {
    const domainUrl = getDomainUrl(request);
    const { publishAt: lastIssuePublishDate } = (await loadLatestIssue()) ?? {};
 
    const xmlString = `
		<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${domainUrl}/main-sitemap.xsl"?>
		<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
			<sitemap>
				<loc>${domainUrl}/issue-sitemap.xml</loc>
				<lastmod>${(await lastIssueUpdateDate()).toISOString()}</lastmod>
			</sitemap>
			<sitemap>
				<loc>${domainUrl}/page-sitemap.xml</loc>
				<lastmod>${lastIssuePublishDate?.toISOString()}</lastmod>
			</sitemap>
		</sitemapindex>`.trim();
 
    const headers = new Headers({
      "Cache-Control": `public, max-age=0, must-revalidate`,
      "Content-Type": "application/xml",
      "x-robots-tag": "noindex, follow",
    });
 
    return new Response(xmlString, { headers });
  },
};
```

### 9. 🧑🏽‍🔧 Middleware

Middleware or **Edge Functions** in Deno let you intercept incoming requests and run code snippets on them before proceeding. Again these are build into Deno Fresh.  As well as intercepting the incoming request, you can effectively alter the response. See the <a href="https://rodneylab.com/deno-fresh-middleware/">video on Deno Fresh Middleware for more explanation</a> on this.

Add a `_middleware.ts` file to any folder containing route files you want it to apply to:

```typescript
import { MiddlewareHandlerContext } from "$fresh/server.ts";

export async function handler(
  _request: Request,
  context: MiddlewareHandlerContext,
) {
  const response = await context.next();
  const { ok } = response;

  if (ok) {
    response.headers.set(
      "Strict-Transport-Security",
      "max-age=31536000; includeSubDomains; preload",
    );
    response.headers.set("X-Frame-Options", "DENY");
    response.headers.set("X-Content-Type-Options", "nosniff");
    response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
  }

  return response;
}
```

### 10. 🦀 WASM

WASM lets you write code in C, C++ or <strong>Rust</strong> but compile to to a module which can run in a JavaScript environment.  This lets you leverage efficiency or existing libraries in those ecosystems.  WASM is a first-class citizen in Deno and the `wasmbuild` module will build out a skeleton Rust WASM project and also compile from within your existing Deno project.

Although the process is not complicated, we will not go into it here as there is an <a href="https://rodneylab.com/deno-fresh-wasm/">example with fully working code on using Rust WASM with Deno Fresh</a>.

## 🙌🏽 Deno Fresh Getting Started: Wrapping Up

We have seen a lot in the last two Deno Fresh getting started articles.  I have tried to integrate my own learnings and take you beyond what is in the Deno Fresh docs.  I do hope this has been useful for you especially covering helpful details for anyone  new to Deno.  In particular, we have seen:

- how **Deno Fresh islands** work,
- examples of **using the platform with Deno Fresh**,
- an introduction to more advanced **Deno Fresh features** like WASM, API and resource routes.

Get in touch if you would like to see more content on Deno and Fresh. Let me know if indeed you have found the content useful or even if you have some possible improvements.

## 🙏🏽 Deno Fresh Getting Started: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter, <a href="https://toot.community/@rodney">@rodney@toot.community</a> on Mastodon and also the <a aria-label="Join the Rodney Lab Element Matrix room" href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on deno" href="https://rodneylab.com/tags/deno/">Deno</a>. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.