Starshop

Starshop is a webshop built as a group project (team "1-Mula") during my HBO-ICT study. The main learning goal was working with third-party APIs. The app lets users browse, search, and purchase game licenses, with all game data pulled live from external sources and payments handled through Stripe. The stack is TypeScript throughout — an Express backend and a Lit-based frontend — deployed on Railway (API) and Vercel (frontend + serverless payment functions).

technologies

TypeScript
HTML
CSS
Node.js
codeSnippet

Third-Party APIs Used We integrated three external APIs, each serving a different purpose: Steam Store API — Our primary data source for game information and pricing. The backend proxies requests to Steam's appdetails and featuredcategories endpoints so the frontend never talks to Steam directly.

1// SteamStoreService.ts — Fetching game details from the Steam Store API
2const STORE_API_BASE: string = "https://store.steampowered.com/api";
3
4public async getAppDetails(appId: string): Promise<SteamAppDetails | null> {
5    const response: Response = await fetch(
6        `${STORE_API_BASE}/appdetails?appids=${encodeURIComponent(appId)}`
7    );
8
9    if (!response.ok) {
10        throw new Error(`Steam Store API error: ${response.status}`);
11    }
12
13    const json = await response.json();
14    const entry = json[appId];
15
16    if (!entry.success || !entry.data) {
17        return null;
18    }
19
20    return entry.data;
21}

SteamSpy API — Used alongside the Steam Store API to get community data like review scores, player counts, and genre tags that Steam's own API doesn't expose easily.

1// SteamSpyService.ts — Fetching community stats from SteamSpy
2const STEAMSPY_BASE: string = "https://steamspy.com/api.php";
3
4public async getAppDetails(appId: string): Promise<SteamSpyAppDetails> {
5    const response: Response = await fetch(
6        `${STEAMSPY_BASE}?request=appdetails&appid=${encodeURIComponent(appId)}`
7    );
8
9    if (!response.ok) {
10        throw new Error(`SteamSpy API error: ${response.status}`);
11    }
12
13    return await response.json() as SteamSpyAppDetails;
14}

Stripe API — Handles the entire checkout and payment flow. We built a StripeService on the backend that creates Checkout Sessions and verifies payment status, keeping the secret key server-side.

1// StripeService.ts — Creating a Stripe Checkout Session
2public async createCheckoutSession(
3    items: { name: string; price: number; quantity: number }[],
4    successUrl: string,
5    cancelUrl: string
6): Promise<{ checkoutUrl: string; sessionId: string }> {
7    const bodyParams: URLSearchParams = new URLSearchParams();
8    bodyParams.append("mode", "payment");
9    bodyParams.append("success_url", successUrl);
10    bodyParams.append("cancel_url", cancelUrl);
11
12    items.forEach((item, index) => {
13        bodyParams.append(`line_items[${index}][price_data][currency]`, "eur");
14        bodyParams.append(`line_items[${index}][price_data][product_data][name]`, item.name);
15        bodyParams.append(`line_items[${index}][price_data][unit_amount]`,
16            Math.round(item.price * 100).toString());
17        bodyParams.append(`line_items[${index}][quantity]`, item.quantity.toString());
18    });
19
20    const response = await fetch(`${this._stripeApiUrl}/checkout/sessions`, {
21        method: "POST",
22        headers: {
23            "Content-Type": "application/x-www-form-urlencoded",
24            "Authorization": `Bearer ${this.getSecretKey()}`,
25        },
26        body: bodyParams.toString(),
27    });
28
29    const data = await response.json();
30    return { checkoutUrl: data.url, sessionId: data.id };
31}

Frontend API Consumption On the frontend, a service layer abstracts all API calls. Game data and prices are fetched in parallel to keep the UI responsive:

1// CurrentGameService.ts — Fetching game detail + prices in parallel
2const [gameResponse, priceResponse] = await Promise.all([
3    fetch(`${VITE_API_URL}games/${gameID}`, {
4        method: "GET",
5        headers: { "Content-Type": "application/json" },
6        credentials: "include",
7    }),
8    fetch(`${VITE_API_URL}products/prices/${gameID}`, {
9        method: "GET",
10        headers: { "Content-Type": "application/json" },
11    }),
12]);
CLICK ME!