js

Build Ultra-Fast E-Commerce Apps with Qwik City and Drizzle ORM

Discover how Qwik City and Drizzle ORM enable instant interactivity and type-safe data for blazing-fast web apps.

Build Ultra-Fast E-Commerce Apps with Qwik City and Drizzle ORM

I was building an e-commerce dashboard recently, and I hit a wall. The page looked great on my fast connection, but on a slower mobile network, it just sat there. Blank. It was waiting for a massive JavaScript bundle to download before anything could happen. The user saw a page, but couldn’t click a button. That moment of frustration is why I’m writing this. There has to be a better way to build fast, interactive web apps from the very first pixel.

That search led me to a powerful combination: Qwik City and Drizzle ORM. This isn’t just another framework tutorial. It’s about a different way of thinking. Instead of sending all the code to the client and then “waking it up” (a process called hydration), Qwik sends a page that’s already awake. It’s ready to go. Drizzle ORM gives us a clean, type-safe way to talk to our database from this new architecture. Let’s build something real together.

We’ll create a product catalog. Think of a simple storefront. We need to list products, show details, and maybe have an admin section. The goal is instant interaction, no matter how big our app gets.

First, let’s set up our project. Open your terminal and run:

npm create qwik@latest qwik-drizzle-shop
cd qwik-drizzle-shop
npm install drizzle-orm postgres
npm install -D drizzle-kit @types/pg

This gives us a Qwik City project and adds Drizzle for database work. Now, we need to define what our data looks like. We’ll create a schema.ts file. This is our single source of truth for the database structure.

// src/db/schema.ts
import { pgTable, serial, text, numeric, integer, boolean } from 'drizzle-orm/pg-core';

export const products = pgTable('products', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  slug: text('slug').notNull().unique(),
  price: numeric('price', { precision: 10, scale: 2 }).notNull(),
  stock: integer('stock').notNull().default(0),
  featured: boolean('featured').default(false),
});

See how that reads? It’s very close to writing SQL, but it’s TypeScript. We get autocomplete and type checking. Drizzle doesn’t hide SQL; it makes it safer. Next, we need a connection. We’ll create a simple utility to get our database client.

// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

export function getDb() {
  const client = postgres(process.env.DATABASE_URL!);
  return drizzle(client, { schema });
}

Now for the interesting part: Qwik City. Its routing is file-based. A folder like src/routes/products/ becomes a route. Inside, an index.tsx file is the page. But the magic is in the loader$ function. This runs only on the server. It fetches data and sends it along with the HTML. The browser never waits for JavaScript to fetch the initial product list.

Let’s make our homepage. We’ll create src/routes/index.tsx. The component is just for display. The data fetching happens separately.

// src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { getDb } from '~/db';
import { products } from '~/db/schema';
import { desc } from 'drizzle-orm';

// This runs on the SERVER
export const useFeaturedProducts = routeLoader$(async () => {
  const db = getDb();
  const featuredItems = await db
    .select()
    .from(products)
    .where(eq(products.featured, true))
    .orderBy(desc(products.createdAt))
    .limit(4);
  return featuredItems;
});

// This is the component that renders in the browser
export default component$(() => {
  const productSignal = useFeaturedProducts();
  const featuredProducts = productSignal.value;

  return (
    <div>
      <h1>Featured Products</h1>
      <div class="product-grid">
        {featuredProducts.map((product) => (
          <div key={product.id} class="product-card">
            <h2>{product.name}</h2>
            <p>${product.price}</p>
            {/* The button is interactive IMMEDIATELY */}
            <button onClick$={() => alert(`Added ${product.name}`)}>
              Add to Cart
            </button>
          </div>
        ))}
      </div>
    </div>
  );
});

Did you notice the onClick$ handler? Here’s where Qwik’s “resumability” clicks. That click handler isn’t active because JavaScript for the whole component downloaded. It’s because Qwik serialized the listener’s purpose into the HTML. Only when you click does the browser fetch the tiny piece of code for that specific action. The page is interactive before the main app JavaScript even finishes loading.

