Apply your knowledge to build something amazing!
:information_source: Project Overview Difficulty Level: Intermediate
Estimated Time: 2-3 hours
Skills Practiced:
- API integration and data fetching
- Component-based architecture in Svelte
- Props and state management
- Event handling and form submission
- Dynamic rendering with conditional logic
- CSS styling and animations
graph LR
A[🚀 Project Setup] --> B[🧩 Create Game Component]
B --> C[🔍 Build Search Component]
C --> D[🏠 Integrate in Main Page]
D --> E[🎨 Style & Polish]
E --> F[🌟 Advanced Features]
style A fill:#e1f5fe
style B fill:#b3e5fc
style C fill:#81d4fa
style D fill:#4fc3f7
style E fill:#29b6f6
style F fill:#03a9f4
Steam Game Searcher
Mini project:
In this project, we will be fetching all available game data from the game purchasing platform known as Steam to be listed on the website using this game API in a new SvelteKit project:
https://www.cheapshark.com/api/1.0/deals?storeID=1&upperPrice=15
Then, users can input a game title to search for any game's details and show it at the top of the website using this game API. The variable gameQuery
is the API path:
https://www.cheapshark.com/api/1.0/deals?title= + gameQuery
Let's use Hoppscotch.io to view the game data. You may refer to Application Programming Interface (API) notes on how to use Hoppscotch.io. To go to the website, click on the link below: https://hoppscotch.io/
:bulb: API Testing Best Practice Before building your app, always test your API endpoints to understand:
- The data structure returned
- Response times and performance
- Error handling scenarios
- Available query parameters
Make sure you add the path for the game title that you want to get the API data. For example, you can add a game title as Tumblestone as the API endpoint.
https://www.cheapshark.com/api/1.0/deals?title=Tumblestone
This is the sample output:
:warning: Common Setup Pitfalls
- Wrong Repository: Make sure you're using the correct GitHub repository URL
- Folder Navigation: Always create components in the
src/lib
folder, not elsewhere- File Extensions: Svelte components must end with
.svelte
- Save Before Testing: Always save your files before running the app
(you may open this GitHub link to download the zip file)
DO NOT DELETE the existing files in the template:
ONLY EDIT the necessary files.
Before proceeding, ensure you have:
src/lib
folder where components will be createdGame.svelte
is responsible for showing all the game details in a division for each game. This component is reusable - you'll use it to display both search results and the full game list!
Create a Svelte component named Game.svelte
by adding a new file through src/lib/Game.svelte
.
In the Game.svelte
file, create 4 props which are gameTitle
, gameImage
, gamePrice
and gameRating
in the <script>
tag. Make sure to use the export keyword to create the props.
<script>
// Props allow parent components to pass data to this component
export let gameTitle; // The name of the game
export let gameImage; // URL for the game's thumbnail
export let gamePrice; // Current sale price
export let gameRating; // Steam's rating text
</script>
:bulb: Understanding Props The
export
keyword in Svelte creates a prop - a way for parent components to pass data down. Think of props as the component's "inputs" that make it dynamic and reusable!
Copy the code below and paste it outside the <script>
tag:
<div class="gameTitle">
<img src={gameImage} alt="{gameTitle}'s Image" />
<h3>Title: {gameTitle}</h3>
<h5>Selling Price: US${gamePrice}</h5>
<h5>Steam Rating: {gameRating}</h5>
</div>
:warning: Image Loading Issues If game images don't load:
- Check your internet connection
- The API might return broken image URLs
- Add a fallback image with error handling (see Extension Challenges)
Copy and paste the style code at the bottom of the Svelte component:
<style>
.gameTitle {
width: 180px;
background: #fff;
padding: 5px;
margin: 20px auto;
border: 1px solid #ddd;
border-radius: 5px;
text-align: center;
transition: transform .2s;
box-shadow: 2px 2px 6px rgba(0, 0.5, 0, 0.5);
}
img {
border-radius: 5px;
max-width: 80px;
}
h3 {
font-size: 16px;
margin: 0;
}
h5 {
font-size: 13px;
margin: 0;
color:#0d6efd;
}
.gameTitle:hover {
-ms-transform: scale(1.3);
-webkit-transform: scale(1.3);
transform: scale(1.3);
}
</style>
Great work! You've completed the Game component. Before moving on:
Game.svelte
in the src/lib
folderGameSearch.svelte
is used to create a searching field in a division that includes a website title, a searching instruction, an input field, a "Search" button and the details of a game that is searched. This is where the magic happens - users can search for any game!
Create a Svelte component named GameSearch.svelte
by adding a new file through src/lib/GameSearch.svelte
.
Game.svelte
into GameSearch.svelte
in the <script>
tag.<script>
import Game from './Game.svelte';
let gameQuery = ""; // Stores what the user types in the search box
let gameSearched = null; // Stores the search result (or null if no result)
</script>
:bulb: State Management
gameQuery
uses two-way binding with the input fieldgameSearched
starts asnull
to hide the result section until a search is made- These variables automatically trigger UI updates when changed!
gameQuery
and gameSearched
. Assign an empty value to gameQuery
. gameQuery
is the API path that we enter in the search input for searching the game details.Create a function by copying the code below that allows the search for a game once the form is submitted.
async function handleSubmit(e) {
e.preventDefault(); // Stops the page from refreshing
// Check if search box is empty
if (gameQuery.trim() === "") {
gameSearched = null;
return;
}
try {
// Fetch game data from API
const response = await fetch(`https://www.cheapshark.com/api/1.0/deals?title=${gameQuery}`);
const data = await response.json();
gameSearched = data[0] || null; // Get first result or null
} catch (error) {
console.error("Error fetching game data:", error);
gameSearched = null;
}
}
:warning: Common API Errors If your search isn't working:
- No results: The game title might not match exactly - try partial names
- Network error: Check your internet connection
- CORS error: Make sure you're running from a proper server, not file://
- Empty results: The API returns an empty array if no games match
The path for the game data can be viewed using Hoppscotch.io.
Copy the code below and paste it outside the <script>
tag:
<div class="gameTitleSearch">
<h2>👁️ Steam Game Searcher 🎮</h2>
<h3>Search for Game Title below:</h3>
<form on:submit={handleSubmit}>
<input type="text" bind:value={gameQuery} placeholder="Enter game title..." />
<button type="submit">Search</button>
</form>
{#if gameSearched}
<div class="search-result">
<h4>Search Result:</h4>
<Game
gameTitle={gameSearched.title}
gameImage={gameSearched.thumb}
gamePrice={gameSearched.salePrice}
gameRating={gameSearched.steamRatingText}
/>
</div>
{/if}
</div>
:bulb: Understanding the Code
on:submit={handleSubmit}
- Calls our function when form is submittedbind:value={gameQuery}
- Two-way binding between input and variable{#if gameSearched}
- Only shows results when we have data- We pass API data as props to the Game component we created earlier!
Copy and paste the style code at the bottom of the Svelte component:
<style>
.gameTitleSearch {
text-align: center;
padding: 50px;
font-family: Comic Sans MS;
border-radius: 10px;
background-image: url("../General/Images/watercolor-background.jpg");
background-size: cover;
background-position: center;
}
h2 {
margin: 0 0 15px;
color: #333;
text-shadow: 2px 2px 4px rgba(255,255,255,0.8);
}
h3 {
margin: 0 0 15px;
font-size: 13px;
color: #555;
}
input {
margin-bottom: 20px;
width: 50%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #0056b3;
}
.search-result {
margin-top: 30px;
}
.search-result h4 {
color: #333;
margin-bottom: 20px;
}
</style>
Excellent progress! Your search component is ready. Check that you have:
GameSearch.svelte
with proper importsNow it's time to bring everything together! The main page will display both the search functionality and a list of all available games.
On the index.svelte
, copy all the coding below and paste it inside:
index.svelte
<script>
import { onMount } from "svelte";
import Game from '$lib/Game.svelte';
import GameSearch from '$lib/GameSearch.svelte';
let games = [];
// onMount runs after the component is first rendered
onMount(() => {
getGames();
});
async function getGames() {
try {
// Fetch all games under $15 from Steam
const response = await fetch('https://www.cheapshark.com/api/1.0/deals?storeID=1&upperPrice=15');
const data = await response.json();
games = data;
} catch (error) {
console.error("Error fetching games:", error);
games = [];
}
}
</script>
<main>
<GameSearch />
{#if games.length > 0}
<div class="games-section">
<h2>Available Games Under $15</h2>
<ul class="gameTitleList">
{#each games as gameTitle}
<li>
<Game
gameTitle={gameTitle.title}
gameImage={gameTitle.thumb}
gamePrice={gameTitle.salePrice}
gameRating={gameTitle.steamRatingText}
/>
</li>
{/each}
</ul>
</div>
{:else}
<div class="loading">
<p>Loading games...</p>
</div>
{/if}
</main>
<style>
@import url("https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css");
main {
min-height: 100vh;
}
.games-section {
padding: 20px;
text-align: center;
}
.games-section h2 {
margin-bottom: 30px;
color: #333;
}
.gameTitleList {
display: flex;
flex-flow: wrap;
list-style: none;
margin: 0;
padding: 0;
background-image: url("../General/Images/steam-background.jpg");
background-size: cover;
background-attachment: fixed;
min-height: 500px;
padding: 20px;
}
.gameTitleList li {
width: 180px;
margin: 0 auto;
float: none;
}
.loading {
text-align: center;
padding: 50px;
font-size: 18px;
color: #666;
}
</style>
:bulb: Performance Note The
{#each}
block efficiently renders multiple Game components. Svelte only updates components that have changed data, making this very fast even with many games!
You're almost done! Verify:
index.svelte
If your app isn't working correctly, here are common issues and solutions:
Error: Failed to resolve import "$lib/Game.svelte"
Solution: Make sure your components are in the src/lib
folder, not elsewhere.
Error fetching games: TypeError: Failed to fetch
Solution:
Solution:
Solution:
<style>
tags are at the bottom of each componentThe complete Steam Game Searcher includes:
Game.svelte
): Displays individual game information with hover effectsGameSearch.svelte
): Provides search functionality for specific gamesindex.svelte
): Combines both components and displays a list of available games:warning: Before Moving to Advanced Features Make sure your basic app is working perfectly:
- Can you search for games successfully?
- Does the game list load on page refresh?
- Do game cards show all information correctly?
- Does the hover animation work smoothly?
If any of these aren't working, debug them first before adding advanced features!
Ready to take your Steam Game Searcher to the next level? Let's add exciting new features!
Other than the game title, game image, game selling price and game rating, let's add on some extra information about the game!
You can go to the Game API to grab any game detail that you want to add.
gameDescription
prop to show game details<script>
export let gameTitle;
export let gameImage;
export let gamePrice;
export let gameRating;
export let originalPrice = null;
export let savings = null;
</script>
<div class="gameTitle">
<img src={gameImage} alt="{gameTitle}'s Image" />
<h3>Title: {gameTitle}</h3>
<div class="price-section">
{#if originalPrice && savings}
<h5 class="original-price">Was: ${originalPrice}</h5>
<h5 class="discount">Save {savings}%!</h5>
{/if}
<h5 class="current-price">Now: US${gamePrice}</h5>
</div>
<h5>Steam Rating: {gameRating}</h5>
</div>
Ready for some extra challenges? Try implementing these features to make your app even better:
Show a friendly message when no games are found:
{#if gameSearched === null && gameQuery !== ""}
<p class="no-results">No games found for "{gameQuery}". Try another search!</p>
{/if}
Add a loading state while searching:
let isLoading = false;
async function handleSubmit(e) {
isLoading = true;
// ... existing code ...
isLoading = false;
}
{#if isLoading}
<div class="spinner">Searching...</div>
{/if}
Handle broken game images gracefully:
<img
src={gameImage}
alt="{gameTitle}'s Image"
on:error={(e) => e.target.src = '/placeholder-game.png'}
/>
Let users filter games by maximum price:
let maxPrice = 15;
$: filteredGames = games.filter(game => parseFloat(game.salePrice) <= maxPrice);
Add sorting functionality:
let sortBy = 'price'; // 'price', 'title', 'rating'
function sortGames(games, sortBy) {
return [...games].sort((a, b) => {
if (sortBy === 'price') return a.salePrice - b.salePrice;
if (sortBy === 'title') return a.title.localeCompare(b.title);
// Add more sort options
});
}
Let users save their favorite games:
let favorites = [];
function toggleFavorite(gameId) {
if (favorites.includes(gameId)) {
favorites = favorites.filter(id => id !== gameId);
} else {
favorites = [...favorites, gameId];
}
// Save to localStorage
localStorage.setItem('favorites', JSON.stringify(favorites));
}
:bulb: Pro Tips for Extensions
- Start with one challenge at a time
- Test each feature thoroughly before moving to the next
- Use browser DevTools to debug issues
- Consider user experience - make features intuitive
- Add smooth transitions and animations for better UX
Code Review Part 1 https://youtu.be/q7SHhF2HgNM
Code Review Part 2 https://youtu.be/WT9VtY0FNWs
Code Review Part 3 https://youtu.be/0B0aaAECqmY
Code with AI: Efficiently handle API data.
Prompts:
:bulb: Using AI Effectively When using AI for help:
- Be specific about what you want to achieve
- Provide context about your existing code
- Ask for explanations, not just code
- Always test and understand the code before using it
- Learn from the patterns AI suggests
Congratulations on completing the Steam Game Searcher! Before submitting:
When you have completed your project, submit it using the link below:
Make sure to test your webpage before submitting to ensure all required elements are working properly!