Getting Started with Deno Fresh & the Platform
Tags: webdev, deno, typescript, css
Published: 2023-01-18 10:20:22

---
title: "Getting Started with Deno Fresh & the Platform"
published: "true"
description: "Getting started with Deno Fresh: how to get going fast with Fresh, from setting up tooling to using the platform & deploying your new site."
tags: "webdev, deno, typescript, css"
canonical_url: "https://rodneylab.com/getting-started-deno-fresh/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8imb03rx1ib7thli6kd.png"
---
## 🍋 Why use Deno Fresh?

- **Deno Fresh** builds **fast sites** so you get first class **user experience (UX)**,
- it **uses the platform** so getting started with Deno Fresh is quick if you already know the **JavaScript APIs** (even if you have to learn them you are not locked into Fresh, use them with Remix, Astro or SvelteKit),
- **amazing Developer Experience (DX)** &mdash; it&rsquo;s serverless with **no build step**, deploys are **live** almost **instantly**.

## 🤷🏽 What is Deno Fresh?

Deno Fresh is a Next Gen site builder.  **It builds Server-Side Rendered (SSR)** sites and supports partial hydration.  Fresh runs in the Deno JavaScript runtime which **prioritises on security and performance**.  In Deno, JavaScript APIs are first-class citizens &mdash; it uses the platform.  This makes Deno quicker and easier to learn. Deno renders your site on the server and by default ships zero JavaScript to the browser.  This makes Fresh a fine choice for **content sites**.  By content sites we mean those which are largely documentation, blogs and such like, typically not heavily interactive.

When you need interactivity on a Fresh site, you can use Fresh's **partial hydration** feature set.  This means only the parts of the page which are interactive, also known as **islands of interactivity**, get hydrated. Hydration is just the step in the page loading in the browser where code allowing interactivity gets loaded and the state of these components becomes consistent.

## 🎬 How do you Spin up a Fresh Fresh App?

To get going you will need Deno in your development environment. If you do not yet have it installed, it is quick to set up from the Terminal with Homebrew on MacOS or a shell script on Linux:

```shell
# macOS
brew install deno
 
# Linux
curl -fsSL https://deno.land/install.sh | sh
 
# Test installation worked
deno --version
```

In contrast to Node.js runtime-based tooling, with Deno typically you run apps and access packages via a URL.  This is exactly what we can do to spin up a new Deno Fresh app.  Once you have Deno installed, type these commands in the Terminal to get going:

```shell
deno run -A -r https://fresh.deno.dev my-fresh-app && cd $_
deno task start
```

## 🧐 Getting Started with Deno Fresh: What's Inside?

```plaintext
.
├── components
│   └── Button.tsx
├── deno.json
├── deno.lock
├── dev.ts
├── fresh.gen.ts
├── import_map.json
├── islands
│   └── Counter.tsx
├── main.ts
├── routes
│   ├── [name].tsx
│   ├── api
│   │   └── joke.ts
│   └── index.tsx
└── static
    ├── favicon.ico
    └── logo.svg
```

- With Deno Fresh, you write your components in **Preact** (just pretend it&rsquo;s React if its your first time with Preact). Your Preact component files get placed in the `components` or `islands` directory depending on whether they are interactive or not. So a button which changes state can go in `islands` but a form which uses the platform and WebAPIs can go into `components`.
- **`deno.json`** this loosely maps to a `package.json` file in Node.js. The included `start` task is what you run to start your app and tells Deno to run the `dev.ts` file (also listed above),
- **`import_map.json`** as well as listing aliases for your packages, we will see how to add import aliases for project source files in this file, further down,
- **`main.ts`**: this is where we run the in-built web server from. You can add additional config for TLS and what not here,
- **`routes`**: this folder contains files which map to actual pages on your site. If you already use **Astro**, **Remix** or **Next.js** the file-based routing system will be very familiar.  `routes/index.tsx` will map to `https://example.com/` on your final site, while a `routes/about.tsx` will map to `https://example.com/about`. `routes/[name].tsx` is a dynamic page template. This provides mechanism for example, to create `/offices/london`, `/offices/berlin` and `/offices/mumbai` all from a single file without duplicated the content and with access to the city name parameter within the template (`/offices/[office-name].tsx`).
- **`static`** is for anything which does not need processing like favicons, `manifest.webmanifest` files for PWAs or **logos**.