What about a dynamic page, like /products/wireless-headphones? Qwik City handles that elegantly. Create a file at src/routes/products/[slug]/index.tsx. The [slug] in brackets is a route parameter.

// src/routes/products/[slug]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$, routeAction$ } from '@builder.io/qwik-city';
import { getDb } from '~/db';
import { products } from '~/db/schema';
import { eq } from 'drizzle-orm';

export const useProductData = routeLoader$(async ({ params }) => {
  const db = getDb();
  const [product] = await db
    .select()
    .from(products)
    .where(eq(products.slug, params.slug));
  
  if (!product) {
    // Qwik City will automatically handle 404s
    throw new Error('Product not found');
  }
  return product;
});

// Even form actions can be server-first
export const useAddToCart = routeAction$(async (data, requestEvent) => {
  // This runs on the server when the form is submitted
  const productId = data.productId;
  // ... logic to add to a cart session
  return { success: true, message: 'Item added' };
});

export default component$(() => {
  const productSignal = useProductData();
  const product = productSignal.value;
  const cartAction = useAddToCart();

  return (
    <div>
      <h1>{product.name}</h1>
      <p>Price: ${product.price}</p>
      <p>In Stock: {product.stock}</p>
      
      <form action={cartAction}>
        <input type="hidden" name="productId" value={product.id} />
        <button type="submit">Add to Cart</button>
      </form>
      {cartAction.value?.success && (
        <p>✅ {cartAction.value.message}</p>
      )}
    </div>
  );
});

The form uses a Qwik City routeAction$. When submitted, it goes straight to the server, processes the request, and returns a result. This works even if JavaScript hasn’t loaded yet—a concept called progressive enhancement. The experience is robust.

So, what does this all add up to? You get a website that feels like a static page in its speed but behaves like a full single-page app. The Time to Interactive metric, which measures when a user can actually do something, becomes nearly instant. Your database queries are clean and type-safe with Drizzle. Your application logic is split into tiny, on-demand pieces with Qwik.

The best part? This isn’t theoretical. You can start applying these concepts to parts of your existing projects. The combination of server-driven data with resumable client interactivity solves real performance problems that users face every day.

Have you ever measured how long it takes for your app to become truly interactive? The results might surprise you. Try building a single route this way and see the difference. I’d love to hear what you build. If this approach to performance makes sense to you, share it with a teammate who’s also tired of the “hydration tax.” Let me know in the comments what you think the biggest hurdle is in adopting this pattern.


As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!


📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!


Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Keywords: qwik city,drizzle orm,ecommerce performance,web app speed,progressive enhancement



Similar Posts
Blog Image
Build Production-Ready Distributed Task Queue: BullMQ, Redis & Node.js Complete Guide

Learn to build a scalable distributed task queue system using BullMQ, Redis, and Node.js. Complete production guide with error handling, monitoring, and deployment strategies. Start building now!

Blog Image
Build a Real-Time Analytics Dashboard with Fastify, Redis Streams, and WebSockets Tutorial

Build real-time analytics with Fastify, Redis Streams & WebSockets. Learn data streaming, aggregation, and production deployment. Master high-performance dashboards now!

Blog Image
Complete Event Sourcing Guide: Build Node.js TypeScript Systems with EventStore DB

Learn to build a complete event sourcing system with Node.js, TypeScript & EventStore. Master CQRS patterns, aggregates, projections & production deployment.

Blog Image
Complete Guide to Next.js Prisma Integration: Build Type-Safe Full-Stack Applications in 2024

Learn how to integrate Next.js with Prisma ORM for type-safe full-stack development. Build powerful web apps with seamless database operations and TypeScript support.

Blog Image
How to Scale React Apps with Webpack Module Federation and Micro-Frontends

Discover how to break up monolithic React apps using Webpack Module Federation for scalable, independent micro-frontend architecture.

Blog Image
Mastering API Rate Limiting with Redis: Fixed, Sliding, and Token Bucket Strategies

Learn how to implement scalable API rate limiting using Redis with fixed window, sliding window, and token bucket algorithms.