|
| 1 | +--- |
| 2 | +title: Build a Cafe Finder with JavaScript |
| 3 | +author: Gabriela Banaag |
| 4 | +uid: |
| 5 | +datePublished: 2025-08-06 |
| 6 | +description: |
| 7 | +header: https://imgur.com/a/iz4inIW |
| 8 | +tags: |
| 9 | +--- |
| 10 | + |
| 11 | +<BannerImage |
| 12 | + link="https://imgur.com/a/iz4inIW" |
| 13 | + description="" |
| 14 | + uid={false} |
| 15 | + cl="for-sidebar" |
| 16 | +/> |
| 17 | + |
| 18 | +# Project Title |
| 19 | + |
| 20 | +<AuthorAvatar |
| 21 | + author_name="Gabriela Banaag" |
| 22 | + author_avatar="" |
| 23 | + username="gabtot" |
| 24 | + uid={false} |
| 25 | +/> |
| 26 | + |
| 27 | +<BannerImage |
| 28 | + link="https://imgur.com/a/iz4inIW" |
| 29 | + description="" |
| 30 | + uid={false} |
| 31 | +/> |
| 32 | + |
| 33 | +**Prerequisites: HTML, CSS, JavaScript** |
| 34 | + |
| 35 | +**Read Time:** 30 minutes |
| 36 | + |
| 37 | +# ## Introduction |
| 38 | + |
| 39 | +If you’re an iced-bev-meets-coworking-space fiend like me and want to shake up what cafe you head to next, this is the perfect project for you. We’ll be using our trilogy of web dev tools: ****HTML****, ****CSS****, and ****JavaScript****, and an API from the Google Maps Platform called ****Places****. We’ll also make some quick animations and implement swiping recognition with a JavaScript library called ****Hammer****! |
| 40 | + |
| 41 | +## About the Google Maps Platform |
| 42 | + |
| 43 | +The Google Maps Platform is a collection of Application Programming Interfaces (APIs) and Software Development Kits (SDKs). Each tool on the platform lets users use Google Maps functions and data in their own projects! |
| 44 | + |
| 45 | +Some popular apps that use the Google Maps Platform include Uber and Airbnb, which access a user’s location to calculate rides and show rentals in specific areas! |
| 46 | + |
| 47 | +The [Places API](https://developers.google.com/maps/documentation/places/web-service/overview) is a smaller API under the Google Maps API. It fetches location data like location names, addresses, photos, and many more attributes! It’s a great tool for accessing updated data and finding multiple places without manually copying all of their information into a project. ****We’ll be using this in our project! **** |
| 48 | + |
| 49 | +# ## Setup |
| 50 | + |
| 51 | +## ### Workspace |
| 52 | + |
| 53 | +Start by creating a new HTML/CSS/JS project in [[Codédex Builds](https://www.codedex.io/builds)](https://www.codedex.io/builds). You can also use a Code editor of your choice, like [VSCode](https://code.visualstudio.com/). |
| 54 | + |
| 55 | +## ### Google Cloud Console |
| 56 | + |
| 57 | +Set up your project in the [[Google Cloud Console](https://developers.google.com/maps/documentation/elevation/cloud-setup)](https://developers.google.com/maps/documentation/elevation/cloud-setup). You’ll enter a project name and billing information. Your website won’t work as it should unless you enable billing, but you won’t be charged if the API doesn’t get called past the monthly limit (we'll teach you how to cap it at the limit, which is usually 10,000 API calls). |
| 58 | + |
| 59 | +## ### Clone Template |
| 60 | + |
| 61 | +Head over to [[this GitHub link](https://github.com/gbanaag/codedex-cafe-finder-template/tree/main)](https://github.com/gbanaag/codedex-cafe-finder-template/tree/main) and clone the repository. The repo has all the basic HTML and CSS set up so we don’t have to worry about it later and focus on API implementation. |
| 62 | + |
| 63 | +You can clone the template in 3 different ways: |
| 64 | + |
| 65 | +- Downloading the folder and adding it to your local workspace |
| 66 | +- GitHub Desktop app |
| 67 | +- ``git clone`` with command line |
| 68 | + |
| 69 | +If you don’t have VS Code and don’t know how to use it, head over to our [[VS Code setup tutorial](https://www.codedex.io/projects/set-up-your-local-environment-in-python#download-vs-code)](https://www.codedex.io/projects/set-up-your-local-environment-in-python#download-vs-code). |
| 70 | + |
| 71 | +# ## Accessing Locations with JavaScript |
| 72 | + |
| 73 | +Time to write some back-end code! |
| 74 | + |
| 75 | +## ### API Key |
| 76 | + |
| 77 | +Here’s how to create and protect your API key: |
| 78 | + |
| 79 | +1. Head to the [Google Cloud Console](https://console.cloud.google.com/) and open the project picker, then click on ****new project**** in the corner. |
| 80 | +2. Come up with a name for your project! _Note that the name can’t be changed later._ |
| 81 | +3. Make sure you’re on your cafe finder project and select the APIs & Services button. |
| 82 | +4. On the left menu, click on library. |
| 83 | +5. Search for the Places API (new) and click on it. Make sure the Places API is enabled. |
| 84 | +6. Click on the hamburger menu and look for the APIs & Services button again. Then click on ****create** **credentials ****, then API key. |
| 85 | +7. After your key is generated, click on edit key. Restrict your application to websites. |
| 86 | +8. Add the domain your project is hosted on. In my case, mine is hosted on Codédex builds. |
| 87 | + |
| 88 | +Put your API key at the top of your JavaScript file. It should look like this: |
| 89 | + |
| 90 | +```jsx |
| 91 | +const apiKey = "STRING_OF_RANDOM_CHARACTERS_HERE"; |
| 92 | +``` |
| 93 | + |
| 94 | +<aside> |
| 95 | +⚠️ |
| 96 | + |
| 97 | +****Make sure your API key is restricted to just your project (step 7), or else anyone can use it freely and you’ll get charged for it!**** |
| 98 | + |
| 99 | +</aside> |
| 100 | + |
| 101 | +## ### cors-anywhere |
| 102 | + |
| 103 | +Head to [https://cors-anywhere.herokuapp.com/](https://cors-anywhere.herokuapp.com/). Make sure to click on the ``Request temporary access to the demo server`` button to get set up with a temporary server for your project. |
| 104 | + |
| 105 | +Then, add these lines to the top of your JavaScript file. |
| 106 | + |
| 107 | +```jsx |
| 108 | +const useProxy = true; |
| 109 | +const proxy = "https://cors-anywhere.herokuapp.com/"; |
| 110 | +``` |
| 111 | + |
| 112 | +## ### Geolocation |
| 113 | + |
| 114 | +To see cafes nearby, the browser needs access to your location. There’s a built in JavaScript function that takes care of that for you called ``useLocation()`` and it takes your device’s latitude and longitude coordinates. |
| 115 | + |
| 116 | +```jsx |
| 117 | + function getLocation() { |
| 118 | + const cache = JSON.parse(localStorage.getItem('cachedLocation') || '{}'); |
| 119 | + const now = Date.now(); |
| 120 | +``` |
| 121 | +
|
| 122 | +Next, we’ll check if any location data has been cached (saved to the browser) and is less than 10 minutes old. |
| 123 | +
|
| 124 | +```jsx |
| 125 | +if (cache.timestamp && now - cache.timestamp < 10 * 60 * 1000) { |
| 126 | + useLocation(cache.lat, cache.lng); |
| 127 | + } |
| 128 | +``` |
| 129 | +
|
| 130 | +If there’s no cached location information found, the browser pulls your current latitude and longitude from a built in function. |
| 131 | +
|
| 132 | +```jsx |
| 133 | +else { |
| 134 | + navigator.geolocation.getCurrentPosition(pos => { |
| 135 | + const lat = pos.coords.latitude; |
| 136 | + const lng = pos.coords.longitude; |
| 137 | +``` |
| 138 | +
|
| 139 | +We’ll save the location and timestamp in ``localStorage`` in case we use our cafe finder website in the near future. Then we’ll add error handling - an alert that lets you know if your location can’t be accessed. |
| 140 | +
|
| 141 | +```jsx |
| 142 | + localStorage.setItem('cachedLocation', JSON.stringify({ lat, lng, timestamp: now })); |
| 143 | + useLocation(lat, lng); |
| 144 | + }, () => alert("Location access denied or unavailable.")); |
| 145 | + } |
| 146 | + } |
| 147 | +``` |
| 148 | + |
| 149 | +## ### Using the API |
| 150 | + |
| 151 | +We’ll start implementing the Google Places API to take locations and their information from Google Maps itself! We’ll start by writing the `useLocation()` function. |
| 152 | + |
| 153 | +We’ll create an ****endpoint****, which can send requests for specific actions and functions to an API. Our endpoint will reference the Google Places API by inserting your API key and saved latitude and longitude. |
| 154 | + |
| 155 | +```jsx |
| 156 | +async function useLocation(lat, lng) { |
| 157 | + const endpoint = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&radius=1500&type=cafe&key=${apiKey}`; |
| 158 | + const url = useProxy ? proxy + endpoint : endpoint; |
| 159 | +``` |
| 160 | +
|
| 161 | +We’ll call the API to find nearby cafes and *_fetch_* their urls. |
| 162 | +
|
| 163 | +```jsx |
| 164 | +try { |
| 165 | + const response = await fetch(url); |
| 166 | + const data = await response.json(); |
| 167 | +``` |
| 168 | +
|
| 169 | +If the API finds results for nearby cafes, we’ll take that data and insert it into cards (we'll write the `displayCards` function next!) |
| 170 | +
|
| 171 | +```jsx |
| 172 | + if (data.results) { |
| 173 | + displayCards(data.results); |
| 174 | + } else { |
| 175 | + alert("No cafes found."); |
| 176 | + } |
| 177 | + } catch (e) { |
| 178 | + console.error("Error fetching Places API:", e); |
| 179 | + alert("Error fetching cafes."); |
| 180 | + } |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +# ## Cafe UI and Animations |
| 185 | + |
| 186 | +We’re gonna create a wrapper for our cards, and inject data from the API into the wrapper as we go. To start, let’s make a function called ``displayCards()`` that renders a div container whenever the website starts up. |
| 187 | + |
| 188 | +<ImageZoom src="https://imgur-url-here.png" style={{ width: "60%", height: "auto" }} alt="alt text here"/> |
| 189 | + |
| 190 | +We’ll start by creating a container that takes the first saved cafe in our cafe options. We’ll make the content of this card blank for now, and fill it in with information when we use the Places API. |
| 191 | + |
| 192 | +```jsx |
| 193 | + function displayCards(cafes) { |
| 194 | + const container = document.querySelector('.cards'); |
| 195 | + container.innerHTML = ''; |
| 196 | +``` |
| 197 | +
|
| 198 | +We’ll then make a for loop that iterates through the cafe list to… |
| 199 | +
|
| 200 | +- Create a new div to wrap each cafe card |
| 201 | +- Add a class (``swipe-wrapper``) to each card to keep card styles consistent |
| 202 | +- Adjust the card’s ****z-index**** (display order) so that new cards appear under old ones |
| 203 | +
|
| 204 | +```jsx |
| 205 | + cafes.forEach((cafe, i) => { |
| 206 | + const wrapper = document.createElement('div'); |
| 207 | + wrapper.className = 'swipe-wrapper'; |
| 208 | + wrapper.style.zIndex = 200 - i; |
| 209 | + |
| 210 | + var newCards = document.querySelectorAll('.location-card:not(.removed)'); |
| 211 | + var allCards = document.querySelectorAll('.location-card'); |
| 212 | + }); |
| 213 | + } |
| 214 | +``` |
| 215 | + |
| 216 | +We’ll add more inside the function that inserts location information into each card by calling the Places API. Add a line that creates a card div and another line that assigns the card’s class name to be ``location-card``. |
| 217 | + |
| 218 | +Then, add a line that takes the URL of a photo of the cafe and saves it into the ``imgUrl`` variable. |
| 219 | + |
| 220 | +```jsx |
| 221 | +const card = document.createElement('div'); |
| 222 | +card.className = 'location-card'; |
| 223 | + |
| 224 | +const imgUrl = cafe.photos?.[0]?.photo_reference |
| 225 | + ? `https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${cafe.photos[0].photo_reference}&key=${apiKey}` |
| 226 | + : 'https://via.placeholder.com/250x150?text=No+Image'; |
| 227 | +``` |
| 228 | +
|
| 229 | +We’ll grab each cafe location’s photo, place id, image URL, and rating. We’ll save all the information in an object called ``cafeData``. |
| 230 | +
|
| 231 | +```jsx |
| 232 | +const cafeData = { |
| 233 | + name: cafe.name, |
| 234 | + place_id: cafe.place_id, |
| 235 | + photo: imgUrl, |
| 236 | + rating: cafe.rating || 'N/A' |
| 237 | +}; |
| 238 | +``` |
| 239 | +
|
| 240 | +Putting in tags between the ``` symbol when changing an element’s ``innerHTML`` allows you to render HTML elements when functions run. We’ll append (connect) the card to its wrapper, and append its wrapper to its container. |
| 241 | + |
| 242 | +```jsx |
| 243 | +card.innerHTML = ` |
| 244 | + <img src="${imgUrl}" alt="${cafe.name}" /> |
| 245 | + <h3>${cafe.name}</h3> |
| 246 | + <p>⭐️ Rating: ${cafe.rating || 'N/A'}</p> |
| 247 | + <p><small>Swipe right to save 💖</small></p> |
| 248 | +`; |
| 249 | +
|
| 250 | +wrapper.appendChild(card); |
| 251 | +container.appendChild(wrapper); |
| 252 | +``` |
| 253 | + |
| 254 | +## ### Hammer |
| 255 | + |
| 256 | +[Hammer](http://hammerjs.github.io/) is a library used to recognize touch gestures in the browser! |
| 257 | + |
| 258 | +<aside> |
| 259 | +💡 |
| 260 | + |
| 261 | +A ****library**** is a collection of reusable resources like functions, classes, and pieces of pre-written code. An **API****** is a an interface that lets your code communicate with another application. |
| 262 | + |
| 263 | +</aside> |
| 264 | + |
| 265 | +To make sure we have swiping gestures detected on the site, we’re gonna import the library by adding this line in the ``<head>`` of your HTML file: |
| 266 | + |
| 267 | +```jsx |
| 268 | +<script src="https://cdn.jsdelivr.net/npm/[email protected]/hammer.min.js"></script> |
| 269 | +``` |
| 270 | + |
| 271 | +[gif] |
| 272 | + |
| 273 | +We’re going to add a function that detects left and right swipes and makes each cafe card flick and fade away. Add this to the bottom of your `displayCards()` function to make it happen: |
| 274 | + |
| 275 | +```jsx |
| 276 | + const hammertime = new Hammer(wrapper); |
| 277 | + hammertime.on('swipeleft', () => { |
| 278 | + wrapper.style.transform = 'translateX(-150%) rotate(-15deg)'; |
| 279 | + wrapper.style.opacity = 0; |
| 280 | + setTimeout(() => wrapper.remove(), 100); |
| 281 | + }); |
| 282 | + hammertime.on('swiperight', () => { |
| 283 | + saveCafe(JSON.stringify(cafeData)); |
| 284 | + wrapper.style.transform = 'translateX(150%) rotate(15deg)'; |
| 285 | + wrapper.style.opacity = 0; |
| 286 | + setTimeout(() => wrapper.remove(), 100); |
| 287 | + }); |
| 288 | +``` |
| 289 | +
|
| 290 | +## ### Saving Cafes |
| 291 | +
|
| 292 | +We’ll be using ``localStorage`**`** to save cafes to the browser. It automatically caches (saves) specific interactions from your site to your browser that you choose to save without needing a database. |
| 293 | +
|
| 294 | +** **JSON Parsing**** is used to take strings and save them as data structures like objects or arrays. To implement this, we'll add a line that stores the string as a cafe **object.** Then we'll save the cafe object into an array called ``saved``. |
| 295 | +
|
| 296 | +```jsx |
| 297 | +function saveCafe(cafeJSON) { |
| 298 | + const cafe = JSON.parse(cafeJSON); |
| 299 | + let saved = JSON.parse(localStorage.getItem('savedCafes') || '[]'); |
| 300 | +``` |
| 301 | +
|
| 302 | +Next, write an `if` statement that searches your array of saved cafes and makes sure that the cafe hasn’t been saved yet. This is done by using the ``find`` function, which sees if a cafe’s `place_id` exists in the array. |
| 303 | +
|
| 304 | +If the cafe isn’t saved, the function will ``push()`` (insert) the cafe into the ``saved`` array of cafes, and alert the user that the cafe has been saved. |
| 305 | +
|
| 306 | +```jsx |
| 307 | + if (!saved.find(c => c.place_id === cafe.place_id)) { |
| 308 | + saved.push(cafe); |
| 309 | + localStorage.setItem('savedCafes', JSON.stringify(saved)); |
| 310 | + alert(`${cafe.name} saved!`); |
| 311 | + } |
| 312 | +``` |
| 313 | +
|
| 314 | +We’ll need a fallback in case the cafe is already saved. Write an `else` statement that alerts the user if a cafe has already been saved, so it doesn’t get duplicated into the saved list of cafes. |
| 315 | +
|
| 316 | +```jsx |
| 317 | +else { |
| 318 | + alert(`${cafe.name} is already saved.`); |
| 319 | + } |
| 320 | +} |
| 321 | +``` |
| 322 | +
|
| 323 | +To check what cafes we have saved, we’ll make a function called ``showSaved()``. Since all the cafe information isn’t saved in HTML and card form, we need to pull the saved info from `localStorage` and use ****DOM manipulation**** to create HTML elements like each cafe’s image, name, and rating of the spot when prompted. |
| 324 | +
|
| 325 | +DOM manipulation is a JavaScript process that changes a specific part of a webpage’s content, style, or structure. In this case, we’ll be inserting cafe information into ``<div>`` blocks onto your webpage. |
| 326 | +
|
| 327 | +Start by writing a function called ``showSaved()``. Similar to the ``displayCards()`` function, we’ll make a container and leave the content blank to start. |
| 328 | +
|
| 329 | +```jsx |
| 330 | +function showSaved() { |
| 331 | + const container = document.querySelector('.cards'); |
| 332 | + container.innerHTML = ''; |
| 333 | +``` |
| 334 | +
|
| 335 | +Then, add this line to parse through the list of saved cafes and save the information in a variable. |
| 336 | +
|
| 337 | +```jsx |
| 338 | + const saved = JSON.parse(localStorage.getItem('savedCafes') || '[]'); |
| 339 | +``` |
| 340 | +
|
| 341 | +If the saved array is empty, it’ll update the page by adding a ``<p>`` tag to say no cafes have been saved yet. |
| 342 | +
|
| 343 | +```jsx |
| 344 | +if (saved.length === 0) { |
| 345 | + container.innerHTML = '<p>No saved cafes yet 😢</p>'; |
| 346 | + return; |
| 347 | + } |
| 348 | +``` |
| 349 | +
|
| 350 | +For each cafe in the saved array, we’ll make a card div, add the `location-card` class name, and update the card’s content with `innerHTML` to include the current cafe’s information. |
| 351 | +
|
| 352 | +```jsx |
| 353 | +saved.forEach(cafe => { |
| 354 | + const card = document.createElement('div'); |
| 355 | + card.className = 'location-card'; |
| 356 | + card.innerHTML = ` |
| 357 | + <img src="${cafe.photo}" alt="${cafe.name}" /> |
| 358 | + <h3>${cafe.name}</h3> |
| 359 | + <p>⭐️ Rating: ${cafe.rating}</p> |
| 360 | + `; |
| 361 | +``` |
| 362 | +
|
| 363 | +We then use the `appendChild()` function to add the cafe card that was just generated to its container to live in. |
| 364 | +
|
| 365 | +```jsx |
| 366 | +container.appendChild(card); |
| 367 | + }); |
| 368 | + } |
| 369 | +``` |
| 370 | +
|
| 371 | +# ## Conclusion |
| 372 | +
|
| 373 | +Here's my final result: |
| 374 | +
|
| 375 | +<ImageZoom src="https://imgur.com/a/TuZpvsR" /> |
| 376 | +
|
| 377 | +You did it! Enjoy your newly built cafe finder and be sure to treat yourself to a coffee, chai, matcha, or boba to celebrate your hard work. 🍵 |
| 378 | +
|
| 379 | +If you’re looking for another challenge or a way to spice up your project, you could try… |
| 380 | +
|
| 381 | +- A [insert favorite cuisine here] restaurant finder |
| 382 | +- Adding a map of places next to the cards |
| 383 | +- Animating your cards further |
| 384 | +
|
| 385 | +## ### Troubleshooting |
| 386 | +
|
| 387 | +- Make sure you’re granted temporary access on https://cors-anywhere.herokuapp.com/. |
| 388 | +- Make sure to allow location access when you try out the site. |
| 389 | +
|
| 390 | +## ### Additional Resources |
| 391 | +
|
| 392 | +- GitHub repository |
| 393 | +- YouTube video (coming soon!) |
| 394 | +- [Places API Documentation](https://developers.google.com/maps/documentation/places/web-service/overview#how-use) |
0 commit comments