### What's deliberately missing?

Notice **there is no**:
- `package.json`: with Deno Fresh,  you can use **`import_map.json`** for dependencies and the `tasks` field in `deno.json` for scripts,
- `tsconfig.json`: Deno Fresh comes with **in-built TypeScript support** and  selects **sensible defaults** under the hood to help you get going quicker on new projects,
- `eslint.js` & `eslint.config.js`: just use the `deno lint` command &mdash; no need to configure this, 
- `.prettierrc` & `.prettierignore`: similar to Rust, **formatting is also integrated** into the tooling. Run `deno fmt` when you want to tidy your code,
- `vitest.config.ts`: you guessed it&hellip; Deno has **integrated testing** built into the tooling too!

## 💫 9 Quick Tips for Getting Started with Deno

1. **Permissions**: Deno prioritises security and gives you more control over what your app has access to. 
```json
{
  "tasks": {
    "start": "deno run -A --watch=static/,routes/ dev.ts"
  }, // ...TRUNCATED
}
```
The `-A` flag in the `start` script grants our app access to all permissions including the file system (read and write), environment variables and the network.  If you want finer grained control remove it and restart your app.  You will now see prompts asking for various permissions in the Terminal.
<Image client:load {...imageProps[1]} />
1. **Environment Variables**
In the cloud you can define secrets in the Deno deploy console (or your host&rsquo;s equivalent).  Locally, as with other tooling you can use a `.env` file.  Remember to add this to your `.gitignore` file so secrets are not committed to your git&nbsp;repo.

```plaintext
.env
.DS_Store
```
```plaintext
DATABASE_PASSWORD="open-sesame"
```
For your project to access the `.env` file you can add the `https://deno.land/std@0.171.0/dotenv` package.  The Deno Fresh way to do this is to update the import map with a `$std/` alias:
```json
{
  "imports": {
    "$fresh/": "https://deno.land/x/fresh@1.1.2/",
    "preact": "https://esm.sh/preact@10.11.0",
    "preact/": "https://esm.sh/preact@10.11.0/",
    "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
    "@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
    "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1",
    "$std/": "https://deno.land/std@0.167.0/"
  }
}
```
As a best practice include the package version in the URL (to keep your app working as expected when maintainers introduce breaking changes in newer package versions). Next, import the load script from `dotenv` in the `dev.ts` file:
```typescript
#!/usr/bin/env -S deno run -A --watch=static/,routes/

import dev from "$fresh/dev.ts";
import "$std/dotenv/load.ts";

await dev(import.meta.url, "./main.ts");
```
Notice two things: you include the `.ts` extension in the path and you can use the new alias you just created.  If you wanted to, you could write out the entire URL in the import statement instead of using the alias:
```typescript
// EXAMPLE ONLY PREFER USING ALIASES AS ABOVE
import "https://deno.land/std@0.167.0/std/dotenv/load.ts";
```
The drawback here is that you will likely use `std` in other source files and when you want to upgrade to `0.168.0` you will need to update each file you include the full path in.  Using the alias approach, you only need to update the import map.

