Astro Integration
Build a storefront with Astro using the Skakio Catalog API.
Setup
1. Install the SDK
npm install @skakio/catalog-api
2. Configure Environment Variables
Create .env:
SKAKIO_API_KEY=pk_live_your_key_here
SKAKIO_STORE_ID=store_abc123
3. Create API Client
Create src/lib/skakio.ts:
const API_BASE = 'https://api.skakio.com';
class SkakioClient {
private apiKey: string;
private token: string | null = null;
private tokenExpiresAt: number = 0;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
private async getToken(): Promise<string> {
const now = Date.now();
const buffer = 60 * 1000; // 1 minute buffer
if (!this.token || now > this.tokenExpiresAt - buffer) {
const res = await fetch(`${API_BASE}/auth/token`, {
method: 'POST',
headers: { 'X-API-Key': this.apiKey },
});
const { data } = await res.json();
this.token = data.token;
this.tokenExpiresAt = now + data.expires_in * 1000;
}
return this.token!;
}
async fetch<T>(path: string): Promise<T> {
const token = await this.getToken();
const res = await fetch(`${API_BASE}${path}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
throw new Error(`API error: ${res.status}`);
}
const { data } = await res.json();
return data;
}
}
export const skakio = new SkakioClient(import.meta.env.SKAKIO_API_KEY);
Page Examples
Store Page
Create src/pages/index.astro:
---
import { skakio } from '../lib/skakio';
import Layout from '../layouts/Layout.astro';
const store = await skakio.fetch(`/api/store/${import.meta.env.SKAKIO_STORE_ID}`);
const { data: categories } = await skakio.fetch(`/api/store/${import.meta.env.SKAKIO_STORE_ID}/listings`);
---
<Layout title={store.name}>
<header>
{store.logo && <img src={store.logo.url} alt={store.name} />}
<h1>{store.name}</h1>
<p>{store.description}</p>
</header>
<main>
<h2>Categories</h2>
<ul>
{categories.map((cat) => (
<li>
<a href={`/category/${cat.id}`}>
{cat.name}
<span>({cat.publicationCount} products)</span>
</a>
</li>
))}
</ul>
</main>
</Layout>
Category Page
Create src/pages/category/[id].astro:
---
import { skakio } from '../../lib/skakio';
import Layout from '../../layouts/Layout.astro';
const { id } = Astro.params;
const listing = await skakio.fetch(`/api/listing/${id}?expand=publication`);
---
<Layout title={listing.name}>
<h1>{listing.name}</h1>
<p>{listing.description}</p>
<div class="products-grid">
{listing.publications?.map((product) => (
<a href={`/product/${product.id}`} class="product-card">
{product.media?.[0] && (
<img src={product.media[0].url} alt={product.title} />
)}
<h3>{product.title}</h3>
<p class="price">
{product.price.currency} {product.price.amount.toFixed(2)}
</p>
</a>
))}
</div>
</Layout>
<style>
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
.product-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 1rem;
text-decoration: none;
color: inherit;
}
.product-card img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
border-radius: 4px;
}
.price {
font-weight: bold;
color: #059669;
}
</style>
Product Page
Create src/pages/product/[id].astro:
---
import { skakio } from '../../lib/skakio';
import Layout from '../../layouts/Layout.astro';
const { id } = Astro.params;
const [product, reviews] = await Promise.all([
skakio.fetch(`/api/publication/${id}`),
skakio.fetch(`/api/publication/${id}/reviews?limit=5`),
]);
---
<Layout title={product.title}>
<article>
<div class="product-images">
{product.media?.map((m) => (
<img src={m.url} alt={m.alt || product.title} />
))}
</div>
<div class="product-info">
<h1>{product.title}</h1>
<p class="price">
{product.price.currency} {product.price.amount.toFixed(2)}
</p>
<p>{product.description}</p>
{product.quantity.available > 0 ? (
<p class="stock in-stock">In Stock ({product.quantity.available})</p>
) : (
<p class="stock out-of-stock">Out of Stock</p>
)}
</div>
</article>
<section class="reviews">
<h2>Reviews</h2>
{reviews.data.length > 0 ? (
<ul>
{reviews.data.map((review) => (
<li>
<div class="rating">{'★'.repeat(review.rating)}{'☆'.repeat(5 - review.rating)}</div>
<h4>{review.title}</h4>
<p>{review.content}</p>
<span class="author">{review.author?.name}</span>
</li>
))}
</ul>
) : (
<p>No reviews yet.</p>
)}
</section>
</Layout>
Search Page
Create src/pages/search.astro:
---
import { skakio } from '../lib/skakio';
import Layout from '../layouts/Layout.astro';
const query = Astro.url.searchParams.get('q') || '';
let results = null;
if (query) {
results = await skakio.fetch(`/api/marketplace/search?q=${encodeURIComponent(query)}&limit=20`);
}
---
<Layout title={query ? `Search: ${query}` : 'Search'}>
<h1>Search</h1>
<form method="get">
<input type="search" name="q" value={query} placeholder="Search products..." />
<button type="submit">Search</button>
</form>
{results && (
<div class="results">
<h2>Products ({results.publications?.length || 0})</h2>
<div class="products-grid">
{results.publications?.map((product) => (
<a href={`/product/${product.id}`} class="product-card">
{product.media?.[0] && <img src={product.media[0].url} alt="" />}
<h3>{product.title}</h3>
<p>{product.price.currency} {product.price.amount.toFixed(2)}</p>
</a>
))}
</div>
</div>
)}
</Layout>
Static Site Generation (SSG)
For static builds, fetch data at build time:
---
// src/pages/category/[id].astro
import { skakio } from '../../lib/skakio';
export async function getStaticPaths() {
const storeId = import.meta.env.SKAKIO_STORE_ID;
const { data: categories } = await skakio.fetch(`/api/store/${storeId}/listings`);
return categories.map((cat) => ({
params: { id: cat.id },
}));
}
const { id } = Astro.params;
const listing = await skakio.fetch(`/api/listing/${id}?expand=publication`);
---
Hybrid Rendering
For frequently updated data, use server-side rendering:
---
// src/pages/product/[id].astro
export const prerender = false; // SSR for this page
const { id } = Astro.params;
const product = await skakio.fetch(`/api/publication/${id}`);
---