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}`);
---