7

Qwik City as a JavaScript Framework

 11 months ago
source link: https://devm.io/javascript/qwik-city-framework
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Fullstack development with Qwik

Qwik City as a JavaScript Framework


In this article, we will look at the JavaScript framework Qwik from the standpoint of fullstack development. In doing so, we will demonstrate how to set up a simple blog in Qwik. The goal is not to have a fully deployable application, but rather to demonstrate Qwik's technical capabilities.

Although Qwik is mostly viewed through the lens of a traditional SPA, it also includes a meta framework called Qwik City. In a nutshell, a meta framework extends a traditional frontend framework so that it can also be used on the backend. Meta frameworks for other frontend frameworks include Next.js (React), SolidStart (SolidJS), and SvelteKit (Svelte).

In this case, a meta framework can act as a traditional BFF (backend-for-frontend), pulling data from the actual backend and preparing it for the frontend. It can also serve as a full-fledged backend, connecting directly to the database. We will use it for the latter.

As a result, we will begin with the classic installation. Then we will get to the backend. We create the data model and connect to the database, as well as create a REST endpoint. Only then do we move on to the frontend. Finally, we will look at a cutting-edge deployment, where we deploy to Cloudflare.

Initialising

To install, we run npx create-qwik@latest. Then we are asked what type of directory we want to create, and we choose the Basic App (default selection) application type from the menu. We also confirm the next question about whether the npm dependencies should be installed. Next, we initialise the Git repository:

git init .
git add .
git commit -m "init"

Now we open the project directory with our IDE. The IDE of choice is, of course, completely up to the user, but Qwik includes pre-installed code snippets for VSCode. Therefore, this article assumes that VSCode will be used.

The application is started with npm start. Everything works fine if a browser opens on port 5173 and you see a start page (fig. 1) with information about Qwik.

Qwik start screen

Fig.1: Qwik start screen

Database connection

We begin with the database, as is standard practice. SQLite, an easy-to-use database, is used. We use the Prisma library to integrate databases in a Node.js environment, which provides full-type safety, automated migrations, and an ORM — similar to Hibernate (Java) or the Entity Framework (.NET). In other words, an all-round carefree package. Prisma and SQLite are installed quickly and in two steps. The following commands are run in the first step:

npm install prisma
npx prisma init --datasource-provider sqlite

Next, we create the table. Because we want to use Qwik to create a blog, our main entity will be a Post. The schema is created by pasting the content from listing 1 into the file /prisma/schema.prisma.

Listing 1: prisma/schema.prisma

model Post {
  id          Int       @id @default(autoincrement())
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
  name        String
  teaser      String
  content     String
  url         String
 
  @@unique(url)
}

After that, we initialise our database and the type-safe client using

npx prisma migrate dev --name init

That is it. The SQLite database file, dev.db, should now be in the prisma directory.

According to the schema description, our blog entity has four additional columns in addition to the automatically assigned values like a primary key (id), created-At, and updatedAt. The users will enter the values for these. In addition to the mandatory name and content, we need a teaser to display on the home page. Finally, the url column allows us to specify a unique URL through which this post can be accessed.

Of course, there are lots of other ways to use Prisma; please see the official Prisma documentation at [1]. We will go with the existing, exit Prisma, and then create a CRUD endpoint for the Post entity.

API endpoint for Post entity

By API endpoint, we mean an endpoint where Qwik does no rendering, and data can only be exchanged using JSON. So it's the same behaviour we've seen from other backends. Qwik City is now being used for this.

There is no additional installation or configuration required. The default installation has everything completely set up.

Routing is part of Qwik City. We are dealing with conventional file-based routing. That means we do not need to configure routing anywhere specific; instead, we simply create a folder whose name corresponds to the route. This folder is automatically considered a subroute if it has a subfolder.

Each directly callable route must also include an index.tsx or index.ts file. The extension .tsx indicates that a component has been rendered, whereas .ts indicates that it is a pure API endpoint. An index.mdx or index.md file could also be used instead. These, however, will not be necessary for our blog.

In Qwik City, the /src/routes folder is considered the root directory for routing. So, if we created an index.tsx file in /src/routes/posts/programming, it would be accessible at the URL /posts/programming.

