ALAN XIE: Hi, everyone.
My name's Alan Xie, and today I'm going to be giving
a CS50 seminar on Meteor and React.
We're also going to talk about a lot of other things
that are related to these topics.
But before I get started, I just wanted to link everyone
to a GitHub repository.
So if you go to this link and copy it down,
this is going to be the link to all of the source code
that we're going to be talking about in today's presentation.
So while everyone's getting set up with that,
let me just give you a brief explanation of what Meteor and React are,
and why they're really interesting to use,
especially for beginners, when it comes to building a web
application in JavaScript.
Meteor is a full stack framework that allows you to rapidly prototype
things straight out of the box because it takes so many different libraries
and other frameworks and links them together in a way that
allows the developer to focus more on building
the actual code of your application rather than the framework and structure
of the application template.
And what that means is, you might think a regular application,
if you were to build it, if you wanted to build in some kind of real time chat
communication or some other type of linkage between the client, which
is what a user would see, and the server, which
is where you run all of your operations, it would take a lot of effort
to do that in a different framework.
But in Meteor, a lot of these things are built straight out of the box.
React is another JavaScript framework and it's developed by Facebook.
And React is usable with a lot of other frameworks,
like Meteor, MEAN, other things you may have heard of.
And React is more of a paradigm than it is an actual library or framework.
And what that means is, React operates in a certain way
by abstracting the functionality in your application into individual components.
And so if you had an application where you
wanted to render some sort of information,
you would abstract that particular graph into one component
and that would simply be one module out of the other components
in your application.
When the data passed to that graph changes,
React is smart enough to know that that graph should re-render
or that component should re-render, and as a result
your application is reactive or responsive,
and is able to change itself given this type of new information.
So, hopefully everyone has gotten that GitHub repository ready.
If you look at the readme on that repository,
it will have instructions as to how to get set up.
And while people get set up, I'm also going to dive
a little deeper into what these technologies are and how they operate.
The final piece of the puzzle, I mentioned React and Meteor,
is going to be MongoDB.
MongoDB is a relatively new non-relational database.
And what that means is that it doesn't adhere to the normal row/column tabular
format that you would expect from a traditional database like SQL.
And it gives us a lot more robustness in being able to store our data,
because we can store data in a hierarchical manner.
We can have one field that then has subfields,
and then those subfields can have even more subfields.
And this would make a lot of sense for us
if we're trying to store data in a way that is hierarchical or otherwise makes
more logical sense to store in a non-relational format.
MongoDB might not be the best solution for you, however.
And there's also some other caveats you might
want to consider when it comes to Meteor and React.
The advantages, as I've mentioned, are that you
can build fast and deploy fast.
But there are a few caveats on the scale of these technologies.
The first thing is that Meteor as a framework
is usually overkill for what you might be trying to build.
What does that mean?
Well, I mentioned Meteor has all of these packages that come out of the box
and has all of this functionality built in.
If you want to build a real time chat application where
something that you say automatically gets sent to someone else,
there's a lot of linking behind the scenes that
needs to get done for that to happen.
And Meteor allows you to do that relatively easily.
But if you're just trying to build a blog,
or you're trying to build another static web page that just displays
some sort of data, then maybe Meteor is overkill
because you'll have too many packages running in the background
and you'll have a lot of inefficiencies when
it comes to deploying your application.
MongoDB might also not be right in the sense
that non-relational structures like MongoDB, which are hierarchical,
are always going to be more inefficient than the relational databases like SQL
when you're looking to make queries on large amounts of data.
And if you have millions and millions of rows of data about say, movies,
you might be more interested in trying to store that data in a SQL database.
So now that we've hopefully gotten an introduction to these technologies,
we're going to set up our actual code base that we downloaded from GitHub.
Luckily for us, Meteor automatically installs MongoDB.
And we can install React as a bunch of libraries
via the Meteor or node package managers at a later step.
We can also install other libraries like jQuery and d3,
which we can use directly in our Meteor application without much fuss.
This looks like a typical Meteor or node package manager command.
Don't actually run these, but this is just
an example of what you would type in to install a particular package.
So the first line, meteor npm install dash dash save react react-dom
would install via the node package manager,
npm, the node packages react and react-dom,
which are the base packages for react that we're going
to want to use in our application.
Meteor add is the syntax for using the Meteor package manager,
and all of the Meteor packages are stored
online on a website called Atmosphere.
And when we add Meteor packages, we identify the repository name--
in this case, mizzao-- and the actual package name, which in this case
is jquery-ui.
And if we type this into the command line
at the directory where our Meteor project exists,
we'll install this into our application.
The Meteor compiler will make sure that all of the linkages
are made appropriately and our application will rebuild itself
and automatically reload in the browser where we currently have it running.
And in this presentation, we'll be running it on localhost 3000.
So if you type that into your browser, it would be localhost:3000.
Well, we'll get there in a second.
If you've downloaded the application from GitHub,
you'll see a file directory tree that looks like this.
And at first, it's going to look very daunting
because if you wanted to build a very basic website,
all you would really need is one HTML file,
one JavaScript file if you're feeling adventurous,
maybe a CSS file to do some basic styling.
Here, we've got a lot of different directories
that span from client to server to imports
and it's very confusing because some of these even have the same name.
So we'll just go through very briefly and try
to figure out what's going on in each one of these folders.
At the root directory, we have a client folder
that's simply a boilerplate template for the basic HTML for what
your application will look like.
You're not actually going to write your application logic in there,
but that's what's going to be actually displayed initially in your browser
before you import additional components or additional JavaScript functionality
that lives in the startup folder.
And we'll see that the client will make a call to imports/startup/client, which
is a folder elsewhere in the tree, which has some startup logic that will run
on the client side of the application.
I mentioned earlier that there is a client side
and there is a server side when you create an application.
And what that means in a more granular level
is that there is some kind of logic that you want
to happen only on the side of the user.
So on the user's computer who is using your application.
But there's other logic that you might only
want to happen on your server side.
If you're running sensitive data or you're managing passwords
or you have API keys that are secret that you don't
want to reveal to anyone, those are things
you would want to operate on the server side.
And these are concepts that are going to be more useful to us
as we move forward.
We see here that there's also a folder called dump,
and this is simply a dump folder that's created by the mongodump command, which
I'll explain in a second.
But essentially, this is what we get when we back up a MongoDB database.
And Meteor as a web application runs with MongoDB built in.
And so when we want to back up our Meteor database,
we run mongodump in the command line, and we get a dump folder like this.
We can use this dump folder to then restore the database
when we run the application.
And that way in an instance like this, where we're sharing application source
code files but we're not actually sharing the database,
all the viewers at home can use mongorestore to restore
the database from this dump and make the application work.
The main bulk of your application is going to be in the imports folder.
The imports folder has several main components.
There's the api folder, the startup folder, and the ui folder.
What's most important to focus on are the startup folder and the ui folder.
The startup folder contains information that gets called by the client
and by the server upon startup.
So if you have information that needs to be figured out, such
as you'll see accountsconfig.js, which loads our account login management
system for this application, you would want to make sure
that lives in the client folder.
If you have security measures that you want to take place,
such as rate limiting the number of calls or requests
someone can make on your application, that
might live in your server folder, which has a security.js file.
The ui folder is the most important, because that's
where each of the React components that we create will live.
We have an app.jsx file in layouts, which
is going to be our main body template that
gets rendered into that main.html in the client folder that we saw at the top.
And then we have each one of these miniature components
which performs one specific function and is rendered inside
of the layout app.jsx.
So we have here actor_RevChart, which in this example
is going to be a graph that visualizes the historical revenue
of an actor, given that they're in our MongoDB database.
And that component is going to be rendered in our app.jsx,
and it's going to be rendered dynamically such that when
we get different information from the user about which actor
they want to visualize, we'll be able to get
new information into actor_RevChart, and it will change itself when we call it.
You might be confused why some of these files have a .js suffix,
and some of these files have a .jsx file extension.
And the reason for that is that when you use React components,
when you use React in general, you have to make sure that the file is .jsx.
And the reason for that is that React is a very special form of JavaScript that
allows you to dynamically render and create HTML
from within the JavaScript file.
And as a result, we need to specify that extension so that we know
that it's not just plain JavaScript.
Now let's get started with some actual work on making our application run.
Assuming that you've downloaded the zip from the GitHub repository,
one of the instructions on that repository
is to potentially download software called Robomongo.
Robomongo is a graphic user interface that
allows you to interact with the MongoDB database
instance that runs every time a Meteor application is created.
So what that means is when you download Robomongo, which is at this link,
you can have an application that looks like this
and that is able to show you in your database what types of information
that you have.
So you can see here in the dump that I've provided in the GitHub repository,
we're able to see that we have two entries in this database of people.
And in each one of these entries, we see that they have name, a profession,
and a list of films.
And so the actor Chris Evans, we have a list of films
that he's been in ranging from Captain America all the way to Snowpiercer.
And we also have here Anna Kendrick and a list of films
that she's been in, ranging from Pitch Perfect to Scott Pilgrim.
And we'll see that this information is going to be in our Meteor application,
and when we query our application to ask for a visualization of one
of these actors, this is the information that gets pulled in by Meteor
and then rendered in actor_RevChart.
So to set up Robomongo, what we want to do
is we want to download the application, and then
when you click New on Robomongo, you'll see
that you have the option to create a new connection to a new database.
Now I mentioned that Meteor runs MongoDB by itself,
and I mentioned earlier that Meteor runs on port 3000 of your localhost.
And what that means is your computer has a local server
that it can run applications on.
And it has a different number of ports through which that can run.
Meteor runs on localhost port 3000, and Meteor's version of Robomongo
runs on port 3001.
So you always will know when you run Meteor in the command line
that your database exists at this port.
So in Robomongo, what we can do is we can
create a new connection via this Create button,
and we can call it whatever we want.
And we want it to point at the address localhost 3001.
And we click Save and then we're able to see something that looks like this.
Pay attention to the sidebar where we see that we have a database
called Meteor, and we have a number of collections,
which are essentially sub-databases within Meteor
that are organized based on the type of information in each collection.
So we have one collection with people that we just saw,
with Chris Evans and Anna Kendrick.
We have one collection called Titles, which
has information about all of the movies that these individuals are in.
And we have another collection called Users, which actually
stores user account login information.
So what we can do if we follow the instructions on GitHub
after installing Meteor, either via OS X or Windows, what we need to do
is first change to the directory of cs50seminar-meteor-react, which
is the folder that you've downloaded.
And you want to copy and paste this set of code into your terminal.
And what this does is it installs all of the React-based dependencies
that you'll need for this particular repository to work
and for your Meteor application to have all of the functionality
that we want relating to React.
So as you can see, I typed it in here, I hit Enter,
and it installed this automatically.
And now that it's finished installing, what I can do is run Meteor.
And what you'll see happen on your computer
is that if you have Meteor open already on port 3000, it may give you an error
and say that you are running two copies of Meteor you're at the same time.
So you can specify Meteor to run on a different port simply with the command,
Meteor--port3005, to pick an arbitrary port number.
And note that if you do specify another arbitrary
port, that your database may end up living on that port number plus 1.
So here, if I specified port 3007, what actually happens
is my MongoDB instance is started by Meteor, and it runs,
it will run on port 3008.
So, running a Meteor application is as simple as typing in that command.
And once you've typed in that command, Meteor will in the terminal,
build all of the dependencies that are necessary,
including the new libraries that we installed previously,
and as soon as it is done successfully compiling your application
and linking all of the new libraries or packages that you may have installed,
it will say something down here that says, app running on localhost colon
the port number that your application is running on.
So while that's running, we'll look back at the GitHub instructions
and we'll see one final line.
We'll see here that we need to run mongorestore,
as I mentioned earlier, also in the command line in this same directory,
in order to make sure that our Meteor application has
all of the data associated with it when you downloaded the application.
On GitHub, unfortunately, the Meteor application that you downloaded
doesn't come with a fully populated database,
because a database is essentially something that has to run on a server.
And the information on each database needs
to be communicated back and forth between the individual people accessing
it, or the clients.
And as a result, the database must be saved in that dump directory
that I mentioned earlier.
And if we copy this command, mongorestore,
and we paste that into our terminal, we will be essentially restoring
to our database the dump file that had all of the titles, all of the people,
and all of the users associated with our application.
And you'll see that this command specifically restores it to port 3001.
Now as we wait for our application to start we can run this.
And if you've run this command twice, you
might get an error that says that your collection already exists.
Now we're assuming that you're approaching
this with a clean installation.
But there's always the potential that on your version of Meteor,
that you may have run this command twice.
And so what we want to do is we want to check in Robomongo whether or not
these collections exist.
And if they do, and we don't think that all of the information
has been added correctly, we can drop these collections,
which is the equivalent of deleting our database.
And we can re-restore these collections back to our database with this command.
And now we can see here that our application
is running at localhost 3005.
And as a result, we can modify this command
so that we restore to the database, which lives at 3005 plus 1, or 3006.
And now we see a lot of commands that have been executed,
which took all of the information from our database
and imported it to the database instance running on that port.
And now if we go to localhost 3005 where our application lives,
we'll see the Meteor application that we've downloaded.
Now, there's a couple of things that are interesting for us to note.
We can sign in with a default user login package that currently exists
and is built into the source code.
And you'll see that you can create an account
or try to sign in with an existing username and password.
The database that I gave you shouldn't have any users in it,
so what you want to do is create an account.
And if I create an account with the username alanx, password is test,
I can create an account.
It'll get mad at me if my password is too short.
And now I have an account that's authenticated with this application.
I can then sign out of my account and sign back in.
And you'll notice that when I'm signed in and when I'm signed out,
I see two different things.
And that's a feature that in Meteor, we simply
can check whether or not a user is authenticated.
And if they are, we can show them different things.
So in this version of the application we wanted to build,
we wanted to visualize information relating
to a particular actor or actress.
In particular, we wanted to visualize something
about Anna Kendrick or Chris Pine.
We'll see here that if we type in Anna Kendrick's name,
we get a graph of her revenue for all of the movies
that she's been in over the past six years.
If we type in Chris Evans, we'll see that the graph re-renders and we
can view his filmography over a slightly longer period of time
because he's made more movies over the past couple of decades.
And we'll see that this is a feature of that reactivity
that I was mentioning earlier where we can
pass new information into this component, which is the revenue chart.
And the revenue chart calls our database where we imported
all of that title actor data just now.
And then after calling our database, it receives some data
that then we visualize using the d3 library, which
is how we made this graph.
So I want to go back to the PowerPoint for a second.
And I want to draw your attention to some code snippets
that I've pulled out of the actual source code.
I've bracketed all of the important bits in a yellow rectangle
for your reference.
So what we're looking at here is actually HTML,
but it lives in a JavaScript X file in app.jsx.
And you'll remember what I mentioned earlier,
that JSX is versatile in that it can dynamically
render HTML using JavaScript.
So we'll notice that in the top of the first of the three yellow boxes,
will see that there is a special type of comment.
If you're familiar with commenting in other languages,
you'll notice that this is unlike the // commenting that you might get in C
or in JavaScript.
But here, in order to make sure that your comments register,
you have to wrap the /* commenting that you might see in HTML around side other
curly brackets in order for them to be properly formatted.
You'll also notice in the second box that we have
something called Accounts-UI wrapper.
And it's simply in these brackets, and there's a slash at the end.
And it's clearly not a normal HTML element.
It's not a div, it's not a span, it's not a paragraph.
So what is it?
An Accounts-UI wrapper is actually something
that we've defined in a separate file called Accounts-UI wrapper.
And when we define a React component, all we
have to do to call it or render it inside of our main layout
is to actually just call it the same way we would a normal HTML element.
So you'll notice that in this slide, the second box shows
a React component called actor_RevChart, and that's
the same acto_RevChart from the actor revenue chart component.
And all we have to do is simply call it the same way that we would
another HTML element.
So one thing that's interesting is if you
look at the comment on this page that's right above the first yellow rectangle,
we'll see another curly bracket.
We'll see something called this.state.readyToViz and a question
mark.
And the code here is actually a ternary condition
that we use to conditionally render a particular piece of our code, based on
whether or not certain criteria have been met
or whether a certain state has been achieved.
So we have here a state called readyToViz.
And what that means is, elsewhere in our code,
we've kept track of whether or not we've received the data from the MongoDB
database and whether or not we've formatted it in a particular way
such that we're ready to generate the graph that we saw in the application.
And if we don't have the data yet, we don't necessarily
want to generate a graph with no data.
And so what this says is, if this.state.readyToViz is true,
then we'll render everything after the question mark.
But if it's false, if you look at the bottom of the screen,
we'll render everything after the colon.
And here, we simply have a null string, which
means that if we are not readyToViz, then we simply render nothing.
But when we are, we render the graph.
And this is the beauty of React, in that when we change the state readyToViz,
React goes through all of these different types of conditions
and re-renders things that are dependent on this particular condition.
And as a result, we're able to get new data passed into this revenue chart
component.
And we see the graph magically update with new information.
Now one thing that's interesting to look at here
is after RevChart has a couple of what looked like input variables
going into the actual component.
And the way that this works is we have one input component
called data and another input property called actor.
And we can store variables in session variables,
which I'll explain in a second.
And we can pass the session variables into this component
from this parent component in app.jsx.
And we will then render the revenue chart based on the different input
property that it receives.
So, you can imagine that elsewhere in our code
we have a function that prepares the data by getting it from MongoDB
and then formatting it in a particular way.
And when we do that, we can store that information
in a session variable, which we can then reference from inside of this call
to the component via those curly brackets.
We can also store information in a session variable
about who is the actual person that we're visualizing.
And so these are information that we pass into this child
component via what are called props.
And props, or properties are essentially things
that we can change based on what new information or new state
has been achieved elsewhere outside of this child component.
One other thing I wanted to bring to your attention about how React works
is that React has a series of special functions in each component that
are run and that relate to the lifecycle of a React component.
We talked about state and we talked about props,
but what we also want to talk about is actually the mounting and updating
of the component itself.
And if you look at the GitHub readme, on that page
there is a link to the React life cycle where
you can look at this information in a little bit more detail.
But essentially, there is a function that's
defined for every single React component called componentWillMount.
And what that does is it allows you to run some sort of logic on your web app
before the actual React component is mounted onto the page.
And here what we want to do is we want to run something called Meteor.call.
I'll get to that in a second.
But you'll see that just as we have componentWillMount,
We also have component-did-mount.
And component-did-mount is just as self-explanatory.
It's a chunk of code that runs after your component has
been mounted to the dom.
And we also have additional functions such as shouldComponentUpdate,
componentDidUpdate, and so on.
And so at each stage of the React lifecycle,
we're able to run certain logic for our component should we need it.
Now, going back to Meteor.call, this is a very interesting part
of the Meteor framework in that we are able to define
things called methods that live on the server side of our application.
And you'll recall what I mentioned about the distinction between client,
which is what the user sees, and server, which is what your application runs.
And on the server, we can have a function called get_names,
and we can call it via Meteor.call and the name of the function
that we've defined.
And the server function looks like this.
It's in a file under imports/startup/server/films.js.
And here we're able to define in something called Meteor.methods,
a function called get_names that simply takes
in no arguments but loads in a local CSV file of names,
presumably actor names, that are in our local project directory.
And by running Meteor.call get_names, what we're able to do
is we're able to conceal all of the logic in this function get_names
onto a server method that the client will never see or be able to access.
And so you can imagine if we wanted to do some secret stuff here,
we could and no one would be able to know.
And our intellectual property or our sensitive information
or our secret keys would be saved here without anyone being able to see them.
And after we call get_names, we have what's
called a callback, which is when we call a function on the server side,
it's not going to return a result instantly.
And so we can't expect code that happens underneath Meteor.call to happen
before the return result of get_names.
And so we can pass another argument into Meteor.call,
which is simply an anonymous function that has two arguments-- error and res.
And what that means is that if Meteor.call fails and get_names somehow
does not succeed, the result of that error will be returned in error.
If Meteor.call succeeds, the result of that will be returned in res,
or result.
And so we'll see here that when we get the list of names from Meteor.call,
we process it via res.
And then we actually store it via session variable.
And that allows us to access it in other components
or to pass it into other components as props.
Now the final piece of the puzzle that I want to draw your attention to
is the actual actor_RevChart file itself.
So this particular component, as you saw on the previous slide with app.jsx,
is rendered in that layout and it's given
a set of props that allow it to determine what particular information
it should visualize.
So here we have the data prop and the actor prop.
And these are both props that are required by the component,
and they're even specified as to the right type of prop.
So if someone tries to pass into this component
an actor prop that's not a string, this component will get mad at you
and it will fail to render.
Now we'll see that there are a couple of specific functions
that are defined for actor_RevChart on top
of the ones that are defined elsewhere that we've seen in the React lifecycle.
ComponentDidMount we've seen before, and that's standard to the React lifecycle.
ShouldComponentUpdate is something I've mentioned before that's also
specific to the React lifecycle.
But we have two of our own functions here called drawChart and updateChart.
And these are defined here as a means of drawing the actual graph that
is this component.
And the way that we draw this graph is using something
called d3, which is a JavaScript library that
allows you to create graphs and versatile visualizations that
are interactive and can also be dynamically changed and be reactive,
depending on the data that you pass in.
So here what we're doing in the logic of these functions
is initially we draw the chart when the component is mounted
using the props that we are initially given about who the actor is
and what their name is.
Then, shouldComponentUpdate when we get new props,
we have to compare the new props to the old props
to make sure that we're getting a different actor.
And if we are, we call updateChart, which then redraws and transitions
the original d3 graph into a new d3 graph.
And we'll see a lot of the logic in here,
which I've minimized for your clarity is going to be d3-specific graphing logic.
So if you're interested in d3 as a visualization library
to generate your own graphs, you can be very hands-on with it.
And the code that you would use would be something that
looks like the code in those functions.
So now that we've gone over all of the individual major components
of this application, let's go back to the application just for a second
and try to abstract some of those concepts
that we talked about into the actual execution of the application.
So if we want to graph Anna Kendrick, what we have here is
the user has some input that they've passed in.
And in this app.jsx, we get that information and we
pass that information into actor_RevChart,
which is now conditionally rendered here based on
whether or not it received the correct props, which it did.
And you can see that if we type in something else
for an actor that doesn't exist in our database, like Leonardo DiCaprio,
we're not going to get anything.
In fact, we're going to get an error on the back end
because this query doesn't correspond to any people
that our database recognizes.
But if we type in Chris Evans, then we're
able to once again regenerate the graph and have a new visualization be
reactive to the user's response.
So before I wrap up, I want to talk about some of the additional things
that you might be able to do with these technologies.
As I mentioned at the beginning, Meteor and React
are very useful because of the incredible amount of developer support
behind these communities, and also because they're
so easy to use out of the box relative to other JavaScript full stack
and framework solutions where you might have to do a lot of extra linking
or a lot of extra work to make a lot of these functions happen by themselves.
What you can do on top of Meteor and React
is to build additional functionality exclusive to what you're actually
trying to work on.
And for me, that came in the process of taking that application that we saw,
the demo that we saw, and actually building out a software as a service
platform for film studios and investors to use
as a means of trying to better gauge their investments on entertainment
properties.
And the way that I was able to do that was
to build on that particular demo, which is actually the earliest
version of the product that I worked on, and to then
extend it with a number of other technologies
that you can use side by side with Meteor, React, d3,
MongoDB, all the stuff that we've talked about today.
So Jupyter, for one, is a very, very strong and powerful
scientific computing and data analysis version of Python
where there is a lot of libraries that come bundled
into the Jupyter such as numpy, scipy, pandas, scikit-learn, that
allow your average user to, with very limited experience,
begin to do very, very sophisticated and very high-level data processing
and machine learning in Python.
Now if you were to analyze movie data, thousands and thousands
of different movies and the actors involved and the revenues involved,
and you performed some sort of machine learning
in Jupyter, what you could do next is actually
try to take that machine learning and abstract that into a service
where you could construct a Flask API, Flask being a mini-framework that
is built in Python.
And you can take all of the machine learning that you did in Jupyter
and you can serve that as an API via Flask.
And if you remember what I mentioned earlier about Meteor.methods
where we can call APIs and we can have server side logic that the user doesn't
see, you can have a Meteor application send requests to a Flask API.
And at a high level, what you can do is have a user submit certain data
and then perform sophisticated machine learning via the Flask API
that then returns data back to Meteor for the user to see
and for the user to get via a visualization.
The final piece of the puzzle that glues all of this
together in a production environment is AWS, or Amazon Web Services,
among other web solutions that you might use.
For my project development, I was able to use
AWS to link Flask and Meteor and MongoDB and to appropriately install
all of the servers I deployed on AWS with all of the libraries
that I needed.
And then I had the Meteor application in React
in MongoDB communicating with Flask.
And so if we go back to that very early first version of the application,
something that looked like this.
If you take this technology, Meteor and React,
and you build on it over the course of several months,
there's so much out of the box that saves you
time that within months, what you can go from is this all the way to something
that's much more robust and sophisticated like this.
And so that's something that you can accomplish
using this suite of technologies.
And I hope that my talk today was informative and useful.
And again, if you have any questions, please
feel free to mark them on the GitHub repository or send them my way as well.
I'm a teaching fellow on the website for CS50.
So I hope everyone had a great time and that they learned something new.