Finally in your source code you can then access the secret variable using `Deno.env.get()`:
```typescript
const DATABASE_PASSWORD = Deno.env.get("DATABASE_PASSWORD");
if (typeof DATABASE_PASSWORD !== 'string') {
  throw new Error("env `DATABASE_PASSWORD` must be set");
}
```
3. **VSCode Setup**: you might already have a `.vscode/extensions.json` file in the project (depending on how you answered the setup prompts).
```json
{
  "recommendations": [
    "denoland.vscode-deno"
  ]
}
```
This will cause VSCode to prompt you to install the  Deno extension when you open the project.  I noticed the extension was connecting to the network even in non Deno projects. Because of that, now I install it as normal, then in VSCode Extensions, find Deno, and click the **Disable** button. Finally, I click the dropdown to select **Enable (Workspace)**.
The extension adds linting hints and formatting capabilities. To format on save, update `.vscode/settings.json`:
```json
{
  "deno.enable": true,
  "deno.lint": true,
  "editor.defaultFormatter": "denoland.vscode-deno",
  "editor.formatOnSave": true,
}
```
I notice the auto-format saved files change ever so slightly when I run `deno fmt` from the Terminal, but imagine this will be addressed soon.
1. **Browser vs. Server** in other frameworks you might remember having to run a little check to make sure a block of code only runs in the client. You can see an example of how to achieve this in Deno in `components/Button.tsx`:
```tsx
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";

export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      {...props}
      disabled={!IS_BROWSER || props.disabled}
    />
  );
}
```
Here the button is disabled if the code is not running in the browser, but you might also use this if you are working with local storage (for example) and need to access it on the client via `window.localStorage`.

### 9 Quick Tips for Getting Started with Deno Continued (5 – 9)

5. **Deno std** we added the `std` package to `import_map.json` earlier to access `dotenv`. This is the **Deno Standard Library** (Deno has a standard library just like C and Rust). Apart from `dotenv` you might also use `std` for:
  - **`$std/crypto`**: to access WebCrypto APIs in your server side code,
  - **`$std/encoding`**: for extracting Frontmatter from Markdown files or to generate base64 for BasicAuth HTTP headers,
  - **`$std/http`** for working with HTTP cookies.
  - **`$std/path`** for file manipulations you might find in `node:path`,
  - **`$std/testing/asserts`**, **`$std/testing/bdd`** and **`$std/testing/snapshot.ts`** for Jest style `asserts` as well as Behaviour Driven Testing `describe` and `it`. For Chai style `expect` try  `https://cdn.skypack.dev/chai@4.3.4?dts`.

As an aside, the import `format` from `$std/fmt/bytes.ts` for formatting file sizes into easily human readable strings.
6. **Finding Packages**: Deno is a little different to Node.js in that you do not have to use NPM as your package repository and also that you can import packages from URLs.  Under the hood, Deno caches the packages locally (a little like pnpm) so that you do not need to download them afresh each time you start up your app.  Three sources for third party modules are:
  - Deno X: `https://deno.land/x?` find Deno specific modules here,
  - Skypack: `https://cdn.skypack.dev/` an alternative with NPM packages,
  - esm.sh: `https://esm.sh/` another NPM package alternative.
<Image client:load {...imageProps[0]} />

7. **More Aliases** as well as adding aliases for your external modules, you can also define them for your project folders. As an example, update `import_map.json`:
```json
{
  "imports": {
    "@/": "./",
    "$fresh/": "https://deno.land/x/fresh@1.1.2/",
    "preact": "https://esm.sh/preact@10.11.0",
    "preact/": "https://esm.sh/preact@10.11.0/",
    "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
    "@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
    "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1",
    "$std/": "https://deno.land/std@0.167.0/"
  }
}
```
Now we can tidy up the import statements in `routes/index.tsx`:
```typescript
import { Head } from "$fresh/runtime.ts";
// import Counter from "../islands/Counter.tsx";
import Counter from "@/islands/Counter.tsx";
```
This can make your code look cleaner and also make it easier mentally to map where the import is coming from.
1. **404 Page**: create a custom page not found template by adding a `routes/_404.tsx` file to your project:
```tsx
import { Head } from "$fresh/runtime.ts";
import { UnknownPageProps } from "$fresh/server.ts";
 
export default function NotFoundPage({ url }: UnknownPageProps) {
  const title = "Page not found 😢";
  const description = "I don’t think you are supposed to be here!";
 
  return (
    <>
      <Head>
        <title>{title}</title>
      </Head>
      <main>
        <h1>{title}</h1>
        <p>
          Not sure: <code>{url.pathname}</code> exists mate!
        </p>
      </main>
    </>
  );
}
```
Note we use the `Head` component here to add the `<title>` element to the HTML `<head>` for our page.
1. **Where do Favicons go?** we mentioned earlier you can put favicons, logos and manifest.webmanifest for a PWA in the project `static` folder. This is also a great place to add your self-hosted fonts and even CSS.  If you are using PostCSS then you might want to have your input CSS in a separate `styles` folder at the root of the project then have PostCSS output the transpiled CSS to `static/styles`. We will see further down how you can automatically append hashes to the served filenames so they cache-bust when updated.


