Skip to content

Commit

Permalink
Qwik tutorial (#1260)
Browse files Browse the repository at this point in the history
Co-authored-by: Jo Franchetti <jofranchetti@gmail.com>
  • Loading branch information
lambtron and thisisjofrank authored Dec 13, 2024
1 parent 475e420 commit f435355
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 0 deletions.
5 changes: 5 additions & 0 deletions examples/_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ export const sidebar = [
id: "/examples/astro_tutorial/",
type: "tutorial",
},
{
label: "Build a Qwik app with Deno",
id: "/examples/qwik_tutorial/",
type: "tutorial",
},
{
label: "Build a Typesafe API with tRPC and Deno",
id: "/examples/trpc_tutorial/",
Expand Down
Binary file added examples/tutorials/images/how-to/qwik/demo.mp4
Binary file not shown.
315 changes: 315 additions & 0 deletions examples/tutorials/qwik.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
---
title: "Build Qwik with Deno"
url: /examples/qwik_tutorial/
---

[Qwik](https://qwik.dev/) is a JavaScript framework that delivers
instant-loading web applications by leveraging resumability instead of
hydration. In this tutorial, we'll build a simple Qwik application and run it
with Deno. The app will display a list of dinosaurs. When you click on one,
it'll take you to a dinosaur page with more details.

We'll go over how to build a simple Qwik app using Deno:

- [Scaffold a Qwik app](#scaffold-a-qwik-app)
- [Setup data and type definitions](#setup-data-and-type-definitions)
- [Build the frontend](#build-the-frontend)
- [Next steps](#next-steps)

Feel free to skip directly to
[the source code](https://github.com/denoland/examples/tree/main/with-qwik) or
follow along below!

## Scaffold a Qwik app

We can create a new Qwik project using deno like this:

```bash
deno init --npm qwik@latest
```

This will run you through the setup process for Qwik and Qwik City. Here, we
chose the simplest “Empty App” deployment with npm dependencies.

When complete, you’ll have a project structure that looks like this:

```
.
├── node_modules/
├── public/
└── src/
├── components/
│ └── router-head/
│ └── router-head.tsx
└── routes/
├── index.tsx
├── layout.tsx
├── service-worker.ts
├── entry.dev.tsx
├── entry.preview.tsx
├── entry.ssr.tsx
├── global.css
└── root.tsx
├── .eslintignore
├── .eslintrc.cjs
├── .gitignore
├── .prettierignore
├── package-lock.json
├── package.json
├── qwik.env.d.ts
├── README.md
├── tsconfig.json
└── vite.config.ts
```

Most of this is boilerplate configuration that we won’t touch. A few of the
important files to know for how Qwik works are:

- `src/components/router-head/router-head.tsx`: Manages the HTML head elements
(like title, meta tags, etc.) across different routes in your Qwik
application.
- `src/routes/index.tsx`: The main entry point and home page of your application
that users see when they visit the root URL.
- `src/routes/layout.tsx`: Defines the common layout structure that wraps around
pages, allowing you to maintain consistent UI elements like headers and
footers.
- `src/routes/service-worker.ts`: Handles Progressive Web App (PWA)
functionality, offline caching, and background tasks for your application.
- `src/routes/entry.ssr.tsx`: Controls how your application is server-side
rendered, managing the initial HTML generation and hydration process.
- `src/routes/root.tsx`: The root component that serves as the application's
shell, containing global providers and the main routing structure.

Now we can build out our own routes and files within the application.

## Setup data and type definitions

We’ll start by adding our
[dinosaur data](https://github.com/denoland/examples/blob/main/with-qwik/src/data/dinosaurs.json)
to a new `./src/data` directory as `dinosaurs.json`:

```jsonc
// ./src/data/dinosaurs.json

{
"dinosaurs": [
{
"name": "Tyrannosaurus Rex",
"description": "A massive carnivorous dinosaur with powerful jaws and tiny arms."
},
{
"name": "Brachiosaurus",
"description": "A huge herbivorous dinosaur with a very long neck."
},
{
"name": "Velociraptor",
"description": "A small but fierce predator that hunted in packs."
}
// ...
]
}
```

This is where our data will be pulled from. In a full application, this data
would come from a database.

> ⚠️️ In this tutorial we hard code the data. But you can connect
> to [a variety of databases](https://docs.deno.com/runtime/tutorials/connecting_to_databases/) and [even use ORMs like Prisma](https://docs.deno.com/runtime/tutorials/how_to_with_npm/prisma/) with
> Deno.
Next, let's add type definitions for our dinosaur data. We'll put it in
`types.ts` in `./src/`:

```tsx
// ./src/types.ts

export type Dino = {
name: string;
description: string;
};
```

Next, let's add API routes to server this data.

## Add API routes

First, let's create the route to load all dinosaurs for the index page. This API
endpoint uses Qwik City's
[`RequestHandler`](https://qwik.dev/docs/advanced/request-handling/) to create a
`GET` endpoint that loads and returns our dinosaur data using the json helper
for proper response formatting. We'll add the below to a new file in
`./src/routes/api/dinosaurs/index.ts`:

```tsx
// ./src/routes/api/dinosaurs/index.ts

import { RequestHandler } from "@builder.io/qwik-city";
import data from "~/data/dinosaurs.json" with { type: "json" };

export const onGet: RequestHandler = async ({ json }) => {
const dinosaurs = data;
json(200, dinosaurs);
};
```

Next, let's create the API route to get the information for a single dinosaur.
This takes the parameter from the URL and uses it to search through our dinosaur
data. We'll add the below code to `./src/routes/api/dinosaurs/[name]/index.ts`:

```tsx
// ./src/routes/api/dinosaurs/[name]/index.ts

import { RequestHandler } from "@builder.io/qwik-city";
import data from "~/data/dinosaurs.json" with { type: "json" };

export const onGet: RequestHandler = async ({ params, json }) => {
const { name } = params;
const dinosaurs = data;

if (!name) {
json(400, { error: "No dinosaur name provided." });
return;
}

const dinosaur = dinosaurs.find(
(dino) => dino.name.toLowerCase() === name.toLowerCase(),
);

if (!dinosaur) {
json(404, { error: "No dinosaur found." });
return;
}

json(200, dinosaur);
};
```

Now that the API routes are wired up and serving data, let's create the two
frontend pages: the index page and the individual dinosaur detail pages.

## Build the frontend

We'll create our homepage by updating our `./src/routes/index.tsx` file using
Qwik's [`routeLoader$`](https://qwik.dev/docs/route-loader/) for server-side
data fetching. This `component$` loads and renders the dinosaur data during SSR
via `useDinosaurs()`:

```tsx
// ./src/routes/index.tsx

import { component$ } from "@builder.io/qwik";
import { Link, routeLoader$ } from "@builder.io/qwik-city";
import type { Dino } from "~/types";
import data from "~/data/dinosaurs.json" with { type: "json" };

export const useDinosaurs = routeLoader$(() => {
return data;
});

export default component$(() => {
const dinosaursSignal = useDinosaurs();

return (
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4">Welcome to the Dinosaur app</h1>
<p class="mb-4">Click on a dinosaur below to learn more.</p>
<ul class="space-y-2">
{dinosaursSignal.value.map((dinosaur: Dino) => (
<li key={dinosaur.name}>
<Link
href={`/${dinosaur.name.toLowerCase()}`}
class="text-blue-600 hover:underline"
>
{dinosaur.name}
</Link>
</li>
))}
</ul>
</div>
);
});
```

Now that we have our main index page, let's add a page for the individual
dinosaur information. We'll use Qwik's
[dynamic routing](https://qwik.dev/docs/routing/), with `[name]` as the key for
each dinosaur. This page leverages `routeLoader$` to fetch individual dinosaur
details based on the URL parameter, with built-in error handling if the dinosaur
isn't found.

The component uses the same SSR pattern as our index page, but with
parameter-based data loading and a simpler display layout for individual
dinosaur details:

```tsx
// ./src/routes/[name]/index.tsx

import { component$ } from "@builder.io/qwik";
import { Link, routeLoader$ } from "@builder.io/qwik-city";
import type { Dino } from "~/types";
import data from "~/data/dinosaurs.json" with { type: "json" };

export const useDinosaurDetails = routeLoader$(({ params }): Dino => {
const dinosaurs = data;
const dinosaur = dinosaurs.find(
(dino: Dino) => dino.name.toLowerCase() === params.name.toLowerCase(),
);

if (!dinosaur) {
throw new Error("Dinosaur not found");
}

return dinosaur;
});

export default component$(() => {
const dinosaurSignal = useDinosaurDetails();

return (
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4">{dinosaurSignal.value.name}</h1>
<p class="mb-4">{dinosaurSignal.value.description}</p>
<Link href="/" class="text-blue-600 hover:underline">
Back to all dinosaurs
</Link>
</div>
);
});
```

Now that we have built our routes and the frontend components, we can run our
application:

```bash
deno task dev
```

This will start the app at `localhost:5173`:

<figure>

<video class="w-full" alt="Build a qwik app with Deno." autoplay muted loop playsinline src="./images/how-to/qwik/demo.mp4"></video>

</figure>

Tada!

## Next steps

🦕 Now you can build and run a Qwik app with Deno! Here are some ways you could
enhance your dinosaur application:

Next steps for a Qwik app might be to use Qwik's lazy loading capabilities for
dinosaur images and other components, or add client-side state management for
complex features.

- Add persistent data store
[using a database like Postgres or MongoDB](https://docs.deno.com/runtime/tutorials/connecting_to_databases/)
and an ORM like [Drizzle](https://docs.deno.com/examples/drizzle_tutorial/) or
[Prisma](https://docs.deno.com/runtime/tutorials/how_to_with_npm/prisma/)
- use Qwik's lazy loading capabilities for dinosaur images and components
- add client-side state management
- self-host your app to
[AWS](https://docs.deno.com/runtime/tutorials/aws_lightsail/),
[Digital Ocean](https://docs.deno.com/runtime/tutorials/digital_ocean/), and
[Google Cloud Run](https://docs.deno.com/runtime/tutorials/google_cloud_run/)

0 comments on commit f435355

Please sign in to comment.