by Eze Nnaemeka
For JavaScript and TypeScript developers, Fresh is a Deno-based web framework that facilitates the construction of high-quality, efficient, and customized web apps. You can use it to make whatever you think of, including your home page, a blog, or a sizable web application. In essence, Fresh combines a routing architecture and a templating engine to produce pages on the server as needed. For maximum interactivity, Fresh offers an interface for seamlessly rendering some components on the client in addition to this just-in-time rendering on the server. For rendering and templating on both the server and the client, the framework employs Preact (a lightweight React alternative) as a templating engine and JSX.
Advantages of Fresh
Hard-to-miss features are packed with Fresh and readily usable for creating web applications right out of the box. Here are a few of these characteristics:
- No build step: You directly write the code that is executed on the client and server. Any necessary translation from TypeScript or JSX to regular JavaScript is performed instantly, as needed. This enables deployments to happen incredibly quickly and iteration loops to happen incredibly quickly.
- Zero runtime overhead: By default, Fresh ships no JavaScript to the browser. Just a static HTML file.
- No configuration is required: You don't need to configure anything to begin developing your application with Fresh. Just use their CLI and get started.
- Fast and tiny (the framework requires no client JS): Client-side rendering is quite expensive because it ships hundreds of kilobytes of client-side JavaScript to users on each request, slowing down the user experience and increasing power consumption on mobile devices. Fresh embraces the true design of server-side rendering and progressive enhancement on the client side. It uses a different model where 0KB of JavaScript is shipped to the client-side by default. Most rendering is done on the server side, and the client-side is only responsible for re-rendering small islands of interactivity.
- TypeScript out of the box: Unlike Node.js, you don't have to configure TypeScript separately in Fresh.
- Routing files using Next Js: Similar to Next Js (A hybrid static & server rendering Javascript framework ), Fresh renders everything to static HTML on the server instead of sending JavaScript code to the browser.
- Island-based client hydration: The island-based client hydration model works with small portions of your application that need JavaScript to be interactive. For example, on the docs, they have this counter at the bottom, which was hydrated to provide interactivity. Only the JavaScript required to render the counter is sent to the client.
The connection between Deno and Fresh
The original architect of Node Js created Deno, a Javascript runtime, to respond to many of the issues with the original Node. However, the eco-space is currently in a very early stage of development. Deno is a secure typescript runtime built on Google's V8, which serves as the JavaScript runtime engine. The capabilities of Node Js will be enhanced by features in Deno. Fresh projects can be distributed to any platform, but for the optimal user experience, it is intended to be pushed to an edge runtime as Deno deploy.
Deno deploy supports
- ES modules compatible with the web: import dependencies just like in a browser, no need for installation.
- Direct GitHub integration: push to a branch, examine a deployed preview, and merge to release to production.
- Builds on the Web: use fetch, WebSocket, or URL just like in the browser.
Creating a new project with Fresh
It's quite simple to get started using Fresh. Fresh projects can be created by using the Fresh project creation tool. It will scaffold a new project with some example files to get you started. The following commands allow you to create a Fresh project.
deno run -A -r https://fresh.deno.dev intro-fresh
cd intro-fresh
deno task start
If we visit localhost:8000 after running the command to start the program, you should now be able to see our application operating on the screen similar to the one below.
Open Source Session Replay
OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.
Start enjoying your debugging experience - start using OpenReplay for free.
Fresh folder structure
The following files and folder directories make up the folder structure for a Fresh app; we'll examine each one to learn what it contains.
- fresh.gen.ts: This file, called a manifest, provides details on your islands and routes. The routes/ and islands/ directories are used to automatically generate this file during development.
- main.ts: This is where production will begin for your project. You link it to Deno Deploy; thus, that's the file. Although this is the norm, this file does not have to be named main.ts.
- import_map.json: Managing dependencies for the project is done via an import map. Consequently, dependencies can be easily imported and updated.
- deno.json: A file is also created in the project directory. This file accomplishes two tasks: it provides Deno with information about the location of the import map so that it can be imported automatically, and by registering a "start" task, the project can be executed without having to type a lengthy Deno run command.
- dev.ts: This is where your project's development process begins. To begin your project, you must run this file. Although it is not required, this file should be named dev.ts.
- static/: This directory contains files with a greater priority than routes; these items are served at the webserver's root.
Additionally, two essential folders are made, one for each of your routes and islands:
- islands/: Every interactive island (component) from your project is housed in this folder. The name of the island defined in each file is reflected in the file's name. This directory code is client- and server-compatible.
- routes/: Every route in your project is contained in this directory. Each file in this directory is named according to the path that will be used to access that page.
Creating routes
The routes directory contains files that define routes. The module's file name is crucial since it identifies the path the route will follow. For instance, the route will handle requests to / if the file name is index.js. The route will handle requests to /contact if the file name is contact.js. The route will handle calls to /about/contact
if the file with the name contact.js
is located in the routes/about/
directory.
An example of such a structure is the following. The file tree will look exactly like the structure below.
routes/
--|contact.tsx
In the routes/ directory, we create a file called contact.tsx.
// routes/contact.tsx
/* @jsx h */
import { h } from "preact";
const Contact () => {
return (
<section>
<h1>My contact</h1>
<p>This is the contact page.</p>
</section>
);
}
export default Contact
Dynamic routing
You might be wondering how this would function when dealing with dynamic routes. To specify a dynamic route with a parameter, enclose that section in square brackets in the file name. For example /post/:title routes maps to file name routes/post/[title].tsx .
Additionally, we include this inside the post/[title].tsx file.
// routes/post/[title].tsx
/* @jsx h */
import { h } from "preact";
import { PageProps } from "$fresh/server.ts";
const TitleDetail (props: PageProps) => {
const { title } = props.params;
return (
<section>
<p>The title of the post is, {title}</p> // The title of the post is, fresh
</section>
);
}
The PageProps interface includes several helpful properties that can be utilized to alter the generated result. The raw URL and the route name can also be found here, in addition to the arguments that fit the URL pattern.
Now, when you go to localhost:8000/post/fresh, a page saying "The title of the post is, Fresh "will appear.
Fetching data from an API with Fresh
We need to illustrate how to use Fresh to build an app. The blog app will be able to fetch blogs from a JSON server.
First steps
We'd have to scaffold a Fresh application by running the commands;
deno run -A -r https://fresh.deno.dev blog-app
cd blog-app
deno task start
Let's create a file inside the islands/ directory and name it BlogCard.tsx.
// islands/BlogCard.tsx
/* @jsx h */
import { h } from "preact";
import { tw } from "@twind";
interface BlogCardProps {
id: string;
title: string;
body: string;
}
const BlogCard = ({ title, body, id }: BlogCardProps) => {
return (
<div
class={tw`sm:w-3/12 shrink-0 basis-1/2 md:w-{2.5/12} mx-5 my-5 p-5 border-2 border-pink-600 `}
>
{""}
<h1 class={tw`text-2xl my-2`}>{title}</h1>
<p>{body.substring(0, 150}</p>
</div>
);
};
export default BlogCard;
This BlogCard component is a simple preact component that accepts props and creates a card for each blog post. Let's also navigate to the routes/ directory and target the index.tsx file, and there we'll create a simple fetch request.
Asynchronous handler functions in Fresh are used to retrieve server-side data. With the data to be rendered as an input, these handler functions can use the ctx.render() function. The Home component can be able to then access this data using the data property on the props.
Here's an example.
/* @jsx h */
import { h } from "preact";
import { tw } from "@twind";
import BlogCard from "../islands/BlogCard.tsx";
import { Handlers, PageProps } from "$fresh/server.ts";
interface BlogType {
id: string;
title: string;
body: string;
}
const Handler: Handlers<BlogType> = {
async GET(_req, ctx) {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!response) {
return new Response("Blog not found", { status: 404 });
}
const blog = await response.json();
return ctx.render(blog);
},
};
const Home = ({ data }: PageProps<BlogType[]>) => {
return (
<div class={tw`w-full flex border-4 flex-wrap justify-center `}>
<div class={tw`flex flex-wrap justify-center `}>
{data.map((item) => (
<BlogCard title={item.title} body={item.body} id={item.id} />
))}
</div>
</div>
);
}
export default Home
The type parameter on the PageProps
, Handlers
, Handler
, and HandlerContext
can be used to enforce a TypeScript type for the render data. The types in each field must coexist on a single page for Fresh to enforce type compatibility during type checking.
The Home component maps through the data and creates a new BlogCard, which will be rendered.
Now, this is what our simple blog application looks like.
Deploying a Fresh application
We'll use the GitHub integration to deploy to Deno Deploy, and this requires that the code be uploaded to a GitHub repository to be used. After completing this, we visit the Deno Deploy dashboard and start a new project.
First, let's head to Deno Deploy and create a new project.
Then, choose the specific repository you want to deploy.
Finally, select the primary production branch and entry point file (main.ts). By doing so, the project will be instantly deployed on the internet and linked to the repository. At this time, the project can be accessed at https://$PROJECT NAME.deno.dev.
After deployment, a link to our application will be generated.
N/B Every time the code in the GitHub repository is updated, it will either be deployed as a preview or production deployment. Only modifications to the default/production branch require the creation of production deployments (often main).
And that's how we deploy our Fresh application!
Conclusion
You've mastered the art of creating Fresh applications by following this guide. Beginning with a brief overview of Fresh, we built a straightforward blog application that retrieves data for the example. The functionality of this program can always be expanded, and you can always learn more about Fresh.
A TIP FROM THE EDITOR: You can learn more about Deno, its design, its current state, and how to deploy apps, in our podcast All about Deno and Deno Deploy with Bartek Iwanczuk.