Building a full stack app with Deno Fresh and Fauna
Tags: javascript, deno, webdev, graphql
Published: 2022-12-16 17:43:59

# Building a full stack app with Deno Fresh and Fauna

The best way to learn about new technology is by building something practical. In this tutorial, you will build a simple blogging platform with the Fresh framework and explore various features of Fresh.

[Fresh](https://fresh.deno.dev/) is a next-generation JavaScript framework built on top of [Deno](https://deno.com/deploy). Fresh requires no configuration and comes with cool features such as just-in-time rendering, no build step, out-of-box Typescript support, and [island architecture](https://www.patterns.dev/posts/islands-architecture/) (also known as partial hydration).

You can play around with the live demo app [here](https://fresh-blogs.deno.dev/). You can also find the complete code for this tutorial in this [Github repository](https://github.com/Shadid12/dev-deno). 

## Pre-requisites

- Deno 1.x
- Familiarity with React
- Some Familiarity with TypeScript

## Creating a Fresh App

Run the following commands to scaffold a new Fresh application.

```bash
deno run -A -r https://fresh.deno.dev my-project
cd my-project
deno task start
```

Visit [localhost:8080](http://localhost:8080) and you are presented with a new Fresh app.

![Fresh](https://i.imgur.com/Fiqji8j.png)

Open the project in your favourite code editor. You will observe a folder structure similar to the following one.

![VS Code](https://i.imgur.com/PnP7kAn.png)

If you are familiar with React you will feel right at home with Fresh. Fresh uses [Preact](https://preactjs.com/), a minimal version of React. 

Fresh uses server side rendering. It compiles javascript in the server side and ships pure HTML to the client. Parts of a server-rendered page can then be independently re-hydrated with interactive widgets (islands). This architecture pattern is called island architecture. You can learn more about Fresh [architecture here](https://fresh.deno.dev/docs/concepts/architechture).

## Creating a Navbar and application layout

Create a new file `components/NavComponent.tsx` and add the following code. 

```jsx
// components/Navbar.tsx

const logoStyle = `inline-flex items-center border-b-2 border-purple-500 px-1 pt-1 text-sm font-medium text-gray-900`;

export default function Navbar() {
  return (
    <nav class="bg-white shadow">
      <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
        <div class="flex h-16 justify-between">
          <div class="flex">
            <div class="ml-6 flex space-x-8">
              <a 
                href="/"
                class={logoStyle}
              >
                Fresh Blogs 🌿
              </a>
            </div>
          </div>
        </div>
      </div>
    </nav>
  );
}
```

Next, create a new file, `routes/_app.tsx` and add the following code. 

```jsx
// routes/_app.tsx

import { AppProps } from "$fresh/server.ts";
import NavComponent from "../components/Navbar.tsx";

export default function App({ Component }: AppProps) {
  return (
    <>
      <NavComponent />
      <Component />
    </>
  );
}
```

You define your application layout in this file. Notice that you are importing the NavComponent in the `_app.tsx` file. This makes the NavComponent visible in every pages of your application. 

Next, go ahead and update the `index.tsx` file with following code.

```jsx
// routes/index.tsx

export default function Home() {
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1 class="my-6 text-3xl">
        Welcome to `🥤 Fresh Blogs 🍋`!
      </h1>
      <p class="my-6">Fresh ideas everyday</p>
    </div>
  );
}
```

![Welcome to Fresh blogs](https://i.imgur.com/3V6999x.png)

Now you have an application layout. Next, let’s go ahead and create some page routes.

## Creating new routes (New Post page)

Create a new file `routes/posts/new.tsx` and add the following code.

```jsx
// routes/posts/new.tsx 

import { PageProps } from "$fresh/server.ts";
import { Fragment } from "preact";

export default function NewPostPage(props: PageProps) {
  return (
    <Fragment>
      <div class="p-4 mx-auto max-w-screen-md">
        New Post Page
      </div>
    </Fragment>
  )
}
```

In the code above you are exporting a new react component. 

Fresh automatically generates a new route for your application when you create a new react component inside the `routes` directory. Observe the `fresh.gen.ts` file and you will notice that your newly created routes are added to this file automatically. 

Visit the `/posts/new` route and notice that the component above gets rendered.

You can also create a handler function in your routes file. The handler function will be executed on the server side whenever someone visits the route. Here's an example.

```jsx
// routes/posts/new.tsx 
import { PageProps } from "$fresh/server.ts";
import { Fragment } from "preact";
import { HandlerContext, Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  GET(req: Request, ctx: HandlerContext) {
    console.log('Inside GET handler');
    return ctx.render({
      message: "Hello New Post 📝",
    });
  },
};

export default function NewPostPage(props: PageProps) {
  return (
    <Fragment>
      <div class="p-4 mx-auto max-w-screen-md">
        {props.data.message}
      </div>
    </Fragment>
  )
}
```

You can also pass data from your handler function to your component using the context `render` function as shown in the previous code block. 

Next, let’s create a form to create new posts. A form has client side interactivity. In fresh you use islands to handle client side interactivity. 

Islands are isolated Preact components that are rendered on the client. Islands are defined by creating a file in the `islands/` folder in a Fresh project.  Islands are re-hydrated as needed which gives Fresh the extra performance boost unlike other SSR solutions for React. 

Create a new file `islands/PostForm.tsx` and add the following code. 

```jsx
// islands/PostForm.tsx

import { useState } from "preact/hooks";
export const inputStyle = `p-4 border-2 border-purple-400 radius rounded-md flex w-9/12`;

export default function PostForm() {
  
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");

  const createPost = (e: Event) => {
    e.preventDefault();
  }

  return (
    <div class="p-5 m-5">
      <h1 class="text-xl">Post Form</h1>
      <div class="mt-1 flex">
        <input 
          class={`${inputStyle}`} 
          placeholder="Post Title"
          onChange={(e: any) => setTitle(e.target.value)}
          value={title}
        />
      </div>
      <div class="mt-1 flex">
        <textarea 
          class={`${inputStyle}`} 
          placeholder="Write something..."
          onChange={(e: any) => setContent(e.target.value)}
          value={content}
        />
      </div>
      <button 
            onClick={createPost}
            class="rounded-md mt-3 border-transparent bg-purple-200 px-4 py-2"
          >
            Submit
          </button>
    </div>
  );
}
```

Import the island component in the `routes/posts/new.tsx` file.

```jsx
// ...routes/posts/new.tsx

import PostForm from "../../islands/PostForm.tsx";

//...

export default function NewPostPage(props: PageProps) {
  return (
    <Fragment>
      <div class="p-4 mx-auto max-w-screen-md">
        <PostForm />
      </div>
    </Fragment>
  )
}
```

### Dynamic routes (View Post page and Edit Post page)

Fresh supports dynamic routes similar to Next.js. Folder structure determines the routing mechanism. Create a new file `routes/posts/[id]/index.tsx` and add the following code. 

```jsx
// routes/posts/[id]/index.tsx
import { useState } from "preact/hooks";
import { Handlers, PageProps } from "$fresh/server.ts";

export const handler: Handlers = {
  GET(_req, ctx) {
    const { id } = ctx.params;
    return ctx.render({
      content: `Hello Post ${id} 📝`,
    });
  }
};

export default function PostPage(props: PageProps) {
  const [post, setPost] = useState<any>();
  setPost(props.data);

  if(!post) { 
    return <div>Loading...</div>
  }
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1>{post.content}</h1>
    </div>
  )
}
```

Notice that the directory has a folder with `[<name>]` syntax. Based on the folder structure the application sets up a  `/posts/:id` route. Visit  [http://localhost:8000/posts/1](http://localhost:8000/posts/1) to make sure the age is loading as expected. 

![Hello Worlds Program](https://i.imgur.com/QQKV55R.png)

Let’s create an edit post page next. Create a new file `routes/posts/[id]/edit.tsx` and add the following code. 

```jsx
// routes/posts/[id]/edit.tsx

import { Handlers, PageProps } from "$fresh/server.ts";
import PostForm from "../../../islands/PostForm.tsx";
import { Fragment } from "preact";

export const handler: Handlers = {
  GET(_req, ctx) {
    const { id } = ctx.params;
    return ctx.render({
      data: { id },
    });
  },
};

export default function EditPostPage(props: PageProps) {
  return (
    <Fragment>
      <div class="p-4 mx-auto max-w-screen-md">
        <PostForm />
      </div>
    </Fragment>
  )
}
```

You now have all the pages ready. Next, let’s go ahead and add a database to the project so you can save and retrieve posts.

## Adding Fauna database to the project

Head over to [fauna.com](http://fauna.com) and create a new account if you haven’t done already. Next create a new database.

Navigate to *Security > Keys* in your fauna dashboard. Select  *New Key* to generate a new key. 

![Generate Security Key](https://i.imgur.com/wxN2SQQ.png)

Select Admin as role. Give your key a name and select Save. 

![Generating keys](https://i.imgur.com/cZsyj0G.png)

You will recieve a key 🔑  like following. Do not share this key. 

```bash
fnAEx.....
```

### Configuring the Database

For this demo application you need 3 Collections in the database. `User`, `Post` and `Comment`. Additionally you need to define relationship between these collections. There is a `has_many` relationship between `User` to `Post` and `Post` to `Comment`. Fauna also provides out-of-box user registration and secure authentication. 

You can create all these collections from Fauna dashboard in the UI or you can run this [setup script](https://github.com/Shadid12/dev-deno/blob/main/utils/fauna-setup.ts) to generate all the resources for you. Copy the script into `utils/fauna-setup.ts` file.

To run the script first export your fauna admin secret and domain in the shell. 

```bash
$ export FAUNA_ADMIN_SECRET=<Your-admin-secret> \
  export FAUNA_DOMAIN=<Your-db-domain>
```

Next run the script.

```bash
$ deno run ./utils/fauna-setup.ts
```

**What does the script generates?**

When the script execution is complete all the collections and indexes will be created in your Fauna database. The script sets up several Fauna resources. It sets up three collections `User` `Post` and `Comment`. It also creates an index to search user by email. 

The script will also create a server secret for your application. Your application will use this secret to communicate with the database. Export the server secret by running following command in your terminal. 

```bash
export FAUNA_SECRET=fnAExE....
```

You can also create a `.env` file to store these environment variables variable. Following is an example of a `.env` file. **Always make sure not to commit this file with your source control.**

```bash
FAUNA_DOMAIN=db.us.fauna.com
FAUNA_SECRET=fnAEuxxxx.....
FAUNA_ADMIN_SECRET=fnADxxxxx..... 
```

Finally, you can visit the Fauna dashboard and explore the generated resources in the UI.

![Collection](https://i.imgur.com/kn5deis.png)

![Indexes](https://i.imgur.com/qvYn2s6.png)

Create a new file `utils/db.ts` and add the following code. Here you are instantiating a new database client. You use these helper functions to query the database. 

```jsx
// utils/db.ts
import * as faunadb from "https://deno.land/x/fauna@5.0.0-deno-alpha9/mod.js";

export const q = faunadb.query as any;

export const faunaClient = new faunadb.Client({ 
  domain: Deno.env.get("FAUNA_DOMAIN"),
  secret: Deno.env.get("FAUNA_SECRET"),
});

export const getFaunaClient = (secret: string) => new faunadb.Client({
  domain: Deno.env.get("FAUNA_DOMAIN"),
  secret,
});
```

## Setting up user registration

Now that you have the database configured let's go ahead and implement user registration and user login functionality.
First, create a new route for user login. Create a new file, `routes/signup.tsx` and add the following code.

```jsx
// routes/signup.tsx
import SignupForm from "../islands/SignupForm.tsx";

export default function UserSignup() {
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1 class="my-6 text-3xl">
        🚀 Let's get you signed up!🚀 
      </h1>
      <SignupForm />
    </div>
  );
}
```

Notice you have a `SignupForm` island imported in this page. Go ahead and create a new island `islands/SignupForm.tsx` and add the following code.

```jsx
// islands/SignupForm.tsx

import { useState } from "preact/hooks";
import { inputStyle } from "./PostForm.tsx";

export default function SignupForm() { 
  const [state, setState] = useState({});

  const handleChange = (e: any) => {
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  }

  const register = async () => { 
  }

  return (
    <div>
      <div class="pl-4 pt-4 mt-4">
        <input 
          onChange={handleChange} 
          type="text" 
          class={`${inputStyle}`} 
          placeholder="Name" 
          name="username"
        />
      </div>
      <div class="pl-4 pt-4 mt-1">
        <input 
          onChange={handleChange} 
          type="email" 
          class={`${inputStyle}`} 
          placeholder="Email" 
          name="email"
        />
      </div>
      <div class="pl-4 pt-2 mt-1">
        <input 
          onChange={handleChange} 
          type="password" 
          class={`${inputStyle}`} 
          placeholder="Password" 
          name="password"
        />
      </div>
      <div class="pl-4 pt-2 mt-1">
        <button 
          onClick={register} 
          class="rounded-md mt-3 border-transparent bg-purple-200 px-4 py-2"
        >
          Register
        </button>
      </div>
    </div>
  )
}
```

On the Register button click, you want to make a call to Fauna to register your users. You can make a direct HTTP call from your `SignupForm` island component. However, you will expose the Fauna's secret key by doing this. It is a good practice to make database interactions and third-party API calls from the server side. This way, your API secrets are never exposed on the client side.

You can create a new API route in Fresh, a server-side REST endpoint, then have your island component make a call to this API endpoint and handle all database interactions in the endpoint.

![Diagram](https://i.imgur.com/n51h2vr.png)

Fresh API routes are similar to Next.js API routes.

Create a new file `routes/api/register.ts` and add the following code.

```jsx
import { Handlers } from "$fresh/server.ts";
import { faunaClient, q } from "../../utils/db.ts";

export const handler: Handlers = { 
  async POST(req: Request) {
    try {
      const body = await req.json();

      const newUser = await faunaClient.query(
        q.Create(
          q.Collection("User"),
          {
            credentials: { password: body.password },
            data: {
              email: body.email,
              username: body.username,
            },
          }
        )
      );

      return Response.json({
        data: {
          ...newUser.data,
        }
      });
    } catch (error) {
      return Response.json({
        error: error.message,
      });
    }
  }
};
```

Here you are creating a new User record in Fauna. You can learn more about Fauna CRUD operations in the [official docs here](https://docs.fauna.com/fauna/current/learn/tutorials/fql/crud?lang=javascript). 

Next, plug this api endpoint to your `SignupForm` register function as shown in the following code snippet. 

```jsx

// islands/SignupForm.tsx
export default function SignupForm() { 
  // ...
  const register = async () => { 
    try {
      const response = await fetch("/api/register", {
        method: "POST",
        body: JSON.stringify({
          ...state,
        }),
      });
      const data = await response.json();
      console.log(data);
      alert("Successfully registered!, try to log 🪵 in now");

    } catch (error) {
      alert("Something went wrong!");
    }
  }

  return (
    <div>
      ...
    </div>
  )
}
```

Visit [http://localhost:8000/signup](http://localhost:8000/signup) and try to register a new user. Once a new user is registered it should appear under User collection in Fauna.

![Lets get signed up image](https://i.imgur.com/TJxrvri.png)

![Collection Populated](https://i.imgur.com/LHrzCgS.png)

Notice that your password is not saved as a string. This is because you defined password field as `credentials` type. This is a part of Fauna’s secure built in authentication feature.

## User Login

You can follow the same pattern to setup user login functionality. Create a new file `routes/login.tsx` and add the following code. 

```jsx
// routes/login.tsx

import LoginForm from "../islands/LoginForm.tsx";
export default function UserLogin() {
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1 class="my-6 text-3xl">
        Login to your account and start blogging! 📝
      </h1>
      <LoginForm />
    </div>
  );
}
```

Create a `LoginForm` island component to handle the form. 

Next, create a new api routes to handle Login. 

```jsx
// routes/api/login.ts

import { Handlers } from "$fresh/server.ts";
import { faunaClient, q } from "../../utils/db.ts";

export const handler: Handlers = { 
  async POST(req: Request) {
    try {
      const body = await req.json();

      const authUser: any = await faunaClient.query(
        q.Login(
          q.Match(q.FaunaIndex("users_by_email"), body.email),
          { password: body.password  },
        )
      );

      return Response.json({
        data: {
          token: authUser.secret,
        }
      });
    } catch (error) {
      console.log('==>>>', error);
      return Response.json({
        error: error.message,
      });
    }
  }
};
```

And finally create a `LoginForm` island to handle interactivity.

```jsx
// islands/LoginForm.tsx

import { useState } from "preact/hooks";
import { inputStyle } from "./PostForm.tsx";

export default function LoginForm() { 
  const [state, setState] = useState({});

  const handleChange = (e: any) => {
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  }

  const doLogin = async () => {
    try {
      const response = await fetch("/api/login", {
        method: "POST",
        body: JSON.stringify({...state}),
      })
      const data = await response.json();
      if(data.error) {
        alert(`${data.error} : Make sure you have a correct email and password`);
      } else {
        localStorage.setItem("token", data.data.token);
        alert("Successfully logged in!");
      }
    } catch (error) { 
      console.log(error);
      alert("Something went wrong!");
    }
  }

  return (
    <div>
      <div class="pl-4 pt-4 mt-4">
        <input 
          onChange={handleChange} 
          type="email" 
          class={`${inputStyle}`} 
          placeholder="Email" 
          name="email"
        />
      </div>
      <div class="pl-4 pt-2 mt-1">
        <input 
          onChange={handleChange} 
          type="password" 
          class={`${inputStyle}`} 
          placeholder="Password" 
          name="password"
        />
      </div>
      <div class="pl-4 pt-2 mt-1">
        <button 
          onClick={doLogin} 
          class="rounded-md mt-3 border-transparent bg-purple-200 px-4 py-2"
        >
          Signin
        </button>
      </div>
    </div>
  )
}
```

You use the Login function from Fauna to generate a temporary user secret (it is invalidated after some time). You save this secret in your local storage just like a `jwt` token. You need to pass this secret with your API call to Fauna when making protected requests (i.e. create or delete posts).

Head over to [http://localhost:8000/login](http://localhost:8000/login) and try logging in with a registered user.

Notice on successful login the Fauna secret is saved in the local storage.

![Notice Token](https://i.imgur.com/fvn5G7N.png)

ℹ️ You can learn more about Fauna’s identity based authentication [here](https://docs.fauna.com/fauna/current/security/best_practices/abac-identity-based?lang=javascript).

## Adding CRUD for Posts

Following a similar pattern, you can add CRUD operations for posts. Create a new API route to handle post CRUD operations. Create a new file `routes/api/post.ts` and add the following code. 

```jsx
// routes/api/post.ts

import { Handlers } from "$fresh/server.ts";
import { getFaunaClient, q } from "../../utils/db.ts";

export const handler: Handlers = {
  /**
   * Create a new post
   */
  async POST(req: Request) {
    try {
      const body = await req.json();
      const faunaClientWithAuth = getFaunaClient(req.headers.get("Authorization")!);
      const newpost = await faunaClientWithAuth.query(
        q.Create(
          q.Collection('Post'),
          { 
            data: { 
              ...body, 
              owner: q.CurrentIdentity(), 
              author: q.Select(["data", "username"], q.Get(q.CurrentIdentity()))
            } 
          },
        )
      );

      return Response.json({
        data: {
          _id: newpost.ref.id,
          ...newpost.data,
        }
      });
    } catch (error) {
      return Response.json({
        error: error.message,
      });
    }
  },
};
```

In the previous code block, you use the `getFaunaClient` function and pass in the `Authorization` header. The Authorization header is a Fauna secret that you save in your local storage on successful login. 

On `PostForm` island component plug in the api call when submit button is clicked. 

```jsx
// islands/PostForm.tsx
...
export default function PostForm() {
  
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");

  const createPost = async (e: Event) => {
    e.preventDefault();
    const response = await fetch("/api/post", {
      headers: {
        "Authorization": localStorage.getItem("token") || "",
      },
      method: "POST",
      body: JSON.stringify({ title, content, author: localStorage.getItem("username") }),
    });
    const data = await response.json();
    console.log(data);
    if(data.error) {
      alert('⛔ Something went wrong! Are you logged in?');
      return;
    }
    alert('Post created!');
    setTitle("");
    setContent("");
  }

  return (
    <div class="p-5 m-5">
      ...
    </div>
  );
}
```

Navigate to [http://localhost:8000/posts/new](http://localhost:8000/posts/new) and try creating a new post. Make sure you are already logged in. 

### Showing a list of posts

In the home page a user should be able to view all the posts. In your `routes/api/post.ts` file add a new GET function to retrieve all the posts.

```jsx
import { Handlers } from "$fresh/server.ts";
import { getFaunaClient, q, faunaClient } from "../../utils/db.ts";

export const handler: Handlers = {
  /**
   * Create a new post
   */
  async POST(req: Request) {
    //...
  },

   /**
   * Get all posts
   */
  async GET() {
    try {
      const posts: any[] = [];
      const { data } = await faunaClient.query(
        q.Map(
          q.Paginate(q.Documents(q.Collection('Post'))),
          q.Lambda("post", q.Get(q.Var("post")))
        )
      );
      data.forEach((post: any) => {
        posts.push({
          _id: post.ref.id,
          ...post.data,
        });
      })
      return Response.json({
        data: posts,
      });
    } catch (error) {
      return Response.json({
        error: error.message,
      });
    }
  },
};
```

Create a new island component, `islands/PostList.tsx` to display all the posts.

```jsx
// islands/PostList.tsx
import { useState, useEffect } from "preact/hooks";

export default function PostList() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    const fetchPosts = async () => {
      try {
        const response = await fetch("/api/post");
        const remotePosts = await response.json();
        setPosts(remotePosts.data);
      } catch (error) {
        console.error(error);
        alert("Something went wrong!");
      }
    }
    fetchPosts();
  },[]);

  console.log(posts);

  return (
    <div class="p-5">
      <h1 class="text-xl">Post List</h1>
      {
        posts.map((post: any) => (
          <PostItem post={post} id={post._id} />
        ))  
      }
    </div>
  )
}

function PostItem({ post, id } : { post: any, id: string }) {
  return (
    <div class="p-5">
      <h4 class="text-md mb-3">{post.title}</h4>
      <a class="border mt-1 p-2 cursor:pointer" href={`/posts/${id}`}>View</a>
    </div>
  )
}
```

Add this island component into your `index.tsx` page.

```jsx
// routes/index.tsx
import PostList from "../islands/PostList.tsx";

export default function Home() {
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1 class="my-6 text-3xl">
        Welcome to `🥤 Fresh Blogs 🍋`!
      </h1>
      <p class="my-6">Fresh ideas everyday</p>
      <PostList />
    </div>
  );
}
```

All the posts will now appear in your homepage.

![Welcome to fresh blog](https://i.imgur.com/3V6999x.png)

### Viewing a single post

Notice that when you select the *View* button for any post from post list it takes you to the `/post/:id` route. In this page you can retrieve the post information and display it. 

In this case, you can use the handler function from fresh to retrieve the data from the database. Since the handler function runs on the server side, you are not exposing any secure information. Make the following changes to your `routes/posts/[id]/index.tsx` file.

```jsx
import { useState } from "preact/hooks";
import { Handlers, PageProps } from "$fresh/server.ts";
import { faunaClient, q } from "../../../utils/db.ts";

export const handler: Handlers = {
  async GET(_req, ctx) {
    const { id } = ctx.params;
    try {
      const post = await faunaClient.query(
        q.Get(q.Ref(q.Collection('Post'), id))
      );
      return ctx.render({
        _id: post.ref.id,
        ...post.data,
      });
    } catch (error) {
      return Response.json({
        error: error.message,
      });
    }
  },
};

export default function PostPage(props: PageProps) {
  const [post, setPost] = useState<any>();
  setPost(props.data);

  if(!post) { 
    return <div>Loading...</div>
  }
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1 class="text-xl pl-4">{post.title}</h1>
      <div class="text-sm font-bold pl-4">By {post.author}</div>
      <p class="pl-4 text-left">{post.content}</p>
    </div>
  )
}
```

## Deleting a post

Add a `DELETE` function to your `routes/api/post.ts` api route to handle post delete.

```jsx
// routes/api/post.ts
import { Handlers } from "$fresh/server.ts";
import { getFaunaClient, q, faunaClient } from "../../utils/db.ts";

export const handler: Handlers = {
  /**
   * Create a new post
   */
  async POST(req: Request) {
    ...
  },

   /**
   * Get all posts
   */
  async GET() {
    ...
  },
  /**
   * Delete a post
   */
  async DELETE(req: Request) {
    try {
      const body = await req.json();
      const faunaClientWithAuth = getFaunaClient(req.headers.get("Authorization")!);
      const post = await faunaClientWithAuth.query(
        q.Delete(q.Ref(q.Collection('Post'), body._id))
      );
      return Response.json({
        data: post.data,
      });
      
    } catch (error) {
      return Response.json({
        error: error.message,
      });
    }
  },
};
```

Next, make the `Delete` call from your page component to delete the post. 

```jsx
// routes/posts/[id]/index.tsx
// ...
export default function PostPage(props: PageProps) {
  const [post, setPost] = useState<any>();
  setPost(props.data);

  const deletePost = async () => {
    const response = await fetch(`/api/post/`, {
      headers: {
        "Authorization": `${localStorage.getItem("token")}`,
      },
      method: "DELETE",
      body: JSON.stringify({
        _id: post._id
      })
    });
    const data = await response.json();
    if(data.error) {
      alert(data.error);
    } else {
      alert("Post deleted!");
      window.location.href = "/";
    }
  }

  if(!post) { 
    return <div>Loading...</div>
  }
  return (
    <div class="p-4 mx-auto max-w-screen-md">
      <h1 class="text-xl pl-4">{post.title}</h1>
      <div class="text-sm font-bold pl-4">By {post.author}</div>
      <p class="pl-4 text-left">{post.content}</p>
      <button class="border mt-1 p-2 cursor:pointer" onClick={deletePost}>Delete</button>
    </div>
  )
} 
```

You can also apply the same pattern and have CRUD for comments. You can follow the [completed code](https://github.com/fauna-labs/fauna-deno-fresh) for this project here to implement comments CRUD functionality.

## Final thoughts

Fresh really takes a fresh new approach to web  dev. It is still quite new but the ecosystem is rapidly growing. Up until now the Deno ecosystem was a missing a full stack framework and Fresh seems to fill that void quite effectively. You can create scalable, performant applications with Fresh and Fauna quite easily. On top of that you can make fully serverless full stack applications on the edge when you deploy your Fresh app to [Deno deploy](https://deno.com/deploy), [denoflare.dev](https://denoflare.dev/) or [Netlify](https://www.netlify.com/).