## Anatomy of a Deno Route File
As a first step, take a look at the `routes/index.tsx` file included in the Deno Fresh skeleton content. It exports a default function which is a Preact component. If you are familiar with React or Preact then there is nothing too interesting for you here.

Even on a fairly basic site you might want to use content from a database or even Markdown files within the project. This is work which Deno Fresh lets you do on the server and sourced using a Deno **handler function**.  The handler function can sit in the same file as the rendered content (the exported React component in `routes/index.tsx`, for example).  I like this pattern of sourcing the data in the same file as the rendered content. For smaller sites it can help debug a lot faster or even help you remember how a page you wrote a few months back works.

### Deno Fresh Route Request Handler

I trimmed down a source file from a little Deno app for scheduling Tweets, it&rsquo;s a kind of poor cousin of Hootsuite or Buffer. This will help us illustrate some more real-world Deno Fresh features.  Let&rsquo;s see the data handling part first:

```typescript
import { Handlers, PageProps } from "$fresh/server.ts";
import type {
  ScheduledTweet,
} from "@/utils/twitter.ts";
import { Temporal } from "js-temporal/polyfill/?dts";
import { Fragment } from "preact";
 
interface HappyPathData {
  scheduledTweets: ScheduledTweet[];
  error?: never;
}
interface ErrorData {
  scheduledTweets?: never;
  error: string;
}
type Data = HappyPathData | ErrorData;
 
export const handler: Handlers<Data> = {
  GET(request, context) {
 
    // my own function to get list of already scheduled tweets
    const scheduledTweets = getScheduledTweets() ?? [];
 
    return context.render({
      scheduledTweets,
    });
  },
  POST(request, context) {
    const form = await request.formData();
    const action = form.get("action");
    const id = form.get("id");

    if (action === "retweet" && typeof id === "string") {
      const text = form.get("text");
      const date = form.get("date");
      const time = form.get("time");
 
      if (typeof date === "string" && typeof time === "string") {
          // my own function to schedule a tweet
          scheduleRetweet({
            id,
            scheduleTime,
            text,
          });
      } 
 
    }
    const { scheduledTweets } = getScheduledTweets() ?? [];
 
    return context.render({
      scheduledTweets,
    });
  },
};
```
The TypeScript support comes out of-the-box and we add nothing to get it to work. You can see the `handler` has a two components: a `GET` handler, while will be called when the page loads and a `PUT` handler which we will invoke by submitting a form (front-end code coming up).  Notice how `GET` and `PUT` are named after HTTP request methods &mdash; Deno loves the platform!  These functions take a `request` and `context` parameter as inputs. The `request` is a standard WebAPI Request and we can apply the `formData()`, `json()` or `text()` methods to manipulate the request body. We can also destructure `headers`, `method`, `url` etc. from it.

Meanwhile `context` contains a `params` object. For a template route (remember the offices in `london`, `mumbai`, etc. example)? There our template file name was `routes/offices/[office-name].tsx`. Well, if for example, we want to pull the phone number for the right office from the database when the visitor goes to `https://example.com/offices/mumbai` we can access the `mumbai` part using the context:

