├── .env.sample
├── .gitignore
├── .gitpod.yml
├── .vscode
└── settings.json
├── README.md
├── astra.json
├── data
├── bonus
│ ├── dataset_action.md
│ ├── dataset_anime.md
│ ├── dataset_awardwinning.md
│ ├── dataset_children.md
│ ├── dataset_comedies.md
│ ├── dataset_documentaries.md
│ ├── dataset_dramas.md
│ ├── dataset_fantasy.md
│ ├── dataset_french.md
│ ├── dataset_horror.md
│ ├── dataset_independent.md
│ ├── dataset_italian.md
│ ├── dataset_music.md
│ ├── dataset_romance.md
│ ├── dataset_scifi.md
│ └── dataset_thriller.md
└── movies_by_genre.csv
├── functions
├── getGenres.js
└── getMovies.js
├── images
├── Filexplorer0.png
├── OpenPorts.png
├── allow.png
├── astra-cli-dsbulk-2.png
├── astra-cli-setup-2.png
├── astra-create-token.gif
├── chrome-logo.svg
├── create_astra_db.png
├── deploy-1.png
├── deploy-2.png
├── deploy-3b.png
├── deploy-4.png
├── deploy-4sitename.png
├── deploy-4skeletal.png
├── deploy-5.png
├── deploy-to-netlify.gif
├── deployed_netflix_clone.png
├── env_file.png
├── firefox-logo.svg
├── gitpod-01-home-annotated.png
├── gitpod-02-url.png
├── gitpod_trick.png
├── graphql-back.png
├── graphql-playground-2.png
├── graphql-playground-3.png
├── graphql-playground-4.png
├── graphql-playground-5.png
├── graphql-playground-6.png
├── graphql-playground.png
├── netflix-badge.png
├── netlify-build.png
├── netlify-connect-01.png
├── netlify-deploy-prod-2.png
├── netlify-install-cli.png
├── netlify-link-2.png
├── netlify-login-2.png
├── netlify-open-site-2.png
├── netlify_env_import.png
├── newbrowser1.png
├── open-playground-2-wh.png
├── open-playground-2.png
├── playground-1.png
├── playground-2.png
├── playground-3.png
├── preview.png
├── tabs-vs-playgroundtabs-labeled-2.png
├── ui.png
└── waiting_for_authorization-2.png
├── know_your_gitpod.md
├── netlify.toml
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── slides
└── slides.pdf
└── src
├── App.css
├── App.js
├── components
├── Card.js
├── HeroSection.js
├── NavBar.js
└── Section.js
└── index.js
/.env.sample:
--------------------------------------------------------------------------------
1 | # Ensure to fully replace "your_token" AND "your_endpoint" with the token
2 | # and endpoint values from your database (keep the quotes).
3 | ASTRA_DB_APPLICATION_TOKEN="your_token"
4 | ASTRA_DB_GRAPHQL_URL="your_endpoint"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | node_modules
3 | .env
4 | .netlify
5 |
6 | #
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - name: workshop-graphql-netflix
3 | before: |
4 | cd /workspace/workshop-graphql-netflix
5 | curl -Ls "https://dtsx.io/get-astra-cli" | bash >> ./install.log
6 | printf 'export PATH="$HOME%s:$PATH"\n' "/.astra/cli" >> $HOME/.bashrc
7 | printf 'unset JAVA_TOOL_OPTIONS\n' >> $HOME/.bashrc
8 | nvm install 16.13.0
9 | npm install -g npm@latest
10 | npm install -g netlify-cli
11 | npm install
12 | command: |
13 | source /home/gitpod/.astra/cli/astra-init.sh
14 | echo -e "\n\n *** Astra DB Netflix Clone READY ... Let's go ! ***\n"
15 | gp open README.md
16 | ports:
17 | - port: 8888
18 | onOpen: open-preview
19 | - port: 3000
20 | onOpen: ignore
21 | github:
22 | prebuilds:
23 | master: true
24 | branches: true
25 | pullRequests: true
26 | pullRequestsFromForks: false
27 | addCheck: true
28 | addComment: false
29 | addBadge: true
30 | addLabel: false
31 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.editor.enablePreviewFromCodeNavigation": true,
3 | "workbench.editor.enablePreviewFromQuickOpen": true,
4 | "workbench.editor.enablePreview": true,
5 | "workbench.editorAssociations": {
6 | "*.md": "vscode.markdown.preview.editor"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 🎓 Netflix Clone using Astra DB and GraphQL
3 |
4 | [](https://gitpod.io/from-referrer/)
5 | [](http://www.apache.org/licenses/LICENSE-2.0)
6 | [](https://discord.com/widget?id=685554030159593522&theme=dark)
7 |
8 | *50 minutes, Intermediate, [Start Building](#1-login-or-register-to-astradb-and-create-database)*
9 |
10 | A simple **ReactJS** Netflix homepage clone running on *Astra DB* that leverages the GraphQL API with *paging* and *infinite scrolling.*
11 | This application is the result of the collaboration between [Ania Kubow](https://www.youtube.com/channel/UC5DNytAJ6_FISueUfzZCVsw) and the Datastax Developer Advocate team.
12 |
13 |
14 |
15 | See the [Video Demo](https://imgur.com/3ns3UJB) of what you will build!
16 |
17 | 
18 |
19 | ## 🎯 Objectives
20 | * Build and run a Netflix clone.
21 | * Learn **GraphQL API** and how to use it with a database to create the tables and navigate the data.
22 | * Learn about **pagination** and **infinite scrolling** in a Web UI.
23 | * Leverage Netlify and DataStax Astra DB.
24 | * Deploy the Netflix clone to production with Netlify.
25 |
26 | ## ℹ️ Frequently asked questions ℹ️
27 |
28 |
29 | Can I run the workshop on my computer?
30 |
31 | There is nothing preventing you from running the workshop on your own machine.
32 | If you do so, you will need
33 | * git installed on your local system
34 | * [node 15 and npm 7 or later](https://www.whitesourcesoftware.com/free-developer-tools/blog/update-node-js/)
35 |
36 | You will have to adapt commands and paths based on your environment and install the dependencies by yourself. **We won't provide support** to keep on track with schedule. However, we will do our best to give you the info you need to be successful.
37 |
38 |
39 |
40 |
41 | What other prerequisites are there?
42 |
43 | * You will need a github account
44 | * You will also need Netlify and Astra DB accounts, but we'll work through that in the exercises
45 | * Use **Chrome** or **Firefox** for the best experience. Other browsers are great, but don't work well with the GitPod integration we use a bit later.
46 |
47 |
48 |
49 |
50 | Do I need to pay for anything for this workshop?
51 |
52 | **No.** All tools and services we provide here are FREE.
53 |
54 |
55 |
56 |
57 | Will I get a certificate if I attend this workshop?
58 |
59 | Yes, but attending the session is not enough. You need to complete the homeworks detailed below and you will get a participation badge.
60 |
61 |
62 |
63 |
64 | ## Materials for the Session
65 |
66 | It doesn't matter if you join our workshop live or you prefer to do at your own pace, we have you covered. In this repository, you'll find everything you need for this workshop:
67 |
68 | - [Slide deck](slides/slides.pdf)
69 | - [Discord chat](https://dtsx.io/discord)
70 | - ["cassandra" on StackOverflow](https://stackoverflow.com/questions/tagged/cassandra)
71 | - ["cassandra" on DBA StackExchange](https://dba.stackexchange.com/questions/tagged/cassandra)
72 |
73 |
74 | # Let's start
75 |
76 | ## Table of contents
77 |
78 | ### Part I - DB Setup & Data Ingest
79 | 1. [Create Astra DB Instance](#1-login-or-register-to-astradb-and-create-database)
80 | 2. [Create a security token](#2-create-a-security-token)
81 | 3. [Create table for genres with GraphQL](#3-create-table-for-genres-with-graphql)
82 | 4. [Insert genre data with GraphQL](#4-insert-genre-data-with-graphql)
83 | 5. [Retrieve genres with GraphQL](#5-retrieve-genres-with-graphql)
84 | 6. [Create a table for movies](#6-create-a-table-for-movies)
85 | 7. [Insert a few movies](#7-insert-a-few-movies)
86 | 8. [Retrieve movies: Pagination](#8-retrieve-movies-pagination)
87 |
88 | ### Part II - Build and Deploy Front-End
89 |
90 | 1. [Deploy skeletal GUI to Netlify](#1-deploy-skeletal-gui-to-netlify)
91 | 2. [Launch Gitpod from YOUR Github repo](#2-launch-gitpod-from-your-github-repo)
92 | 3. [Set up and use `astra-cli`](#3-set-up-and-use-astra-cli)
93 | 4. [Serverless Functions](#4-serverless-functions)
94 | 5. [Fetching from the Front-End](#5-fetching-from-the-front-end)
95 | 6. [Install the Netlify CLI](#6-install-the-netlify-cli)
96 | 7. [Provide DB connection parameters](#7-provide-db-connection-parameters)
97 | 8. [Run the app in dev mode](#8-run-the-app-in-dev-mode)
98 | 9. [Connect to your Netlify site](#9-connect-to-your-netlify-site)
99 | 10. [Deploy in production!](#10-deploy-in-production)
100 |
101 | [**🎓 Complete the assignment, receive your Badge!**](#homework)
102 |
103 | ### Extra resources
104 |
105 | - [Intro to GraphQL Workshop](https://github.com/datastaxdevs/workshop-intro-to-graphql)
106 | - [React starter using NPX](https://github.com/datastaxdevs/react-basics)
107 | - [React ToDo app](https://github.com/datastaxdevs/appdev-week1-todolist)
108 | - [What is JamStack?](https://github.com/datastaxdevs/workshop-battlestax/blob/master/README_JAM.md)
109 | - [Video tutorial with Ania Kubow](#video-tutorial-with-ania-kubow)
110 |
111 | # Part 1 - DB Setup & Data Ingest
112 |
113 | ## 1. Login or Register to AstraDB and create database
114 |
115 | > 🎁 *When creating your instance, use the promotion code **ANIA200** to get 200$ of additional free credit!*
116 |
117 | _**`ASTRA DB`** is the simplest way to run Cassandra with zero operations at all - just push the button and get your cluster. No credit card required, 40M read/write operations and about 80GB storage monthly for free - sufficient to run small production workloads. If you use up your credits the databases will pause, no charge, and you will be given the option to upgrade to a higher tier._
118 |
119 | Leveraging [Database creation guide](https://awesome-astra.github.io/docs/pages/astra/create-instance/#c-procedure) create a database. **Right-Click** the following button and *Open in a new TAB.*
120 |
121 |
122 |
123 | |Field|Value|
124 | |---|---|
125 | |**Database Name**| `workshops`|
126 | |**Keyspace Name**| `netflix`|
127 | |**Regions**| Select `GOOGLE CLOUD`, then an Area close to you, then a region with no LOCK 🔒 icons: the LOCKed regions are the region not accessible to the Free Tier.|
128 |
129 | > **ℹ️ Note:** If you already have a database `workshops`, simply add a keyspace `netflix` using the `Add Keyspace` button on the bottom right hand corner of the DB Dashboard page. You may have to "Resume" the database first in case it is in "hibernated" state.
130 |
131 | While the database is being created, you will also get a **Security token** (needed to authenticate with your database and start using it):
132 | **please IGNORE THIS ONE, as we will be soon creating a new, more powerful token for today**.
133 |
134 | The status will change from `Pending` to `Active` when the database is ready, this usually only takes 2-3 minutes.
135 |
136 |
137 | ## 2. Create a security token
138 |
139 | > Note: this step is very important, as the token generated automatically for you with
140 | > the database lacks some permissions we'll use in the workshop.
141 |
142 | [Create a token for your app](https://awesome-astra.github.io/docs/pages/astra/create-token/#c-procedure), _using the **"Database Administrator"** role_.
143 | Keep it handy for later use (best to download it in CSV format, as the values
144 | will not be visible afterward).
145 | This will provide authentication later when interacting with the database.
146 | Today, in particular, you'll need the string labeled "token" (the one starting with `AstraCS:...`).
147 |
148 | > **⚠️ Important**
149 | > The instructor will show the token creation on screen,
150 | > but will then destroy it immediately for security reasons.
151 |
152 |
153 | ## 3. Create table for genres with GraphQL
154 |
155 | ✅ **Step 3a:** Open **GraphQL Playground**:
156 |
157 | 0. Ensure you are logged on to your [Astra](https://astra.datastax.com) account
158 | 1. Click on the "workshops" database on the left (expanding the list if needed)
159 | 2. Click `Connect` TAB
160 | 3. Click the `APIs` connection method
161 | 4. Make sure `GraphQL API` is selected
162 | 5. Locate the link to your GraphQL Playground in the text
163 |
164 | 
165 |
166 |
167 | Click here if you are not using the "New Astra Experience" UI (yet)
168 |
169 | 
170 |
171 |
172 |
173 | **Note**: in the following, we will refer to "playground tabs". These are _not_ the tabs
174 | in your browser, rather they are tabs _within_ the Playground application,
175 | to switch between the (logically distinct) realms of "managing schema" and "managing data in the tables"
176 | (more on that later).
177 |
178 | 
179 |
180 | ✅ **Step 3b:** Provide the database token as header
181 |
182 | In the GraphQL Playground, **Populate HTTP HEADER** variable `x-cassandra-token` on the bottom of the page with your token (including the `AstraCS:` part).
183 | _This is the "Database Administrator" token you created earlier on the Astra DB dashboard (Step 2 above)._
184 |
185 |
186 |
187 |
188 | Note: make sure you are on the graphql-schema playground tab in this step. Click here to show image.
189 |
190 |
191 | 
192 |
193 |
194 |
195 | > Note: the GraphQL Playground starts with a ready-to-use _temporary token_ as the `x-cassandra-token` header. But you want the queries run in the Playground
196 | > to be identical to those that the Netlify functions will run from code, so **please replace the token with your DB token as instructed**.
197 |
198 | ✅ **Step 3c:** In GraphQL Playground, create the `reference_list` table:
199 |
200 | Copy the following **mutation** to the left panel
201 |
202 | ```yaml
203 | mutation createReferenceList {
204 | reference_list: createTable(
205 | keyspaceName:"netflix",
206 | tableName:"reference_list",
207 | ifNotExists:true
208 | partitionKeys: [
209 | { name: "label", type: {basic: TEXT} }
210 | ]
211 | clusteringKeys: [
212 | { name: "value", type: {basic: TEXT}, order: "ASC" }
213 | ]
214 | )
215 | }
216 | ```
217 |
218 |
219 | and then use the big "play button" arrow in the center to execute it. Click for screenshot.
220 |
221 | 
222 |
223 |
224 |
225 |
226 | **GraphQL Playground troubleshooting** (covers this whole section)
227 |
228 | |Trouble| Shooting|
229 | |---|---|
230 | |Server cannot be reached | Add Astra token to headers (including `AstraCS:...`; check quotes) |
231 | |Server cannot be reached (second playground tab) | Check playground target URL ends with `netflix` |
232 | Response not successful: Received status code 401 | Same as "server cannot be reached" |
233 | | Response not successful: Received status code 404 | Check spelling of keyspace in target URL |
234 | |"Play" button does nothing| Ensure query is syntactically correct |
235 | "Validation error of type FieldUndefined" | Most likely query in the wrong playground tab, or writing to table not created yet |
236 |
237 | ## 4. Insert genre data with GraphQL
238 |
239 | ✅ **Step 4a:** Get to the API URL for your keyspace
240 |
241 | In graphQL playground, **change playground tab** to now use `graphql`. The Playground has its own address bar
242 | (**note**: it's _not_ the address bar of your browser). Edit the ending of the URL shown there, from `system` to the
243 | name of the keyspace: `netflix`
244 |
245 | ✅ **Step 4b:** Repeat the insertion of the `x-cassandra-token` header for this playground tab (as you did for the first one):
246 |
247 |
248 | Show me!
249 |
250 | 
251 |
252 |
253 |
254 | ✅ **Step 4c:** In the GraphQL Playground, run the mutation that writes genre data:
255 |
256 | Copy the following mutation on the left panel:
257 |
258 | ```yaml
259 | mutation insertGenres {
260 | action: insertreference_list(value: {label:"genre", value:"Action"}) {
261 | value{value}
262 | }
263 | anime: insertreference_list(value: {label:"genre", value:"Anime"}) {
264 | value{value}
265 | }
266 | award: insertreference_list(value: {label:"genre", value:"Award-Winning"}) {
267 | value{value}
268 | }
269 | children: insertreference_list(value: {label:"genre", value:"Children & Family"}) {
270 | value{value}
271 | }
272 | classic: insertreference_list(value: {label:"genre", value:"Classic"}) {
273 | value{value}
274 | }
275 | comedies: insertreference_list(value: {label:"genre", value:"Comedies"}) {
276 | value{value}
277 | }
278 | crime: insertreference_list(value: {label:"genre", value:"Crime"}) {
279 | value{value}
280 | }
281 | cult: insertreference_list(value: {label:"genre", value:"Cult"}) {
282 | value{value}
283 | }
284 | documentaries: insertreference_list(value: {label:"genre", value:"Documentaries"}) {
285 | value{value}
286 | }
287 | drama: insertreference_list(value: {label:"genre", value:"Dramas"}) {
288 | value{value}
289 | }
290 | fantasy: insertreference_list(value: {label:"genre", value:"Fantasy"}) {
291 | value{value}
292 | }
293 | french: insertreference_list(value: {label:"genre", value:"French"}) {
294 | value{value}
295 | }
296 | horror: insertreference_list(value: {label:"genre", value:"Horror"}) {
297 | value{value}
298 | }
299 | independent: insertreference_list(value: {label:"genre", value:"Independent"}) {
300 | value{value}
301 | }
302 | international: insertreference_list(value: {label:"genre", value:"International"}) {
303 | value{value}
304 | }
305 | italian: insertreference_list(value: {label:"genre", value:"Italian"}) {
306 | value{value}
307 | }
308 | musicmusicals: insertreference_list(value: {label:"genre", value:"Music & Musicals"}) {
309 | value{value}
310 | }
311 | realitytv: insertreference_list(value: {label:"genre", value:"Reality TV"}) {
312 | value{value}
313 | }
314 | romance: insertreference_list(value: {label:"genre", value:"Romance"}) {
315 | value{value}
316 | }
317 | scifi: insertreference_list(value: {label:"genre", value:"Sci-Fi"}) {
318 | value{value}
319 | }
320 | thriller: insertreference_list(value: {label:"genre", value:"Thriller"}) {
321 | value{value}
322 | }
323 | tvshow: insertreference_list(value: {label:"genre", value:"TV Show"}) {
324 | value{value}
325 | }
326 | }
327 | ```
328 |
329 | then click on the big "play button" arrow in the center to execute the mutation
330 |
331 | ## 5. Retrieve genres with GraphQL
332 |
333 | ✅ **Step 5a:** In GraphQL Playground, not changing playground tab (stay on the second: "graphql", yeah) run the following query to read the `value` column of all table rows:
334 |
335 | ```yaml
336 | query getAllGenres {
337 | reference_list (value: {label:"genre"}) {
338 | values {
339 | value
340 | }
341 | }
342 | }
343 | ```
344 |
345 |
346 |
347 | Show me!
348 |
349 |
350 | 
351 |
352 |
353 |
354 | ## 6. Create a table for movies
355 |
356 | ✅ **Step 6a:** Switch back to first playground tab ("graphql-schema"; the token header will be already set).
357 |
358 |
359 |
360 | Click for screenshot
361 |
362 |
363 | 
364 |
365 |
366 |
367 | Use the following mutation to create a new table:
368 |
369 | ```yaml
370 | mutation createMoviesTable {
371 | movies_by_genre: createTable(
372 | keyspaceName:"netflix",
373 | tableName:"movies_by_genre",
374 | ifNotExists: true,
375 | partitionKeys: [
376 | { name: "genre", type: {basic: TEXT} }
377 | ]
378 | clusteringKeys: [
379 | { name: "year", type: {basic: INT}, order: "DESC" },
380 | { name: "title", type: {basic: TEXT}, order: "ASC" }
381 | ]
382 | values: [
383 | { name: "synopsis", type: {basic: TEXT} },
384 | { name: "duration", type: {basic: INT} },
385 | { name: "thumbnail", type: {basic: TEXT} }
386 | ]
387 | )
388 | }
389 | ```
390 |
391 |
392 |
393 | Show me!
394 |
395 |
396 | 
397 |
398 |
399 |
400 | ## 7. Insert a few movies
401 |
402 | ✅ **Step 7a:** Go to playground tab "graphql" again.
403 |
404 |
405 |
406 | Click for screenshot
407 |
408 |
409 | 
410 |
411 |
412 |
413 | Use the following mutation to populate the `movies_by_genre` table with four movies:
414 |
415 | ```yaml
416 | mutation insertMovies {
417 | inception: insertmovies_by_genre(
418 | value: {
419 | genre:"Sci-Fi",
420 | year:2010,
421 | title:"Inception",
422 | synopsis:"Cobb steals information from his targets by entering their dreams.",
423 | duration:121,
424 | thumbnail:"https://i.imgur.com/RPa4UdO.mp4"}) {
425 | value{title}
426 | }
427 |
428 | prometheus: insertmovies_by_genre(value: {
429 | genre:"Sci-Fi",
430 | year:2012,
431 | title:"Prometheus",
432 | synopsis:"After a clue to mankind's origins is discovered, explorers are sent to the darkest corner of the universe.",
433 | duration:134,
434 | thumbnail:"https://i.imgur.com/L8k6Bau.mp4"}) {
435 | value{title}
436 | }
437 |
438 | aliens: insertmovies_by_genre(value: {
439 | genre:"Sci-Fi",
440 | year:1986,
441 | title:"Aliens",
442 | synopsis:"Ellen Ripley is sent back to the planet LV-426 to establish contact with a terraforming colony.",
443 | duration:134,
444 | thumbnail:"https://i.imgur.com/QvkrnyZ.mp4"}) {
445 | value{title}
446 | }
447 |
448 | bladeRunner: insertmovies_by_genre(value: {
449 | genre:"Sci-Fi",
450 | year:1982,
451 | title:"Blade Runner",
452 | synopsis:"Young Blade Runner K's discovery of a long-buried secret leads him to track down former Blade Runner Rick Deckard.",
453 | duration:145,
454 | thumbnail:"https://i.imgur.com/xhhvmj1.mp4"}) {
455 | value{title}
456 | }
457 | }
458 | ```
459 |
460 |
461 |
462 | Show me!
463 |
464 |
465 | 
466 |
467 |
468 |
469 |
470 | ## 8. Retrieve movies: Pagination
471 |
472 | ✅ **Step 8a:** In GraphQL Playground, not changing playground tab (stay on the second tab, "graphql", yeah) list values from the table with the following command:
473 |
474 | ```yaml
475 | query getMovieAction {
476 | movies_by_genre (
477 | value: {genre:"Sci-Fi"},
478 | orderBy: [year_DESC]
479 | ) {
480 | values {
481 | year,
482 | title,
483 | duration,
484 | synopsis,
485 | thumbnail
486 | }
487 | }
488 | }
489 | ```
490 |
491 |
492 |
493 | Show me!
494 |
495 |
496 | 
497 |
498 |
499 |
500 |
501 | ✅ **Step 8b: Enable pagination:** On a small dataset, you can retrieve all values in the table at once; but in general, for performance or network reasons, you'll need pagination. Run a similar query as before, but this time asking for a _page size of 2_:
502 |
503 | ```yaml
504 | query getMovieActionPag1 {
505 | movies_by_genre (
506 | value: {genre:"Sci-Fi"},
507 | options: {pageSize: 2},
508 | orderBy: [year_DESC]
509 | ) {
510 | values {
511 | year,
512 | title,
513 | duration,
514 | synopsis,
515 | thumbnail
516 | }
517 | pageState
518 | }
519 | }
520 | ```
521 |
522 |
523 |
524 | Show me!
525 |
526 |
527 | 
528 |
529 |
530 |
531 |
532 | ✅ **Step 8c: Fetch the next page:**
533 |
534 | Notice that `pageState` now is also returned. Use it to fetch the next 2 items (next page):
535 | edit the next query to replace `YOUR_PAGE_STATE` with your own string value:
536 |
537 | ```yaml
538 | query getMovieActionNextPage {
539 | movies_by_genre (
540 | value: {genre:"Sci-Fi"},
541 | options: {pageSize: 2, pageState: "YOUR_PAGE_STATE"},
542 | orderBy: [year_DESC]
543 | ) {
544 | values {
545 | year,
546 | title,
547 | duration,
548 | synopsis,
549 | thumbnail
550 | }
551 | pageState
552 | }
553 | }
554 | ```
555 |
556 |
557 |
558 | Show me!
559 |
560 |
561 | 
562 |
563 |
564 |
565 |
566 | If you try to paste the _newly-obtained_ value for `pageState` and re-run the query, you get an empty list and a null `pageState` in return. D'oh! You had scrolled through all rows already:
567 | _this is how pagination signals the end of the full results list._
568 |
569 | # Part 2 - Build and Deploy Front-End
570 |
571 |
572 | ## 1. Deploy skeletal GUI to Netlify
573 |
574 | ✅ **Step 1a: Netlify Button:** Click the following button to deploy the skeletal GUI to Netlify. There is no data since there is no database connected to the app (yet); we will connect the database to the app shortly.
575 |
576 | **Note**: preferrably Ctrl-click for a new tab.
577 |
578 |
579 |
580 | What does the Netlify deploy button do?
581 |
582 |
583 | The Netlify deploy button will:
584 |
585 | - Create a new repository for you on Github (Note: it's an unrelated _copy_, not a fork)
586 | - Create a site on Netlify (and deploy a nonworking build of the app, which lacks the DB connection parameters still)
587 | - Link the two together.
588 |
589 |
590 |
591 | [](https://app.netlify.com/start/deploy?repository=https://github.com/datastaxdevs/workshop-graphql-netflix)
592 |
593 | Show me!
594 |
595 | 
596 |
597 |
598 |
599 | This will take a few minutes:
600 |
601 | - you may have to authenticate through Github in the process;
602 | - confirm the repo name and "Save & Deploy" when asked.
603 |
604 | _Note: if there is an existing account in Netlify, check the settings to make sure the Netlify account is connected to your Github account._
605 |
606 | Show me!
607 |
608 | 
609 |
610 |
611 |
612 | ✅ **Step 1b: Check the deploy logs:** Click on `Site deploy in progress` within the Netlify UI.
613 |
614 | Show me!
615 |
616 | 
617 |
618 |
619 |
620 | Then click the top deploy link to see the build process.
621 |
622 | Show me!
623 |
624 | 
625 |
626 |
627 |
628 | ✅ **Step 1c: Complete the build:** Wait until the build shows `Netlify Build Complete`, **When you see "_Pushing to repository..._"** you're ready to move on.
629 |
630 | Show me!
631 |
632 | 
633 |
634 |
635 |
636 | ✅ **Step 1d: Get back to your new site:** Scroll up to the top and click on the site name (it'll be after "_[your login]_'s Team" next to the Netlify button). Then locate your app's full URL and click to open it.
637 |
638 | Show me!
639 |
640 | 
641 |
642 | Clicking on the full URL (something like `https://YOUR-SITE-NAME.netlify.app`) you will see the skeletal GUI (without the data from the database) in a new tab. Here is where to click:
643 |
644 | 
645 |
646 | and here, finally, your skeletal GUI in its full splendour:
647 |
648 | 
649 |
650 |
651 |
652 | ## 2. Launch Gitpod from YOUR Github repo
653 |
654 | ✅ **Step 2a: Jump to YOUR repo:** Click on the `GitHub` in `Deploys from GitHub` to get to your new repository on Github.
655 | Scroll to where you were in the README.
656 |
657 | Show me!
658 |
659 | 
660 |
661 |
662 |
663 | > **Note** At this point, you MUST be reading this README from **YOUR** Github repository.
664 | > That is, if the address bar still says `https://github.com/datastaxdevs/...` please
665 | > head over to YOUR copy of the repo before going the Gitpod route!
666 |
667 | ✅ **Step 2b: Launch Gitpod:**
668 |
669 | Use this link to open Gitpod from **YOUR** repository! (_Tip: Ctrl-click on the button to open in new tab._)
670 |
671 | [](https://gitpod.io/from-referrer/)
672 |
673 | _Note: the button works on Chrome and Firefox._
674 |
675 |
676 | Click to troubleshoot if you have another browser
677 |
678 |
679 |
680 |
681 |
682 | ℹ️ _It may take a few minutes (approx. 3-5) for GitPod to fully initialize.
683 | Please wait until the console in the lower half of Gitpod is responsive._
684 |
685 | > You may see a dialog about "opening this workspace in VS Code Desktop": you can safely dismiss it.
686 |
687 | Gitpod will be your IDE from now on. If you are familiar with VSCode, you can probably
688 | just use it. Otherwise, take a moment to review a separate page
689 | ["Know your Gitpod"](know_your_gitpod.md)
690 | and then come back here.
691 |
692 | ## 3. Set up and use `astra-cli`
693 |
694 | You are going to use a CLI tool to simplify operations with Astra DB. The tool
695 | is [preinstalled](https://docs.datastax.com/en/astra-classic/docs/astra-cli/installation.html)
696 | on your Gitpod.
697 |
698 | ✅ **Step 3a: Set up the CLI:**
699 |
700 | Run the following in the Gitpod terminal and,
701 | when prompted, enter the `AstraCS:...` you obtained at the beginning.
702 |
703 | ``` bash
704 | astra setup
705 | ```
706 |
707 |
708 |
709 | Show me!
710 |
711 |
712 | 
713 |
714 |
715 |
716 |
717 | ✅ **Step 3b: Bulk data load:** Load a large movie dataset in the database.
718 | This command installs and properly launches the `DSBulk` tool ([docs](https://docs.datastax.com/en/dsbulk/docs/dsbulkAbout.html)):
719 |
720 | ``` bash
721 | astra db load workshops \
722 | -url data/movies_by_genre.csv \
723 | -k netflix \
724 | -t movies_by_genre
725 | ```
726 |
727 |
728 |
729 | Show me!
730 |
731 |
732 | 
733 |
734 |
735 |
736 | Show the syntax for old versions of astra-cli (click here)
737 |
738 | Note: you should not need this.
739 |
740 | ```bash
741 | astra db dsbulk workshops load \
742 | -url data/movies_by_genre.csv \
743 | -k netflix \
744 | -t movies_by_genre
745 | ```
746 |
747 |
748 |
749 | > *Note*: we mock the trailers for these thousands of movies by using a handful
750 | > of them over and over. Don't be surprised if you'll see the wrong trailers
751 | > for your favorite movie!
752 |
753 | That's it! All 6000+ movies are now loaded and ready to go!
754 |
755 | ## 4. Serverless Functions
756 |
757 | > _Note_: this section and the next one ("Fetching from the Front-End")
758 | > are not steps to "perform", rather suggestions to dive in the app
759 | > code and figure out how the various parts (React components, Netlify
760 | > functions and finally the GraphQL server in Astra DB) fit together.
761 | > **You can skip these and jump to the [next practical step](#6-install-the-netlify-cli)**
762 | > if you are in a hurry, but please come back to these
763 | > for reference if you want to dissect the code!
764 |
765 | Show me this section
766 |
767 | Take a look at `functions/getGenres.js`
768 |
769 | ```javascript
770 | const fetch = require('node-fetch')
771 |
772 | exports.handler = async function (event) {
773 |
774 | const body = JSON.parse(event.body)
775 | const url = process.env.ASTRA_DB_GRAPHQL_URL
776 | const query = `
777 | query getAllGenres {
778 | reference_list (
779 | value: { label: "genre"},
780 | options: {
781 | pageSize: ${JSON.stringify(body.pageSize)},
782 | pageState: ${JSON.stringify(body.pageState)}
783 | }
784 | ) {
785 | values {
786 | value
787 | }
788 | pageState
789 | }
790 | }
791 | `
792 |
793 | const response = await fetch(url, {
794 | method: 'POST',
795 | headers: {
796 | "Content-Type": "application/json",
797 | "x-cassandra-token": process.env.ASTRA_DB_APPLICATION_TOKEN
798 | },
799 | body: JSON.stringify({ query })
800 | })
801 |
802 | try {
803 | const responseBody = await response.json()
804 | return {
805 | statusCode: 200,
806 | body: JSON.stringify(responseBody)
807 | }
808 | } catch (e) {
809 | console.log(e)
810 | return {
811 | statusCode: 500,
812 | body: JSON.stringify(e)
813 | }
814 | }
815 | }
816 | ```
817 |
818 | You'll notice the familiar GraphQL query "getAllGenres" you used previously in the playground.
819 | It's been modified a bit to utilize pagination.
820 | When building the GraphQL query, you pass the desired page size and state to the GraphQL API:
821 |
822 | ```javascript
823 | options: {
824 | pageSize: ${JSON.stringify(body.pageSize)},
825 | pageState: ${JSON.stringify(body.pageState)}
826 | }
827 | ```
828 |
829 | You ask the API server to give us back the table column "value" (containing the genre name), but also the page state
830 | for when you'll need the next page:
831 |
832 | ```javascript
833 | {
834 | values {
835 | value
836 | }
837 | pageState
838 | }
839 | ```
840 |
841 | The serverless function `functions/getMovies.js` works in much the same way, but you provide the specific genre you want and the page size is hardcoded:
842 |
843 | ```javascript
844 | query {
845 | movies_by_genre (
846 | value: { genre: ${JSON.stringify(genre)}},
847 | orderBy: [year_DESC],
848 | options: { pageSize: 6, pageState: ${JSON.stringify(pageState)} }
849 | ) {
850 | values {
851 | year,
852 | title,
853 | duration,
854 | synopsis,
855 | thumbnail
856 | }
857 | pageState
858 | }
859 | }
860 | ```
861 |
862 |
863 |
864 | ## 5. Fetching from the Front-End
865 |
866 | Show me this section
867 |
868 | Take a look at how you fetch from these serverless functions from the front-end. Start in `src/App.js`
869 |
870 | There is a fetch method defined, that will retrieve a page of genres by calling the `getGenres` serverless function.
871 |
872 | ```javascript
873 | const fetchData = async () => {
874 | if (! isFetching) {
875 | setIsFetching(true)
876 | const response = await fetch("/.netlify/functions/getGenres", {
877 | method: "POST",
878 | body: JSON.stringify({pageState, pageSize}),
879 | })
880 | const responseBody = await response.json()
881 | setPageState(responseBody.data.reference_list.pageState)
882 | setGenres(gs => (gs || []).concat(responseBody.data.reference_list.values))
883 | setIsFetching(false)
884 | }
885 | }
886 | ```
887 |
888 | You pass in the current `pageState` and `pageSize` state variables and receive a response from the serverless function. You then set the `pageState` var to the new pagestate, and set the `genres` state variable to the received data. (Note that you are concatenating the new data to the var, since you want to keep all previously fetched data, not replace).
889 |
890 | When rendering the page, you generate a `` component for each genre, plus a `
` at the bottom, which will detect a `mouseEnter` event and trigger the loading of a new pageful of genres:
891 |
892 | ```javascript
893 | <>
894 |
895 |
896 | {genres && (
897 |