











This is the second part of a three-part guide that focuses on achieving caching at scale for e-commerce websites using Astro and Turso. Here are the parts of the guide:
The guide is based on a fork of the Astro Storefront template. The source code is available on GitHub, and a live demo is available on Netlify.
Netlify has loved Astro long before we established our partnership with them, which is why we’ve transitioned our main website to use it.
Much of Astro’s charm is in how it starts off with a lightweight, zero client-side JavaScript approach, then lets you add state-of-the-art dynamic behaviors while still providing freedom of choice. Want to use React? Great! But you could also choose from a whole list of other UI frameworks, or keep building without a UI framework at all (e.g. with actions).
Beyond Astro, the web frameworks class of ‘24 was really exciting, from Angular 19 to Tanstack Start. This also means that most of the caching functionality we offer applies to these frameworks as well.
If you’re using Astro already, you may already know how well Turso is integrated with the framework, via Astro DB. Astro DB works with any libSQL-compatible database, and Turso is the company behind the libSQL project.
We also recently launched a Turso integration, which makes it easy to get started with Turso in your Astro project.
For these reasons, Turso makes a great team with Astro and was the perfect fit for this caching guide.
The original storefront template from the Astro team was built to support pluggable e-commerce backends. (Astro built their own merch shop with it.)
We’ve created our own fork to demonstrate the caching functionality for scaling e-commerce sites, and this is what we’ll be using for the rest of the guide.
The open-source template currently provides a simple mock implementation with a few products and collections hard-coded into the TypeScript code.
To realistically demonstrate caching, we need an external data source for our storefront, and a decently-sized product catalog. We chose Astro DB as the data access layer for storage, as it is very quick to start with.
Astro DB is built on top of Drizzle ORM, a popular type-safe ORM, and libSQL, a fork of SQLite created and maintained by Turso. Astro DB takes care to generate the actual database schema from code, handle migrations, seed the database, and more.
By design, SQLite is an embedded library with no standard server process or network protocol of its own. The libSQL fork adds such a server, which Turso uses to offer a managed service.
During local development, Astro DB uses libSQL in in-memory mode so there’s no additional process for you to run. In production, you can easily connect to a remote server, either managed by Turso or by yourself.
Here is the full schema file for the example site:
import { defineDb, defineTable, column } from "astro:db";
const ProductsTable = defineTable({
columns: {
id: column.text({ primaryKey: true }),
name: column.text(),
slug: column.text(),
tagline: column.text({ optional: true }),
description: column.text({ optional: true }),
price: column.number(),
discount: column.number(),
imageUrl: column.text(),
collectionIds: column.json({ optional: true }),
createdAt: column.date(),
updatedAt: column.date(),
deletedAt: column.date({ optional: true }),
},
indexes: [{ on: ["price"] }, { on: ["updatedAt"] }],
});
const CollectionsTable = defineTable({
columns: {
id: column.text({ primaryKey: true }),
name: column.text(),
description: column.text(),
slug: column.text({ optional: true }),
imageUrl: column.text({ optional: true }),
createdAt: column.date(),
updatedAt: column.date(),
deletedAt: column.date({ optional: true }),
},
});
const JobsTable = defineTable({
columns: {
name: column.text({ primaryKey: true }),
lastSuccess: column.json({ optional: true }),
lastFailure: column.json({ optional: true }),
},
});
export default defineDb({
tables: { ProductsTable, CollectionsTable, JobsTable },
});
A few notes on the above:
createdAt, updatedAt and deletedAt, which we’ll use later on for detecting changes. Most production systems have such columns and ensure that these are kept up-to-date on any mutation going through the data layer.ProductsTable.collectionIds. We also skipped entirely the definition of product variants, which can get opinionated and complex in e-commerce systems.Based on the above schema, Astro will take care to generate the actual database schema and TypeScript interfaces. Next, we’ll look at the data.
For the data, we’ve taken a subset of 10,000 products from a high-quality dataset of fashion products that is publicly available on Kaggle.
We used Claude AI to transform the raw data (which is never fully consistent for a real dataset of this kind, because humans x time = mess) into concise descriptions and catchy taglines for all products, and store it in a single JSON file. Additionally, there is a much smaller JSON file for product collections.
To populate the databases, the seed function (db/seed.ts) reads the JSON files and inserts their content to the database. This takes place automatically every time you run astro dev, giving you a clean in-memory database.
For production, provisioning the database should be done by you, either manually or as part of your infrastructure automation code. The needed steps are detailed in the next part of the guide.
Finally, we’ve modified the implementation of the mock e-commerce client module (src/lib/client.mock.ts) to fetch data from Turso, following the interface from the original Astro template with minimal changes.
When it comes to queries and updates (i.e. the DML part of SQL) Astro DB gets mostly out of the way and provides you with Drizzle ORM’s functionality as-is, without introducing new abstractions on top to complicate your life.
While Astro DB is great for greenfield projects, you may well already have a database, an e-commerce backend, a choice of CMS, etc. or just prefer a different approach to database access. Admittedly, magical type inference and all that jazz isn’t everyone’s cup of tea, especially the type warnings which can get enigmatic sometimes (this is a common gripe with TypeScript, really).
For the scope of the demo, we’ve laid out a bare-bones authentication mechanism. You don’t need to setup any extra software for it, but only define two environment variables in your .env file:
BASIC_PASSWORD for a shared password-based login.JWT_SECRET to a random token (see .env.example for how to generate it).This is needed for two reasons:
POST|GET /api/revalidate endpoint. We’ll go back to this one later.Once a website visitor successfully logs-in via the /login page, the server sets a cookie with a JWT, based on your secret. This cookie is then used to verify relevant actions done via the browser.
For API calls, the endpoint checks for the presence of a valid JWT in the standard Authorization: Bearer <...> request header.
With a database and a splash of authentication now in place, let’s provide a very quick way for logged-in users to update a product and witness revalidation in action.
Instead of a building a whole admin interface-like mechanism, we’ve added a one-click update button for products in the /search page that is available to logged-in users. This button adds a random Pokemon name to the end of a product’s name, or replaces that name with another one if such exists.
Here’s how that looks like in action:
With all the infrastructure in place, it’s time for some cache tagging and purging in the final part of the guide.
Next: Using cache tags
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。