```tsx
  GET(request, context) {
        const {
      params: { 'office-name': officeName },
    } = context;
    const phoneNumber = getPhoneNumber(officeName);
    // ...TRUNCATED
```

### `context.render()`

As well as letting us access path parameters, the context object also defines a `render` method. This is what provides the server-side rendering for us. By returning it, we can pass the data into the client template.  Let&rsquo;s see that next.


### Client React Code

Remember we can keep things simple, placing this code in the same file as the handler. We didn&rsquo;t handle it above in the handler (to keep things simple), but we could have returned an error if we had some issue pulling scheduled tweets from the database.  Checking for that error in the client code, we can let the user know something is not right. Here `<Fragment></Fragment>` is syntactically equivalent to using `<></>`. If you are new to React or Preact you might not know, our rendered elements must have a single top level element (that is why wrap the other elements in `Fragment`, otherwise `HTMLHead`, `Header`, etc would all be top level elements).

```typescript
export default function TweetQueue(ctx: PageProps<Data>) {
  const {
    data: { error, scheduledTweets, tweets },
  } = ctx;

  if (error) {
    return (
      <Fragment>
        <HTMLHead
          title="Isolinear 🖥 | Twitter | Tweet Queue"
          description="Isolinear tweet queue."
        />
        <Header />
        <h1>Something went wrong!</h1>
        <p>{error}</p>
      </Fragment>
    );
  }

  const { url } = ctx;
  const { searchParams } = url;
  const retweet = searchParams.get("retweet");
  const id = searchParams.get("id");
  const minDate = Temporal.Now.plainDateTimeISO().toPlainDate().toString();

  return (
    <Fragment>
      <h1>Twitter</h1>
      <h2>Retweet</h2>
      {retweet === "true" && typeof id === "string"
        ? (
          <Fragment>
            <h3 id="retweet">Retweet</h3>
            <form action="/twitter/tweet-queue" method="post">
              <input type="text" name="id" value={id} />
              <input
                type="text"
                name="text"
                placeholder="Tweet text"
                maxLength={280}
              />
              <input type="date" name="date" min={minDate} />
              <input type="time" list="tweet-schedule" name="time" />
              <input type="hidden" name="action" value="retweet" />
              <button type="submit">[ retweet ]</button>
            </form>
          </Fragment>
        )
        : null}
    </Fragment>
  );
}
```

If we need to access the page URL, we can do so using the `ctx` prop. Finally, notice we have a form to handle submitting a new tweet. This works using the platform and with no need for additional JavaScript.  Check the MDN docs if you are new to this. Learn it once and use it here in Deno Fresh, in SvelteKit, in Remix, in Astro or even a plain old HTML/CSS website!  When the user clicks the button, the `action="/twitter/tweet-queue" method="post"` on the form element means the browser will send a `POST` HTTP request to `/twitter/tweet-queue` (this exact route). The `POST` function in the handler above will then take care of it. Are you not entertained 😅


## 🙌🏽 Getting Started with Deno Fresh: Wrapping Up

We&rsquo;re only just getting started! However so the post doesn&rsquo;t get too long, in a follow-up we will&nbsp;see:

- how **Deno Fresh Islands** work,
- Deno&rsquo;s inbuilt **testing framework**,
- creating **API Routes** and a whole lot more!

The journey so far&hellip; we have taken a quick look at getting started with Deno Fresh.  In particular, we saw:

- how **create a new Deno Fresh app**,
- some useful **Deno standard library functions**,
- how to **pass data into a client React page**.

Check out the Fresh docs for further details.  Get in touch if you would like to see more content on Deno and Fresh. I hope you found the content useful and am keen to hear about possible improvements.

## 🙏🏽 Getting Started with Deno Fresh: 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.