For our CRUD endpoint, we create an api folder and add the posts subfolder. In it, we create an index.ts, which should have the contents shown in listing 2.

Listing 2: src/routes/api/posts/index.ts

import { RequestHandler } from "@builder.io/qwik-city";
 
export const onGet: RequestHandler = async () => {
  return { value: "Hallo von Qwik" };
};

Now we call http://localhost:5173/api/posts, which should return an object literal with the property value "Hello from Qwik".

The endpoint implementation adheres to conventions as well. If this route is called, Qwik City checks whether the index.ts exports a function that listens for the name on{HttpMethod}. Because we just chose the GET method, the onGet function was used.

To display posts, we first need to get entries into the database. We'll use the API right away for this. Because we are going REST here, the POST method is ideal. By convention, we need to add an onPost method to our index.tsx that receives the data and sends it to the database. For our Post entity, we need four properties: name, teaser, url, and content (listing 3).

Listing 3: src/routes/api/posts/index.ts

import { RequestHandler } from "@builder.io/qwik-city";
import { PrismaClient } from "@prisma/client";
 
type AddPost = {
  name: string;
  teaser: string;
  url: string;
  content: string;
};
 
export const onGet: RequestHandler = async () => {
  return { value: "Hallo von Qwik" };
};
 
export const onPost: RequestHandler = async ({ request }) => {
  const addPost = await request.json();
  const prisma = new PrismaClient();
  prisma.post.create({ data: addPost });
};

This is expected to change in the future, as we will see later, there are still a few vulnerabilities here. The type in RequestHandler is unfortunately not the body of our POST request but refers to the parameters of the URL. While this makes it perfectly suitable for typical GET requests, for POST, we, unfortunately, have to live with the type any and the json() function.

As a result, we should perform type validation at runtime here as well. There are numerous libraries available that can do this mostly automatically. We'll use zod as an example.

zod is only used for this one example. The esteemed reader can then add zod as an extra exercise for all subsequent requests. After installation using npm install zod, we extend our code as shown in listing 4.

Listing 4: src/routes/api/posts/index.ts

import { RequestHandler } from "@builder.io/qwik-city";
import { PrismaClient } from "@prisma/client";
import { z } from "zod";
 
const addPostSchema = z.object({
  name: z.string(),
  teaser: z.string(),
  url: z.string(),
  content: z.string(),
});
 
export const onGet: RequestHandler = async () => {
  return { value: "Hallo von Qwik" };
};
 
export const onPost: RequestHandler = async ({ request }) => {
  const body = await request.json();
  const addPost = addPostSchema.parse(body);
  const prisma = new PrismaClient();
  await prisma.post.create({ data: addPost });
};

With z.object, we define the structure of the AddPost type. After that, we have the possibility to use addPostSchema.parse to see if an unknown object matches the schema. We use a very simple schema here and require it to have four string properties with corresponding names. If it does not, the parse method will throw an error. Thus, we have created a 100 percent type-safe endpoint.

Let's give it a shot right now by executing the following POST request with curl. Instead of curl, you can use any other tool, such as Postman, etc.:

curl -X POST http://localhost:5173/api/posts -H "Content-Type: application/json" -d '{"name": "Test", "url": "test", "content": "Das ist ein Testpost", "teaser": "Qwik Test

After successful execution, we can modify our existing onGet method to read the items directly from the database. However, we do not want to pass the complete entity to the outside, but only the properties that are needed in the frontend. These would be our already known four string properties and the id (listing 5).

Listing 5: src/routes/api/posts/index.ts

// imports
 
export const onGet: RequestHandler = async () => {
  const prisma = new PrismaClient();
  const dbPosts = await prisma.post.findMany();
  return dbPosts.map(({ id, name, teaser, url, content }) => ({
    id, name, teaser, url, content,
  }));
};
 
// onPost

If we now call the URL http://localhost:5173/api/posts in the browser, our previously created post should be displayed as JSON. We can now create the onPost and onDelete methods using the same pattern. However, because there is nothing significantly new here, this is left as an extra exercise for the reader.

We can see in onPost that we extracted the request property from the function. Another set of properties allows us to set HTTP headers, read or write cookies, and more. The params object, which we can use to read Query-Strings in GET requests, is one of the most commonly used.

It is time to see not only JSON output but also HTML renderings...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK