Loading video player...
Learn how to build a complete full stack
movie streaming app with AI powered
movie recommendations. You'll use Go
with the Jin Ganic framework on the back
end, React on the front end, and MongoDB
for data storage. For the AI features,
you'll connect your Go backend to OpenAI
using Langchain Go. Along the way,
you'll set up authentication with secure
token handling, making sure access
tokens are stored in HTTPON cookies to
protect against cross-sight scripting
attacks. You'll also learn best
practices for securing client server
communication over HTTPS.
Once the app is built locally, you'll
deploy it to the cloud with MongoDB
Atlas, render for the Go API, and
Verscell for the React client. By the
end, you'll gain experience not just in
Go, MongoDB, and React, but also in
cyber security, AI integration, and
cloud deployment. Gavin lawn developed
this course and MongoDB provided a grant
to make this course possible. Hi
everyone and welcome. I'm Gavin Lawn.
I'm super excited to bring you this
course where we'll build a full stack
web application stepby step using
cuttingedge technologies. We are
developing this application for a
fictional company named Magic Stream
that provides
Why'd you do it?
I didn't.
A movie streaming service as well as an
AI powered personalized movie
recommendation service that leverages a
technology called lang chain go for
interfacing from our go or golang code
with open AI. We are going to use React
on the front end to leverage high
performance front-end user interaction
functionality so as to provide our users
with a superb UX user experience. We'll
create the serverside code for a web API
component using Go also known as Golang.
Go or Golang is a statically typed
compiled programming language created by
Google. It was designed with simplicity,
efficiency, and scalability in mind,
making it well suited for modern systems
programming, cloudnative applications,
and large-scale distributed systems. I
have to say, after working with Go
lately, it has become one of my favorite
programming languages. You'll see it's
just great to work with. It's a great
modern, simple, yet powerful language to
learn, and it also pays really well. For
our data storage facility, we'll use the
awesome MongoDB, a NoSQL
document-oriented database designed to
store, query, and manage large volumes
of data in a flexible and scalable way.
MongoDB is optimized for fast read and
write operations, especially useful for
real-time applications. As mentioned
earlier, for creating a super responsive
front end, we'll use React in order to
provide our users with an excellent UX
user experience. React is a JavaScript
library, not a full framework, used for
building user interfaces, especially
single page applications, SPARS, where
the UI needs to update efficiently in
response to user actions or data
changes. It was originally developed by
Facebook and is now open source and
widely adopted. So this course has a bit
of everything. Learn a modern
cuttingedge programming language like
Go. Create a full stack distributed web
application. Learn important aspects of
cyber security. Learn how to integrate
AI into your applications. And then at
the end of the course when we deploy our
app to the cloud, we engage in aspects
of DevOps. Once we've developed our
application, we'll deploy the serverside
web API component written in Go to
Render, a modern cloud hosting platform
that provides developers with a way to
easily deploy and scale web
applications. We'll deploy the React
client code to Versel, a cloud platform
optimized for front-end development. And
we'll deploy our MongoDB database to
Atlas. Atlas is MongoDB's fully managed
cloudnative database service, sometimes
described as database as a service. So
this web application is a great example
of the implementation of a loosely
coupled architecture where functional
entities on the web, the serverside
component written in Go and the client
written using React interact via HTTP as
one full stack web application. We'll
also include cyber security features
like token authentication over HTTPS to
protect our data. So, as mentioned
earlier, I'm Gavin Lon. I've been
developing software professionally for
decades at this point and love sharing
my knowledge. I run my own YouTube
channel where I teach programming and
discuss technology in general. I'm proud
to say that I've created several courses
for free code camp, most of them
dedicated to teaching programming. But I
must confess that I went to the dark
side last year and taught a course on
video editing using Da Vinci Resolve. So
please check out that course if you want
to add video editing to your skill set.
Okay, so enough about me. Let's discuss
what this course is all about.
I've created a fictional company called
Magic Stream. We are going to develop a
movie streaming service for Magic Stream
in the form of a full stack web
application that provides a special
feature that uses AI to recommend the
top five movies tailored to the user's
specific tastes. This recommendation
service is based on the users's movie
genre preferences and the sentiment
derived from movie reviews created by
the administrators of our system. So the
workflow here is an administrator logs
onto the system and creates a movie
review in natural language for a movie
offered for streaming through magic
stream. When the administrator submits
the movie review, a custom prompt that
we'll create is submitted along with the
movie review to OpenAI via a technology
called Langchain Go. The relevant LLM
large language model returns a response
in one word describing the sentiment
behind the movie review. Our prompt
includes instructions to only return one
word describing the sentiment behind the
movie review. For example, the sentiment
could be excellent, good, okay, bad, or
terrible. This sentiment described in
one word is then saved to our database
along with a unique numeric value
associated with each sentiment. You can
see how it is easy for our system to
include a value for each of these
sentiments. So for example, excellent
can have a value of one, good, two,
okay, three, and so on. The last one,
terrible, has an associated value of
five. We can then query our MongoDB
database for movies and order them by
their values that denote the sentiment
for each of the movies stored in our
database. It is important to note that
these sentiments are extensible. We are
going to architect the application so
that we can extend the number of
possible sentiments associated with
movie reviews. We could later include a
wide spectrum of sentiments and
associated values. For example, you
could include the word unwatchable below
terrible where terrible has an
associated value of five and unwatchable
has an associated value of six. So these
values can be used as a ranking order
for the relevant recommended movies.
This course has pretty much got
everything. I mean we are also going to
include cyber security features like
tokenbased authentication over HTTPS.
I'll firstly show you how the access
token is generated once the user
authenticates with the system i.e by
logging on with the user's credentials.
Then for subsequent HTTP requests the
token is passed through the header of
the relevant HTTP messages so that the
user can be authenticated before
accessing protected endpoints.
But it doesn't stop there. By passing
the token through the header, our
application is still vulnerable to XSS
attacks, cross-sight scripting attacks,
where malicious JavaScript code can
potentially be injected into the client
side and read the access token. An
access token is like a key that can be
used to unlock protected resources. So,
we must protect the key. How can we do
that? I'll show you how you can avoid
storing the tokens on the client side
and passing it through the header by
instead storing the access token as HTTP
only cookies which means the tokens
cannot be read through JavaScript code
and therefore mitigates the risk of the
access token being stolen. I'll show you
how to do that in detail in this course.
So these are very important cyber
security related practices to learn to
leverage Go for our web API component.
We are going to use a web framework
called Genonic. Genonic sometimes
referred to as Jyn is a high performance
HTTP web framework for go Golang.
Gingonic is lightweight and fast,
minimalistic but powerful. Jinggonic. I
know it sounds a bit like jin and tonic.
We're not going to be vibe coding in
this course. Oh, sorry. Whiskers.
I feel it's important to learn to code
at least at the beginning without the
use of AI assistance. So it gives you a
tactile and deep sense of the
development process and building up the
code step by step without AI assistance
in my view is the best way to understand
programming and associated technologies.
So we are going to build this full stack
web application step by step and we are
going to have a great time doing it. So
as I said at the beginning this course
has a bit of everything. Learn a modern
cutting edge programming language like
Go. Create a full stack distributed web
application. Learn important aspects of
cyber security. Learn how to integrate
AI into your applications. And then at
the end of the course when we deploy our
app to the cloud, we dabble with DevOps.
Dabble with DevOps.
We engage with aspects of DevOps. So we
are going to have a great time
developing and deploying our full stack
web application. Right, let's start from
the beginning which involves setting up
our local development machines with the
technologies we'll use to develop and
test the application. Let's get into it.
Right. So to install MongoDB, we need to
navigate to this URL. So let's go
through Chrome. I'm going to use Chrome
as my browser and I'm going to navigate
to this URL.
www.mongodb.com/try/d
download/ community then press the enter
key and you can see the website has
appropriately detected my platform
settings. So we have the platform
setting for Windows x64 which is the
platform I'm using uh which I'll be
using to develop the application for
this course. You have a choice between
MSI and Zip here, but we want the MSI
package. And this is the appropriate
platform for us. But we have a list of
various other platforms. We have Mac
platform and we have various
distributions of Linux available to us
here. But as stated, we want the Windows
x64 platform selected here. So the next
thing we do is just hit the download
button to download the relevant MSI
file. Once it's downloaded, we'll double
click on the relevant MSI file and
run the relevant installation package.
You can see we've got an indication as
to how much longer it will take to
download the full MSI package. So, we
just need to be a little bit patient
here. 2 minutes left. It says not too
long. Let's fast forward. And please
note that this is the community edition
of MongoDB server and it's absolutely
free to use on our local machines. All
right, not long now. 9 seconds, 8
seconds, 7 seconds, 6 5 4 3 2 1. Okay,
great. And now we should have the folder
icon displayed to us. Here it is. And we
can just navigate to our downloads
folder using that there. So to install
MongoDB on our local machines, we simply
doubleclick this file here which has
themsi extension. Excellent. The setup
wizard will install MongoDB 8.0.11
blah blah blah blah blah. Click next
button. Excellent. And then let's accept
the licensing agreement by selecting
this checkbox here. And then let's press
the next button. And we are recommended
to do a complete install here. So let's
hit the complete button. And on this
page, I think we can just leave
everything as is. So let's press the
next button to continue. Now this is a
very important checkbox here. Let's keep
this checked because this will install
automatically a
guey interface for MongoDB. So we'll be
able to use this guey interface to
manipulate our data, create databases,
collections, documents within
collections, etc. and it's a really
great graphical user interface that we
can utilize. Great. So, let's press the
next button here. And then the last step
is to press the install button. I'm not
going to press the install button
because I've already installed uh
MongoDB on my local machine. So, I'm
just going to cancel out of this. But
please go ahead and press the install
button and go through with the
installation process. So, the next step
is we want to verify that we have indeed
installed MongoDB successfully on our
local machine. So to do that we can do
it through the command prompt like this.
So
d-
version like this.
Excellent.
So we've got version 8.0.8 which is the
target version that we installed on our
Windows platform. Excellent.
Then in order to be able to interact
with MongoDB with the MongoDB database
on your local machines, you can run
MongoDB commands like for example query
commands from your command line using
the MongoDB shell. This can be handy for
test purposes. Please note that the
installation of the MongoDB shell
requires a separate installation to the
one for installing MongoDB, i.e. the
installation we have just completed and
verified. So to install MongoDB shell,
it's www.mongodb.com
slashtry
slashd download
and then the word shell slshell like
this. Press the enter key. And you can
see now that the actual website itself
has detected my particular platform,
Windows x64. So, I've got Windows x64
version 10 plus. So, I'm actually on
version 11 of the Windows 6 x64
operating system, which is correct. And
then here, we can download the relevant
MSI file. And then we can just run that
MSI file from our downloads folder by
double clicking on the relevant file and
going through the relevant installation
instructions. Then to verify that
MongoDB shell has actually been
installed and I have actually gone
through the installation process. So
I've got MongoDB shell installed. You
can verify this by typing MongoDB.
So sorry it's not MongoDB, it's Mongsh
version like this
2.5.0. So, I've got the appropriate
version of MongoDB shell
installed on this local machine, and we
verified it with this command.
Excellent. Uh, sometimes it won't
automatically configure your path
environment variable, which can lead to
problems. So to verify that our path
environment variable has indeed been
configured correctly, we can go to this
utility
cis
dm dot
cpl.
Press the enter key. Then we can go to
this tab, the advanced tab, and press
the environment variables button here.
And let's look inside the system
variables list here and navigate to the
path environment variable. Hit the edit
button and let's see if we can find the
MongoDB environment variable that we
want. Sorry, well the path environment
variable and make sure that MongoDB is
appropriately set up within the path
environment variables. And here it is. C
program files MongoDB server 8.0 bin.
And if we copy that to our clipboards
and we press Windows key R here and use
Windows Explorer, we can navigate to
that path. And we can see here that the
relevant files have been appropriately
put into that directory structure and we
are appropriately pointing to that
directory from within the path
environment variable. So that's
excellent. Great. And then the other
verification we should do is check that
MongoDB is actually running in the
background. And we can do that by
running our services. So Windows keyr
type services.msc
like this
and then type select the list and type
mo. And there we are. We can see our
MongoDB server is running in the
background here. And we can use this
facility to stop or start start our
server. But we don't want to stop our
server. We want to keep MongoDB server
running in the background. The next
thing we want to do is launch Compass.
So we can see the graphical user
interface that ships with this
installation of MongoDB.
And then let's press on the connect
button here next to main connection
here.
Excellent. As you can see, I've been
playing around with a number of MongoDB
databases, and this is essentially the
structure we're about to create. So,
we're going to create a new database
next to the main connection node here.
Let's press the plus button, and then
let's
give our database a sensible name. So,
let's call this magic stream movies
like this. And our first collection will
be named movies. So let's enter it here.
Collection name. And then we just press
the create database button to create our
database. Excellent. So I want to create
three other collections within this
database. So let's hit this plus icon
here to create a new collection. And
let's name this collection users. This
will store information regarding the
users of the system. I'm going to import
three data for three users initially so
that we can uh test our application as
we develop it using that data initially
and then we'll also create a
registration facility where we can add
further users but we'll get to importing
the relevant seed data in just a bit. So
let's create a new collection. Let's
call this one genres
like this.
Let's hit the create collection button
here. Excellent. And we've got one last
collection that we need to create. So,
let's hit the plus button here. And this
collection's name is rankings.
And we'll see the data for the rankings
collection in just a bit. But let's
first import the data for movies. Now to
import the seed data you can firstly
download the relevant data from my
GitHub repository. So let's go to my
GitHub repository here. And if we go to
Magic Stream
and this folder, Magic Stream seed data,
you can see a number of JSON files here
that I recommend you download to your
local machine and then you can import
the relevant data into your collection
through a utility within Compass. And
I'll show you how to do that right now.
So, if we go back to Compass, let's
import the data for our movies
collection. And to do that, we hit the
add data button here. Import JSON or CSV
file. Let's select that option and then
doubleclick movies.json here and then
hit the import button. And you can see
I've prepared offcreen 15 data for 15
movies and we've just imported them into
our movies collection here. You got Jack
Reacher, Blitz, Star Trek, The
Undiscovered Country, Star Wars, Empire
Strikes Back. What a classic. And we've
got a few classic movies here. The Good,
the Bad, and the Ugly. Classic from the
' 60s. Unforgiven. Awesome movie. Great.
And then I was talking about the
rankings. Now, we're going to create a
facility that interacts with AI with
OpenAI and it's going to extract
sentiment from movie reviews written in
natural language. You can see here
within each document, each movie has a
movie review.
Um, so for example, this movie is awful.
The AI will extract the sentiment just
in one word, terrible, and the ranking
value makes that sentiment quantifiable.
So, we're taking a movie review written
in natural language and we're
quantifying its sentiment. And you'll
see how useful that can be later on when
we develop that part of the application
relevant to the AI functionality.
Excellent.
And so, let's import the data for the
rankings collection. And it's the same
procedure. We go import JSON like this.
And double click on the rankings.json
file and hit the import button here. And
there we have it. And you can see the AI
will select from the admin review
written in natural language one of these
options, excellent, good, okay, bad or
terrible. And in doing so, the AI is
taking qualitative data written in
natural language and turning it
essentially into quant a quantifiable
value which is represented by each of
these sentiments. So, we've got
excellent, good, okay, bad, and terrible
to choose from here.
Right, let's import the genres. And it's
the same procedure. You click this
dropown, go import JSON or CSV file, and
then double click on genres.json
and click the import button here.
Excellent. And lastly, we have the
users. Let's import the data for the
users collection like this.
Let's hit the import button.
And we've now imported three users, one
of which is an administrator. So Bob
Jones is the only one because he's part
of the admin role. He's the only one who
can actually add reviews to movies.
Okay. So the next thing I'd like to
demonstrate is how we can use the
command prompt to interact with our
MongoDB database. Before we test the
MongoDB shell, I just want to clarify
that this is just one way of interacting
with MongoDB. In fact, in this course,
we are going to use a specific MongoDB
for Go driver from our Go or Golang code
in order to interact with our MongoDB
database. Using the MongoDB shell can be
a convenient way of interacting with our
MongoDB database. For example, during an
appropriate testing phase of your
application or perhaps to diagnose
potential database related issues during
the development phase of your
applications. As stated in this course,
we are going to interact mostly with our
MongoDB database in code via the MongoDB
for Go driver. We'll look at this in
detail a little bit later. So let's fire
up the command prompt like this and then
we need to go into the MongoDB mode if
you like and we can do that with this
command
mongod.
Press the enter key and as you can see
we are automatically
in a database named test here. So it
actually created this database by
default when we went through the
installation process.
So let's create let's run a command that
creates a collection within the test
database. And we can do that with this
command db.create
create collection
and let's name our collection customers
like this and press the enter key. Okay,
one. So that looks good. It should have
created our customers collection and we
can verify that by going into compass
here. Let's go into our test database
customers and there it is. There's our
collection
and let's create let's first clear the
screen and let's create a document
within our customers collection and we
can do that with this command dbc
customers doinsert
one and then we can include the relevant
JSON object like this and we're going to
add a customer named Bob Jones. So let's
give this field
the name name like this. And then let's
include the field value like this within
quotation marks. Bob
Jones.
And the email address email
is Bob
Joneshotmail.com.
Let's press the enter key. Excellent.
So, we've added a document to our
customer's collection. The document is
denoted by the customer, Bob Jones. And
let's verify that Bob Jones has been
added to the customer's collection. And
there's the document for Bob Jones. And
you can see it's added a unique
identifier for this particular document.
And let's for good measure, let's add
another customer to our customers
collection. This time, let's add a
customer called
Sally James.
Let's give Sally a sensible
email address.
Hotmail. Whoops. Should be at
atotmail.com.
And let's press the enter key.
Excellent.
And let's verify that Sally James has
been added to the customer's collection.
And there it is, Sally James. Brilliant.
And then if we want to display, if we
want to query for all of the documents
within our customers collection, we can
run this command. DB.Customers
customers.find
open close brackets press the enter key
and there we have the two documents that
we've just added to the customers
collection displayed to us. Brilliant.
Let's clear the screen
um and let's actually go into the the uh
and let's actually go into our magic
stream movies database. So to do that we
type use magic
stream
movies like this. So we've switched to
Magic Stream movies and then we want to
return all the movies that are saved to
our movies collection. So to do that we
type db dot moviesov
dotfind open and close brackets. Press
the enter key. And there we have our 15
movies. Brilliant.
Excellent. And let's say we wanted to
just return one of those movies. Let's
clear the screen first. We can do that
with this command. DB
dot movies oops movies
dotfind
one and then each movie has a field
named IMDb ID. So you probably know this
but on the internet each movie has a
unique identifier. It's IMDb number. So
I'm using that within our database to
identify them to uniquely identify our
movies. So let's say we wanted to find
the movie called Once Upon a Time in
Hollywood. So we can do that through its
IMDb number or IMDb ID. And to do that
in code, we pass in a JSON object to the
find one method like this. So this field
is called imb_id.
So we don't need the quotations here,
but the field is called IMDb_ID
colon. And then we want to include the
value for the IMDb ID field. TT 713
6 22. And then let's press the enter
key. Excellent. Once upon a time in
Hollywood. The document for Once Upon a
Time in Hollywood is displayed to us.
And our command has executed.
successfully.
Great.
Right. So to install Go on our local
machines, let's navigate
to this URL. So it's https
colon slash slashgo.dev.
It's an easy URL to remember.
Excellent.
And then let's press the get started
button here.
And then let's press the download button
here.
And you can see here we have a number of
installations to choose from. And it all
depends on what platform you are
running. I'm running a Windows platform.
So Windows 10 or later. This will be
compatible with Windows 10 or later. I'm
on Windows 11. So, I'm going to
click on this link here to download the
relevant MSI package.
And then we've of course been through
this before. We got our indicator here
and it's downloading that pretty
quickly. So, we don't have to fast
forward anything. And we're on we've
only got say a few seconds left to go.
That's looking good. Oh,
a bit of a hiccup.
Right, let's fast forward.
And here we go. A few more seconds. Six.
Four. Three. Two. No.
It's another Okay. 4 seconds. 6 seconds.
9 seconds. 8 seconds.
[Music]
Guess my internet's not particularly
good today, but we are getting there.
Nearly there.
Excellent. Done. So now all we need to
do is click on the folder icon here to
go to our downloads folder. And here's
our MSI package. And then to install go
simply double click on the MSI package
and follow the instructions provided to
us in the wizard.
Please wait while setup wizard prepares
to guide you blah blah blah. So the next
button isn't yet enabled. So we can't
click on that just yet. So let's just be
patient here.
Migrating feature states from related
applications. It's doing things behind
the scenes.
Welcome to the Go programming language
AMD 64 Go 1.24.4
setup wizard. The setup wizard will
install Go programming language AMD 64
Go 1.24.4 on your computer. Click next
to continue or cancel to exit the setup
wizard. Let's press next and install
Go. So, what's happening there is it's
detected that I've already got Go on my
machine. So, I'm actually going to exit,
but please go through the instructions,
go through the installation process to
install Go. Right. So, I'm just going to
go no exit here and then finish. And
just going to minimize this. Right. And
then just to verify that I've got Go
installed successfully on my local
machine, I'm going to go into the
command line here and type Go version.
Excellent. So 1.24.2 Windows AMD 64.
That's excellent. So I've got Go
successfully installed on my local
machine. Um just like we did when we
installed MongoDB, we should verify that
Go is appropriately configured within
our path environment variable. So to do
that, we do the same procedure. We type
CIS
DM
CPL here
and we go to advanced and to get to our
environment variables, we hit the
environment variables button here.
So within our system variables, let's
find the path
environment variable settings here.
Press edit and let's see if we can find
go
within the paths configured here. And
here we go. Program files go.bin. And we
can actually copy that to our
clipboards. Go
Windows keyr and navigate
to that directory. And we can see we got
go.exe exe there go FMT.exe. So we've
got the relevant executables in our Go
directory and that has been successfully
configured with for within our path
environment variable. So that's great.
Okay. So let's go out of this here
and the next thing we can do is let's go
to the command line.
I'm actually going to navigate to the C
drive
because we have configured go correctly
within our path environment variable. We
should be able to run our Go commands
from any
directory. So, we're in the C drive here
and let's create our first Go
application. So, to do that, I'm first
going to create a folder. So, I'm just
going to go mk dur and create a folder
called go first
app like this. Press the enter key and
then I'm going to navigate into that
folder that we've just created. Go first
app.
Excellent. And then I'm going to launch
notepad because for this first
application, we're just going to use
Notepad. It's going to be a very simple
traditional Hello World application. So,
we don't need a particularly
sophisticated editor for that. But
when we get down to creating the web API
that we're going to create with Go and
Jenonic, the Genonic web framework,
we'll use Visual Studio code for that.
But for now, let's just use Notepad and
create a basic hello world application.
So, let's initialize our Go application
with this command. go mod init
my app like that. Press the enter key
and then let's launch notepad. So we can
just type notepad. If you've got notepad
appropriately, it's set up. You should
be able to just type notepad to launch
notepad.
And let's save this file
as
main.go. Go.
It's going to go to my C directory.
Go first app. There it is. And then I'm
going to create a file called main.go
here.
Set that to all files so that it doesn't
mess with our extension here. So main.go
is going to be where we write our code.
And let's save that like that. So
firstly we want to designate this go
file as part of a package called main.
So to do that we type package main like
that. Press the enter key. Then we want
to import a particular package that we
can use to write out text to the screen
and we can do that with this package. So
I'm just going to type import
and it's the package we need is called
fmt. Let's press the enter key here. So
every go application has a main entry
point, a method called main and that's
the entry point of the application. So
let's create the function for this
using go. So we type funk and then the
name main like this. Open close brackets
and then we can open and close curly
brackets. And this demarcates where our
go code will be where we're going to
write
some text to the screen functionality
that writes text to screen. So we're
going to use that fmt package
functionality and then the method is
print ln and then we're going to write
uh whoops hello
go world like this.
Let's give it an exclamation mark here.
Let's save that.
And that's our code. That's our first Go
application written. And let's see if we
can run it. Right. So, I've cleared the
screen and let's see if we can compile
our Go application. So, to do that, we
go we type go build like this.
Great. And let's look in that directory
and see if it's created an executable
for us.
And it's created the executable called
my app.exe.
So to run that, we can just type my app
like that. Hello Go world. And we've
written our first Go application.
Brilliant.
Right. So as mentioned earlier, we're
going to be using on both the client and
service side, we're going to be using
Visual Studio Code to develop our
application. So we of course want to
firstly make sure that we have Visual
Studio Code installed on our machine. So
let's type in https/
slash slashcode.visisualstudio.com/d
download
so that we can download Visual Studio if
we don't already have Visual Studio on
our machines. And then it's just a case
of going through the install
instructions as we've done before. So
you want to download the installer by
clicking if you're on a for example on a
Windows 10 or 11 platform, you want to
download this installer here. You want
to click this option here. If you're on
a Mac platform, you want to click this
option here. And of course, this is for
Linux.
So let's click on the Windows option
here.
And we're downloading it. Excellent.
Going very well. Here we've got our
indicator.
And once it's downloaded, of course, all
we do need to do is uh double click on
the relevant file and go through the
installation instructions provided to us
through a wizard.
And that's it. We've got the folder icon
presented to us. And then we've got an
EXE file here which is our installer
executable. And we can just double click
on this
and install. I accept the agreement.
Next. I would actually encourage you to
select this check box here to click on
this checkbox here so that the icon is
readily available on your desktop once
the installation process completes. Then
let's press the next button here. And
then you press the install. I've already
got Visual Studio installed, so I'm not
going to go through this process, but
please go through and install Visual
Studio Code if you haven't yet installed
Visual Studio Code. So, the next thing I
want to do is create a folder on my
local machine. So, cdev
and
Golang.
And you can see I've done a number of
incarnations of the magic stream
application in preparation for this
course. But we're going to create a
fresh new folder where we'll create our
application. And I'm going to call this
folder
magic
stream
movies.
Like that. Press the enter key.
Excellent. So we've got our designated
folder that will house the files for our
application. So we're going to now fire
up Visual Studio Code and firstly create
the infrastructure for our projects. So
we got a React project on the front end,
the client, and we've got a Go which
leverages Jenonic which is a web
framework on the server. Great. So let's
open the folder we've just created, the
fresh new folder. So, open folder
and we want magic movie. No, we don't
want that one. We want magic stream
movies. And let's select that folder.
If you already had Visual Studio Code
installed on your machine, you might not
necessarily have the latest version. And
you can check for updates with through
this menu option here. Check for
updates.
Okay, there are currently no updates
available. So, I've got the latest
version. So, I'm good to go. But if you
haven't yet got the latest version, just
hit the check for updates menu option
here.
Excellent. In this part of the course,
we're going to focus on the server side
code, but just bear in mind, we're going
to then once we finish the server side
code, we're going to come back and
develop the client React code.
Excellent. So, let's create a folder for
the client React code. And we'll call
this folder aptly client. And we'll get
back to this a little bit later. So
let's progress to the server side code.
So let's create a folder called server
which will house our server go code. So
let's create a folder called server. And
then within this folder, let's create a
subfolder
called
magic
stream
movies
server like that. Okay,
excellent. Please note that if you get
stuck at any point or you just want to
reference the final code for the
application, please check out the final
code at this URL. There may be a few
minor differences between, for example,
naming conventions for the files when
comparing the final code to what we've
discussed in the video. But the
differences are very minor. So please
don't be put off by these minor
differences.
Okay. So now let's invoke the terminal
window. You can do that by control back
tick like this. By default, uh it has
selected the root directory of our
solution which will contain both client
and serverside code. So we're in the
magic stream movies folder here. And
we're going to be using PowerShell here.
Okay. So within this directory here, we
want to create a module which will store
all the references to the dependencies
that we will be using in our Go project.
So to do that we first need to be in the
root folder of our serverside code which
is
magic
stream movies server
and then we can type the command to
create our module
our go.mod file. So to do that we type
go mod in it. So we're initializing
our project and this uh file that we're
creating will store all the the
references to the dependencies that we
will be leveraging for our for the
server side part of our application
which will be written in go. Okay. So go
mod init and then the name of our module
this is a best practice will actually be
where our serverside code will be stored
on GitHub. So, we'll be uploading this
serverside code to a designated
location on GitHub. And this is where
our serverside code will reside when we
eventually push it to GitHub. So,
github.com/gavanlon
[Music]
digital.
So, this is my path. So, obviously you
need to upload it to your particular
GitHub path. So you you'll want to
include where your code will be stored
on GitHub. So, github.com/gavlon
digital and then magic
stream movies
and then server
and then magic
stream
movies
server like that. Right. So, Gavinon, so
github.com gavlon digital magic stream
movies server magic stream movies
server. That's where my serverside code
will reside. And as I said, this is just
a best practice. What this will mean is
that for example other users will be
able to import your packages by using
the go get command and then typing the
relevant GitHub path to get your
particular packages to import those
packages into their applications. So
this is why it's a best practice. It
futureproofs your application. Great. So
obviously I haven't yet uploaded
anything to this path but I will be
uploading the relevant code to this path
at the appropriate time. Great. So I'm
going to press the enter key here and
it's created
now the good
file here. Right. So before we start
writing a little bit of code, I suggest
that we firstly install a very handy
extension
that
will enhance our go development
experience basically. So uh to do that
we go to the extensions tab here.
And let's search for Go
like that. And this is the extension we
want to install.
And I've actually already installed it.
That's why this button text is set to
uninstall. So if I press it, it will
uninstall it. But yours might be set to
install, meaning you haven't installed
it yet. And if it's set to install, I
recommend pressing the install button
and installing this very handy extension
here.
So this go extension
contains the following handy
functionality. IntelliSense results
appear for symbols as you type.
Code navigation jump to or peak at a
symbols declaration. Code editing
support for saved snippets, formatting
and code organization and automatic
organization of imports, diagnostics,
build, vet, and lint errors shown as you
type or on save. Enhanced support for
testing and debugging. So, it's well
worth installing this Go extension.
Great. So the next step is to create a
file within the magic stream movies
server folder called main.go. So let's
do that.
main.go.
As discussed earlier when we created our
hello world application,
each go application has its entry point
and the entry point of the application
is denoted by a function called main. So
firstly let's name the package for the
code we're about to write. Let's name it
main like this. Excellent.
Let's import
fmt.
This is the package we are going to use
to write out
our text to the screen within the main
method. And let's now create the
function main
like this.
And let's use the fmt package.
Print line.
And let's print out.
Hello.
Go world. Like that. Okay,
that looks good. Let's make sure we save
our code. Right. And now we want to run
our code. So to do that, we can make
sure firstly make sure you've saved the
code within the main.go file. And then
we can run this command. So it's just go
run dot
and that should run the code within our
main function here. But firstly make
sure that you are in the appropriate
directory the root directory of your
serverside code which in my case is
magic stream movies server like this.
And then go run dot should run our code.
Excellent.
Hello Go world. Great. So now that our
infrastructure is set up appropriately,
we're ready to write our web API code.
Okay. So firstly, we want to make sure
that we're in the same directory as the
go.mod file. So let's navigate to the
appropriate directory. So we want to be
in server slash
magic magic
stream movies server like this and then
we want to type the command that will
import and install the ginonic web
framework package. So to do that, let's
type go
get
dash u like this.
And then we want the path on github to
where the relevant package is. So
github.com
[Music]
jinonic
slashjin like that. Whoops. Jin and then
press the enter key. And it's as simple
as that.
And as always, we have to be a little
bit patient while
the import and installation process is
underway. And we're finished now. That's
brilliant. That wasn't too painful.
Let's clear the screen.
And we're ready to develop basic
functionality using the Genonic web
framework. Excellent. So firstly let's
replace this code within the main
method. Well let's get rid of this code
and then we can start coding the main
method which of course as discussed
earlier is the entry point for our
application. But before we start writing
the code let's have a quick look at
go.mod here. And you can see as
discussed earlier, we have references to
the relevant packages here
from within our gomod file. And we have
this gossum
file which has also been created for us
here since we installed the jinggonic
web framework. Let's go back to the
main.go file and let's write some basic
code. First of all, you can see here
that the FMT package reference here is
underlined. It's got a red squiggly line
under it. And that's just because we've
imported it and we're not currently
using it because we removed our FMT. Ln
code. So, let's write the code for our
Genonic functionality. This is going to
be very basic functionality that we
create just to
test that we've got the Genonic web
framework now installed and is now ready
to use. So firstly let's just write some
basic code using the Genonic
functionality.
Okay. So let's firstly type routter like
that and go rout equals.
So this operator means we are both
establishing or declaring the type for
this variable
as well as assigning a value to it. And
we want to assign jin dot default
to this variable. And you can see here
it's automatically imported gonic.
So
it's automatically imported the jonic
web framework package here.
And the the reason there's a red
underline under router is because we
we've declared and assigned a value to
the routter variable but we haven't yet
done anything with it. Okay. So firstly
we need to actually include open and
close brackets here. And now let's
create an end point a very basic
endpoint initially just for testing
purposes. So this is going to be a get a
HTTP get endpoint. So we go router.get
open and close brackets. And then let's
create the root. So it's going to be for
slashhello
like this. And then let's create a
function
a function handler for our get end
point. So to do that we simply type funk
like this.
open and close brackets. And now we want
to include a parameter within our
function definition. Let's call this
parameter C because it represents the
context of our Jin Gonic web framework.
So this is the context of the incoming
request coming from the client which
allows us to call various functionality
on this C object that is given to us
through the function handler method
automatically.
This is the function handler method for
our for/hello endpoint. Great. So we got
C and then we include the type of C
which is star jin dot context like that.
Excellent. And then let's implement the
code for our function handler method.
So we also want to close the brackets
here. Okay. And all we want to do here
is return C dot string return a string
to the calling code. So we're going to
go C.string 200 which is a restful web
API return value indicating that
the execution of this function handler
method has been successful.
And let's return a message.
Hello.
Magic stream movies like that.
Okay. And we have a little issue here.
And we don't need this bracket here.
That's the problem there. And let's save
that.
And now we need to run the router on a
particular port. So in in go we can do
it like this. We go if
an assignment operator
router.run
and we're going to run
our web framework on this port. So we're
listening on this port port 8080 here on
the server.
And then let's make an assertion. If
error is not equal to null,
let's run
this code which uses now I see that the
FMT package has been removed. We're
going to need the FMT package here
because we're going to write something
to the screen. So if error is not equal
to null, we want to write something to
the screen. And so it's going to be fmt
dot
print ln open and close brackets
and a appropriate message failed to
start server and then comma and the
error like this. And we need to include
FMT here within the imports. FMT.
Excellent.
Um, and nil should just have one L here.
So, let's save that.
And now our server, our web API should
be listening on port 8080. And we should
be able to access through our browsers
this/hello
endpoint and it should return a string
value of hello magic stream movies.
Great. So to run our code,
let's just save that. Make sure that
that is saved first. Then type
go run dot press the enter key.
Great. So it's telling us that it's
listening on port 8080. So we want to go
to localhost port 8080/hello.
And it should return hello magic stream
movies to us through our get request.
And we've got our get request function
handler method which is returning the
relevant status the HTTP status of 200
as well as a message
and that should be printed to our
browsers. So let's go into our browsers.
So I'm going to activate Chrome here.
So I've got Chrome running here and I'm
going to type HTTP.
We're running on HTTP like this
localhost and then we want port 8080
forward slash and we type the name of
our route or the name of our endpoint
hello like this and let's see if that
works. Hello magic stream is printed to
the browser as expected. So our ginonic
functionality in its most basic form is
up and running for us to use. So, we've
created a route and we've been able to
access that route through our browsers
and our function handler method handling
the relevant functionality for our hello
route has been executed and printed the
appropriate text value to our browsers.
It's returned it to the client and
printed the appropriate value to our
browsers. So, great. We're now ready to
write more complex functionality and get
into the substance now of the course.
Excellent.
Okay. So now we are going to get serious
about building our restful web API
solution using Go and Jenonic. So before
we start writing our code, let's firstly
create a basic folder structure so that
we can keep our code neat, clean, and
dry. Of course, dry stands for don't
repeat yourself. So, we want to keep our
code in discrete, easy to find
structures so that as our project
becomes more complex, we don't get lost
when we need to, for example, implement
a piece of reusable code. Right? So,
firstly, let's create a folder named.
So, let's hit this icon here, new
folder, and let's name this folder con
controllers like that. Excellent. This
is where we'll store our controller
code, which will contain endpoint
function handler code used for
implementing logic for our HTTP
endpoints. If this isn't clear to you at
the moment, don't worry. It will become
clearer as we progress with implementing
the functionality for our controllers.
We've actually already created a HTTP
endpoint function handler that handled
the functionality for the hello
endpoint. If you'll recall, it was a
very simple function that returned a
message containing the text hello magic
stream movies to the client.
And the next one we want to create is a
folder called database. So let's select
that. You can rightclick and go new
folder. And let's call this database
like that.
We'll store code here that will leverage
the MongoDB go driver to connect our web
API component to our MongoDB database.
Right. So the next one is middleware.
Whoops. We don't want to create it
there. We want to create it here. So
let's select that folder, right click,
go new folder, and middleware. Like
that. Press the enter key. Got our
middleware folder. We'll implement code
within this middleware folder later when
we implement the authentication and
authorization functionality. The
middleware code is relevant because some
of our endpoints will be protected
meaning only logged on users will have
access to these protected endpoints and
conversely some of our endpoints will be
unprotected meaning a user will not need
to be authenticated before accessing the
unprotected endpoints. The meaning of
the middleware code will become clearer
as we progress with our course and we
get into the authorization and
authentication functionality later in
the course. So the next one we want to
create is a models folder. So let's
select this folder here, the root
folder. Right click new new folder and
let's call this models like that.
Excellent. This is where we'll define
our models which are used to define the
structure of the data that we'll be
handling from within our web API
solution. The structure of the relevant
models will be based on the relevant
document structure seen in our MongoDB
database. For example, we'll create a
movie strct that will be based on the
structure for a movie document stored
within the movies collection in the
MongoDB database. So the movie strct
will represent our movie model which is
based on the movie document structure
implemented within our movies collection
within our MongoDB database.
Great. So the last one actually not the
last one but the next one second to last
one is called roots. So let's create a
roots folder within our root directory.
Okay so here we go roots. Excellent.
Uh here is where we'll write the code
for defining both protected and
unprotected routes. Essentially these
roots define a path that client code for
example JavaScript code running in the
user's browser can use to access the
endpoints defined in our serverside code
our web API code written in Go. As
discussed the unprotected roots pertain
to endpoints that clients can access
without the need to be authenticated
before accessing the relevant
unprotected routes. Protected roots
pertain to endpoints that a user can
access only after being appropriately
authenticated. So the user will need to
successfully log into the system before
accessing protected endpoints. We'll
look at the authentication and
authorization functionality in detail
later in this course. Right. So let's
create the last folder and this is
called the utils folder. So let's
create a folder called utils within the
root directory.
utils. So this is where utility
functionality will be stored. We'll
store code here for reusable helper or
utility functions that can be reused
throughout our web API solution. Great.
So now that we've got our folder
structure in place,
let's create our first file, our first
code file
within one of our newly created folders.
And we're going to create the new file
within our models folder. So to do that,
let's create
a file
called
movie model.go
like this. So we're going to create our
movie strct within this file. To access
our MongoDB database, we'll use a
specific MongoDB go driver. For this
purpose, the ID for our movies model
will be of a data type defined within
this driver. So firstly, we need to
install the MongoDB go driver. So let's
do that. Okay. So firstly, we need to
navigate into the same directory where
the go.mod file is stored. So we need to
navigate to the
server
slash magic stream movies server. magic
stream movies server like that. And now
we just need we can install the MongoDB
driver for go with this command. So the
goget command. So it's goget then go do
db.org org
slash
d-driver
slashv2
slash like that and let's press
the enter key and that should install
the relevant
go driver the mongodb driver for go
as always we need to be a little bit
patient here
while the installation process process
takes place.
Excellent. So now if we look at the
go.mod file,
we should be able to see the reference
here to the new Go driver. And where is
it? There it is. So here's a reference
to the MongoDB driver for Go package
here. Excellent. So we're ready to
create our model now. Let's go to the
movie model.go go file and firstly we
want to define we want to declare the
name for our package that these models
will where these models will reside. So
we're just going to call this package
model
like this.
Excellent. So this means we'll be able
to import the model package which
contains which is going to contain the
various strcts representing our data
models. We'll be able to import the
functionality within this file easily
within other Go files. And you'll see
how we do this in just a bit. The
package declaration at the top of a file
defines the package name to which the
file belongs. A package in Go is a way
to group related files and functions
together. Files that belong to the same
package can share functions, types, and
variables. Note that only exported names
starting with a capital letter are
accessible from outside the package.
Unexported names starting with a
lowercase letter are private to the
package. So these packages can be
imported by other Go programs or
packages. So the name of the package
affects how other Go files or modules
import and use its contents. So this
named package declaration helps us with
the organization of our code. So as our
project grows in size and becomes more
complex, we are able to identify
relevant code efficiently. Now let's
write code to import the relevant data
type information from our MongoDB driver
for Go package. You'll soon see that we
need the BSON.object object ID data type
to define our ID field within the movie
model which is what we're about to
create as a strct. This bson object ID
data type resides within the MongoDB
forgo driver. So let's create an import
block like this. So import like that
then within round brackets we can import
the relevant package. So all we do is we
type
go.mongo
[Music]
db.org
slashgo.mongodb.org
slmongod-driver/v2
slashbson like that. And the only reason
it's got a red squiggly line under it is
because we're not using
the the driver functionality anywhere
yet, but we're about to do that. So
anyway, let's press the enter key and
let's create the movie strct. So let's
uh declare our strruct. And we can do
that with this code here. Type
movie
strct. And that's how we define a strct
and go. Open and close curly braces. And
we want the first field we want to
include is to store a unique identifier
for a movie. So let's call this ID. And
then its type as discussed is
BSON.object
ID. So the first field is called ID and
it's of type BSON.object ID. And this
field is for the purpose of uniquely
identifying a movie document or or movie
data for a particular movie. So, we've
got ID there. And then the next field is
IMDb
ID like this. And it's we're going to
define this as a string like that. Okay.
Just a quick word about the IMDb field.
An IMDb number, often called an IMDb ID,
is a unique identifier assigned by the
Internet Movie Database, IMDb, to a
specific title like a movie, TV show, or
episode or a person like an actor,
director, etc. So, for example, the IMDb
ID for the Clint Eastwood classic
western, Unforgiven, has an IMDb ID of
TT 0105695.
Great. So let's create the next field
which is named title like this and this
will of course store the title of the
movie for example unforgiven the shaw
shank redemption once upon a time in
Hollywood etc. And the next field is
called poster path like this
and it's also defined as a string.
This field is used for storing a
publicly available URL that points to an
appropriate poster image for each of our
movie documents. Shout out to the
movieb.org website. We are using the
poster images available on their website
in our application for this purpose. And
the next field is the YouTube ID field.
So let's create a field called YouTube
ID like this. And it's also of the
string data type.
This field stores a YouTube video ID
that points to a trailer for each movie
document stored within our MongoDB
movies collection. We are using the
trailers on YouTube as a substitute for
streaming the actual movies. You'll see
a bit later how we can use this YouTube
video ID value to run an appropriate
trailer for each of our movies from
within our web application. So let's
include a field named genre like that.
And this field is going to be defined
as a genre strct which we haven't yet
created. And we want an array of genres
stored within the movie strct. Great. So
in this field we are going to store an
array of genres. Each movie can be
associated with one or more genres. For
example, airplane would just have one
associated genre in the array which
would be the comedy genre. Unforgiven
could be associated with more than one
genre. for example, western and drama.
You'll see a bit later in the course how
we are going to use this genre array to
help recommend movies to the user. So we
need to create our own strruct
definition for the genre field which
will store an array of genre strcts. So
let's define the genre strruct like
this.
type
genre
strruct and let's open and close the
brackets like this and then let's create
a field called genre ID and this will be
of the int data type.
So the genre ID field will uniquely
identify each genre within our database.
And then the next field is
genre name and this is defined as
string.
This field will store the actual name of
the genre as a string. For example,
comedy western drama thriller etc.
And then next we have the admin review
field which we'll define as string. This
field stores the movie review associated
with each movie document. So an
administrator which is just a user who
is part of the admin role will create a
review for each movie in natural
language. We'll use open AI through lang
chain go. We'll discuss lang chain go at
the appropriate time and we'll prompt an
LLM through the use of lang chain go to
extract one word to define the sentiment
behind each of the administrators movie
reviews. The sentiment can either be
excellent, good, okay, bad, or terrible.
These options are specified in the
relevant prompt along with the relevant
movie review. The sentiment can then be
stored in a field which we'll name
ranking.
So let's create the ranking field. So
the ranking field here is of type
ranking and we haven't yet created the
strct that represents the ranking type.
and we'll do that now. So let's create
the strct for the ranking data type. So
we do that by typing type and then
ranking
strruct
open and close curly brackets like this.
And this has two fields one named
ranking
value like this
and it is of type int.
So as discussed each sentiment
excellent, good, okay, bad or terrible
has an associated value. For example,
excellent has the associated value of
one. Good has the associated value of
two. Okay has the the associated value
of three. Bad the associated value of
four and terrible has a ranking value of
five. So the next one we want to include
is ranking name and this is defined as a
string data type. So an integer value is
stored in this ranking value field which
is associated with the relevant ranking
name field which as discussed can be one
of the following excellent, good, okay,
bad or terrible. So these fields are
part of functionality that turns a movie
review written in natural language into
a quantifiable value, a ranking if you
like. You'll see later how we can use
these fields to help recommend movies to
users. So the next thing we want to do
is define
how we want our fields to look in BSON
and JSON format. So the BSON format
refers to how the field looks within the
relevant document in the database and
the JSON format is how the data will
look, how the relevant field will be
named. For example, when
the data is returned from our web API
application to calling client code, for
example, to JavaScript code, for
example, running in the browser. So to
do that, we open and close back to
characters like this.
And then for the bon format, we type b.
Then within quotation marks we include
how we want the format to look.
ID like that.
Excellent. And then we can also do the
same for the JSON format. So it's got to
remain within the back characters. And
we type JSON. And in this particular
case,
the ID is represented the same way as
the BON as in the BON format. So it's
underscore lowercase id like that. So we
can define within our strcts how the
fields map to our MongoDB database as
well as to the JSON data that will be
sent to calling client code. So for
example, our ID field must conform to
this BSON format because this is how the
field is defined within our MongoDB
database and we want our ID field to
look the same within the JSON data sent
to calling client code. This may not
always be the case. Sometimes we may
wish a field to look different in JSON
format to what it looks like within the
database.
Therefore, the BSON definition here may
be different to the JSON definition here
in some cases. So, we include these JSON
and BSON definitions within back
characters like this. And let's fill out
the other fields. So bon and we want in
bon format
we want our field represented as imdb
ID and in fact we want the same within
the JSON format.
So we just include the same format here
like that.
And we just really go through here and
do the same sort of thing for each
field. So the title field bson
in quotations title will just be title
like this. And you can see that in all
of the cases so far bon and Jason are
exactly the same.
What have I done wrong there?
Underline bon
genre id.
Okay, so we can't leave gaps like that
or else it complains.
Okay, so interesting. Anyway, let's just
remove that gap
and that s and let's include
the definitions here.
So the last thing we want to do in this
section of the course is create
declarative rules for each of our
fields. we can establish within our
strct rules for each of our fields so
that only valid data is stored within
our strcts. So when for example client
data is passed into our serverside code
at the point where our code maps for
example our movie strct to specific
movie data passed in from the client the
movie data is automatically validated
based on the rules that have been
defined. So establishing these types of
rules can for example protect our
database from being updated with
undesirable data. So let's look at the
IMDb ID field.
So for this field we want to include
validation
whereby this field cannot be left empty.
So validate. So if the client passes in
data representing a movie,
this field is required. So it cannot be
empty. So we include the required
keyword here within quotation marks like
that. So this validate
keyword here
is associated with a comma delimited
string of criteria validation criteria
represented by keywords like for example
the required keyword like that. So we're
only going to include the required
validation criteria for the IMDb ID
field here.
Okay, let's move on to the title field.
And for the title field, we want it to
be required. So we include the same
thing as we did in the other in the IMDb
ID field. So we include required like
this. But we also want the title field
to contain more than two characters. And
we can enforce that rule by using the
min equals 2 code here within
quotations. And we can also include a
max. So we don't want this field to
contain more than 500 characters. So we
can include max equals to 500 to enforce
that rule. Oh, still got a brown
squiggly line there. Can't believe it.
Oh, it's cuz that needs a space there.
Okay, perfect. So that now is formatted
correctly. So the the yellow squiggly
line went away. No more warning. Okay.
Okay. Let's move on to the poster path
field. And we want this to be a valid
URL. So let's include validate
colon within quotation marks. We first
want to include required. This has to be
filled in. This has this poster path
field cannot be empty. and then comma
and then we can include the URL keyword
which means it has to be a valid URL for
this path here. Okay, required URL and
we have problems here. All right, and
it's because we haven't closed off
poster path here. Okay, and now it likes
it. It's okay. All is well. Okay. So
within here validate and we're just
going to include a required
validation criteria here. Okay.
Brilliant.
Genre. So for genre we want it to be
required
but we also want the validation to dive
into the nested array.
So let's first include required here.
Let's first include required and then
dive. This keyword dive ensures that the
nested structure here which is an array
of genres will also be validated. And
we'll include the various validation
criteria in just a bit for the genre
strct. Okay. And this one here, the
admin review will just be
we'll just include the required
keyword
like that. Okay. And we'll just include
required validation
for the ranking field.
Okay.
Required. Right. So let's move on to the
genre strct and we've got as you can see
here we've got the dive keyword. So this
will also be validated. So let's include
the criteria and this must
be required and that's all we need for
the genre ID.
Okay. And then for the genre name
we'll include we'll include the required
criteria
but also min and max
criteria. So min equals to two whoops
max equals to 100.
Lastly we just need to include the
validation criteria. Oh, and we haven't
included the BSON and JSON
definitions here either. So, let's do
that first. So,
so validate,
let's just make this required
like that there. And then let's include
the declarative code for the the
appropriate declarative code for the
ranking name field. So this is just
ranking
name
ranking name. It's a required field. And
now you could establish a little bit
more complex validation for this field
because you might want to include
validation where only
one of the following values
can be stored within this field. So to
do that
you can go comma one
of. So perhaps you don't need the
required because you're already telling
it that one of these must be in the
field. So we go one of
equals
excellent. So this is delimited by a
space these values. Excellent.
Good.
Okay.
bad,
terrible.
But you may want this field ranking name
to be extensible
because the way I've designed this
application is that you could
potentially create a huge spectrum of
sentiments, not just excellent, good,
okay, bad, and terrible. So you must
bear in mind that this functionality can
be be extended in which case you you'll
have to change that here. So in fact I'm
going to remove this because it's a bit
too restrictive especially at this point
in the development process. So I'm just
going to make this required.
Brilliant. And that's it. So in this
part of the course we are going to
create an endpoint and an endpoint
function handler named get movies within
a controller that returns movie data to
a calling client. We'll write code to
query our movie collection from our
MongoDB database and return all the
movie data to a calling client. We'll
use the Jin Gonic web framework to help
with HTTP requests and HTTP responses.
And we'll use the MongoDB driver for Go
to query our movies collection within
our MongoDB database. We'll then send a
HTTP response to the calling client that
will contain a list of movie data. But
firstly, let's get set up with the
basics for our get movies endpoint
function handler and then set up the
endpoint that will make it easy to
invoke the functionality in our get
movies method via HTTP. Okay, so we're
firstly going to create a file within
the controllers folder. So let's select
the controllers folder here and let's
click this icon here to create a new
file. And let's call this file
movie_ontroller.
So we are using a common naming
convention here which is snake case for
the name of our movie controller file.
Excellent. Um and because I'm using
snake case here, we should probably use
snake case everywhere to keep things
consistent. So I'm just going to rename
the movie model.go file to movie
and then a lowercase M here for model.
Great. So that's now consistent. We're
using the same naming convention for
these files. So let's go back to the
movie_controller.go
file and let's name the package. So we
want our package to be named
controllers. Yep.
Like that.
So we've got our models our model
package here. Yes. Okay. So this package
we should have named models. So I'm
going to do that now. I'm going to name
this package models so that we're being
consistent with our naming convention.
So this is a package named models which
contains
movie model and this is the
movie_controller
file and it's part of the controllers
package. Great. Let's create a block for
our imports
like that.
Right out of the gates, I'm going to
include
the gingonic
package.com.
So, this is the package for the gingonic
web framework
for slashjin like that. Let's save it.
Excellent.
And in fact, that should be import not
imports.
Okay. Okay. And this red squiggly line
is just because we're not currently
implementing any code regarding the G
jinggonic web framework. So let's
implement a function called get movies.
And ultimately this function will be
used to return a collection of movie
data queried from our movies collection
that exists within our MongoDB database
and return that to the calling client
code. So let's create the function. We
use the funk keyword and we want this
function to be exportable. So it's going
to the first letter must be capitalized.
If the first letter was not capitalized,
it means the function is a private
function. But we want this to be a
public exportable
function. So we're going to capitalize
the first letter here. Get. And let's
move these like that. And you can see
it's in camel case. And let's open and
close brackets like that. And now this
function must return a jin dot
handler funk type because we're now
going to actually return a function from
the get movies function. And within the
return function will contain our
implemented logic. So let's type return
like this.
funk open and close brackets C and then
star jin dot con
context like that
and then open and close curly brackets
and we're going to then implement
sorry there should be a curly bracket
here
open the first function and then a
closing bracket here to close the nested
or the returned function here. And you
can see now those red squiggly lines
have actually disappeared here because
we're now implementing a function
handler that uses Genonic. This is how
we're hooking into the Genonic web
framework. So here we are able to
interact with the Genonic web framework
through a function that returns a
function. The interaction with Jenonic
is facilitated by our endpoint handler
function that returns a gingonic type
jin. handler funk. So basically get
movies returns a function of type jin
dot handler funk. Through this context
object denoted by the C parameter name
passed to our HTTP endpoint handler
function get movies. We are able to read
incoming HTTP requests as well as create
HTTP responses. So let's create the
relevant response. So we're just going
to create a test response for now and
then after this just so that we can test
our root. We're going to set up our root
for our endpoint and map that route to
the get movies function handler. So
firstly we're just going to create a
basic JSON response here.
So we're keeping this very simple just
so that we can set up. So return 200
HTTP status. Okay. And then we can use
jin.h H to help us return a valid JSON
message to the calling client. So let's
go message
list of movies.
Of course, in the actual end code, we're
going to be we're going to actually
return a list of movies, a list of movie
data. But before we do that, let's just
set up our route correctly so that we
can access the get movies functionality
through an endpoint. So to do that,
let's go to the main.go
method here and let's create a new
endpoint.
Let's go to do that. Let's go rout.get.
We're using an HTTP get request to
access this endpoint. This endpoint is
going to be for slashmov. And then we're
going to map that to to our method. So
firstly, in order to do that, we need to
actually import
our controllers package into the main
package. And to do that, we simply type
the following controller
and then within quotations.
And I'll explain what the controller
keyword here represents in just a bit.
But let's include the path to or the
name of our package, the full name of
our package which includes
github.com/gavanlon
[Music]
digital
slashmagic
streamovserver
slashmic
check stream movies
server and then forward slash
controllers.
So this might look a bit weird to you at
the moment, but let's go back to when we
created the go.mod file. If you look
here, we named this github.com gavanlon
digital magic stream movies server magic
stream movies server. So we want all of
our packages to stem from this root path
basically and this just makes it more
futurep proof our code and ultimately
easier for another developer to import
our packages into their application.
So we're going to keep this path as our
root path and then all the other
packages that we create will stem from
this. So for example, if we wanted to
import our models, you would just
include this root path forward/models
and the same for controllers. So let's
go back to the movie_controller
file here.
And you can see that this package is
named controllers. So if we go to the
main.go file,
we have controllers appended to this
root path if you like. So, github.gavlon
digital magic stream movies server magic
stream movies server controllers and you
can make sure that that's correct by
just copying and pasting that. So if you
copy that, go back to the main method to
the import section here and you can just
paste that in there
forward slash controllers
like that. And we're doing this so that
we can access the get movies endpoint
function handler that we just created in
our controllers package. As mentioned
earlier, this get movies function is
exportable because the first letter of
the function name is capitalized. If get
movies had a lowercase G, it would be
deemed as private and therefore not
exportable. So because it is exportable,
we can access it from within the main
method once the controller package has
been imported. So here we are importing
the controllers package like this. Now
you can see here we are using this
import alias and this is just for
readability. So instead of accessing the
get movies method via controllers, we
are going to use this import alias
controller. So that's why there's a red
squiggly line under it because we're
currently not using it and we're about
to use it here. Great. So now let's
access the get movies
function from the controllers package
and we because because we're using an
alias controller we can type controller
here dot and in intellisense we've got
get movies and that's the one we want.
So we are mapping the for slash movies
endpoint to controller.get movies here.
And if we just go back to get movies, we
can select that and go to
go to reference. Sorry, go to
definition.
Okay. And you can see we are just
sending back a JSON object to the client
message list of movies. So we should be
able to run this, go to our movies
endpoint and receive this message.
Assuming that our route has been set up
as we wish it to be set up and we're
declaring how we wish it to be set up
with this line of code here. So let's
run it. Let's first go into the root
directory. CD server
CD
magic
stream
movies server
and let's type go run dot
okay and it's listening on port 8080. So
if we go to port 8080,
we should be able to.
We want local host, but we don't want
that port. We want 8080,
not hello this time. We want movies.
That's our endpoint. If we click the
enter key, if we hit the enter key
rather,
oops 88
that's not what we want. We want 8080
there. And there we go. Message list of
movies. So this means that our end point
has been successfully mapped to the get
movies method, which is what we want. So
now we are in a position to query our
MongoDB database and return the actual
movie data to the client. So let's get
on with that then. So let's go back to
our code here
and cancel out of that. Clear the
screen.
And now that our route is set up, let's
complete the logic for our get movies
function handler method. Right. So I'm
going to delete that. So now that we
have our for/mov endpoint
and we've mapped it to our get movies uh
function handler, let's create the code
that queries our magic stream movies
database and returns the document data
within the movies collection to the
calling client. But before we do that,
we need to create reusable code that
will connect our web API solution to the
MongoDB database. And that will be done
through the use of the MongoDB for Go
driver. Let's create a file within the
database folder here.
New file and let's name it database
connection.go
like this. Press the enter key. package
database. So the code within this file
will belong to the database package.
Let's include an import block like this.
And we want to import FMT the FMT
package so that we can write certain
values to the screen. This is good for
when we're during the testing phase of
our development during the testing and
development phase. And we want to be
able to log information. So let's
include the log package. And we want to
include the OS package. And this package
will be used for reading environment
variables which we will configure in a
bit within a file called env. And now
very importantly we need to import the
MongoDB for Go driver packages. So if we
go to go.mod,
we can see we've installed
the driver here. So we can
actually just import the packages. We
actually went through the install
process already. Oh yes, and the reason
we've already installed that is because
we needed the bison.object
ID
data type which resides within this
package here. That's why we've already
installed it.
Okay. So let's go back to the main.go
file. Sorry, we want to go back to the
database connection.go file here.
Okay. And let's include the following
imports. So we want go.
MongoDB.org
or
slash
dashd driver/v2
slash
like that.
So go.mongodb.org/mongod
org/mongod
driver/v2/mongo.
And then we want a similar import here,
but this time we include a forward slash
options. And we'll see the package
functionality in action in the context
of our database package in just a bit.
And of course, they have these have all
got red squiggly lines under them
because we're not using any of the
functionality of these packages yet. So
we're going to create a function called
DB instance which will be used for
connecting our Jin Gonic web API
application to our Mongodb database
through the use of the MongoDB forgo
driver. We have already imported these
driver related packages for this
purpose. Okay. So let's create our DB
instance method. So to do that we type
funk DB instance like this open and
close brackets. And we want this to
return a value of type
client like that. Let's open and close
curly brackets.
And the first thing we actually want to
do is read an environment variable.
So to read an environment variable from
av file, we actually need a particular
package and we haven't yet got that
installed. It's called joo/go.env.
So to install it, we use the goget
command. So let's type goget
and then it's github.com/
[Music]
slashjo
slashgo.ev.
So go get github.com/jo/go.env.
Let's press the enter key.
And it's done it for us. Excellent.
So we can verify that. If we look at our
go.mod file, we should be able to see a
reference to it here. It should have
added it.
And there it is. Great. Okay, so let's
create the code for our DB instance
method. So as discussed, we want to
access av file, but we don't yet have an
env file to house our uh various
environment settings. So to do that,
let's rightclick magic stream movies
server here. Let's go new file and then
create a file called
env.
So the first environment variable that I
want to configure is called database
name.
So let's configure our database name
here. And we've called our database
magic
stream
movies. We can verify that. Let's just
save that. And we can verify that by
going to compass. Let's launch compass.
Okay. So, magic-stream.mmov,
that's what we've called it.
Okay. That's what we've called our
database. So, let's go back to
the code
and change this to
magic dashream
dash movies like that.
And then the other environment variable
that we want to create is called
MongoDB
uri. So this is a pointer to our MongoDB
database. This is a connection string to
our MongoDB database and we can
configure it here. And this will make it
easier for when we deploy our
application to the cloud later on. And
we can just configure the new location
of our database here. The new connection
string for our remote deployment
deployed version of our MongoDB
database. We can just configure that
connection string here. So let's
minimize that and go back to compass.
And we can actually get our connection
string
from here like this.
So we can just click on this and copy
the connection string to our clipboard.
So, let's head to this option here
and then we can
then we can just paste it in here.
Excellent.
Okay. And let's go
back to our database connection.go file.
Okay. So, the first thing we want to do
is load our uh file that contains our
environment variables into memory. And
we can do that with this line of code.
[Music]
env.load
and then in quotations env like this.
And oops. And if a error has occurred
while trying to load this file, it means
it might be deployed
to a remote server that doesn't contain
this file. So we're actually just using
our environment variables
locally at the moment and that's why
we're using this particular package. The
environment variables will be configured
differently um when we deploy it to the
cloud. But we need this line of code
here. So in fact this line of code will
return an error in the cloud. So we
don't want it to be a fatal error. We
just want to check for the error and we
can just print it to the screen. Just
print a warning telling us that the env
file does not exist. And this is this
probably is harmless because it means
that it's deployed to the cloud and it's
reading the environment variable a
different way. We'll cover that in more
detail when we deploy the application to
the cloud. So if error does not equal to
null, this means an error has occurred,
which probably means that the env file
does not exist, which probably means
that the version that's running is a
deployed version and the environment
variables are not configured within av
file. But we'll discuss this a bit
later. So I'm just going to print a
warning message to the screen. warning
unable to find.
Envile
like that. Great. So if you had for
example gone log.fatal here, the code
would actually stop. It would print the
the error to the screen, the error
message to the screen and this function
would and the execution of this function
would discontinue
at this point. So, we want to
print it
as a warning message so it's not a fatal
error. Great.
Okay. And then let's read the
environment variable. So, let's include
this line of code
that assigns
that assigns the relevant environment
variable. We're using the OS package
here. Get
env method and we want to read
a particular environment variable. We
want to read the Mongodb
URI environment variable. So let's go
back to that and paste it here. And then
we can check. So if
db
is equal to an empty string then some
sort of issue has occurred
and our application cannot really
continue without this information. So
we're going to log a fatal error this
time. dot fatal. We don't want the code
to continue after this point. So we go
log.fatal fatal and we can output
a message saying MongoDB URI not set
not set that should do okay can't read
the MongoDB URI which contains the
connection string
and we can just make we can just print
that using FMT to the screen we can
actually print the
the relevant environment variable to the
screen using the FMT package like this
comma
db like that.
Excellent.
So now that we've got our URI,
we should be able to connect
our code here, our client code in this
case. Even though it's serverside code,
it's in the context of connecting to the
MongoDB database, it's client code. So
we can go client
options
colon equal options
dot
client
open and close brackets dot apply
uri and then let's include the readin
environment variable which is mongod db.
Excellent.
And then
now that we've read in the actual
connection string,
we want to actually connect to our
database,
our MongoDB database. So let's do that
with this line of code. Client,
error. You see this function that we're
about to to call returns two
values.
one which is a Clyde object and the
other which potentially contains an
error. If the code hasn't been executed
for some reason, if the code could not
be executed for some reason due to an
error, this object will contain a value.
Great. So now we call mongo.connect
connect and we pass in the client
options
to connect to our MongoDB database.
So if error
is not equal to null, we'll cover error
handling in just a bit. We'll cover the
details of error handling in just a bit.
Let's first write our DB instance
method. Let's just get the fundamental
code in place first and we'll look at
some of the details in just a bit.
So if it's not equal to null, I'm
actually just going to return null to
the calling code.
In fact, let's return the in fact.
So if is not equal to null, let's just
return
error to the calling code.
This means the code will cease to
execute after this and it will return
whatever error occurred at this point in
the code. And if no error has occurred,
let's return
the client object.
For now, let's just return null. If the
error occurs at this point, we'll just
return null just to get things up and
running. We can handle the details of
that exception a bit later. So now we
want to create a method which opens the
actual connection to our database.
So let's create a function
called open collection
like this.
And this function contains a collection
name parameter
as a string. So for example, if we are
querying the movies collection, movies
will be passed in here as a string.
Okay. And we want this to return a
collection. So
collection like that open and close
curly brackets. And we can implement the
logic for this method now. Okay. So we
need to firstly load the env file to see
if it exists.
and it will exist if the code is running
locally but perhaps not if but won't
exist if we've deployed it to the cloud.
So let's do a check for that. Let's go
gov.load
and let's load the env
file here like that.
So if uh
is not equal to null,
let's do the same thing. We're just
going to
print a warning.
Okay,
like that
the env file doesn't exist. And then we
want to read the relevance environment
variable at this point. So, and assign
it to a variable called database
name. And you can see that we're using
this special operator here, colon
equals, which means it's actually
declaring the variable and its type.
It's inferring the type by the returned
value here. So, get the get env
method
returns a string data type. Therefore,
database name because we're using this
particular assignment operator, it knows
that database name go knows that
database name is defined as a string at
this point. So, we don't have to
explicitly for example in a line up here
go something like string database name
because it already knows that this is a
string based on what is returned by this
method. Okay, let's go data base
name like this.
Okay. And we can go use fmt
print ln just to for testing purposes to
make sure that the correct database name
is indeed being read from the
environment variable. And we can just
output it here like this.
Okay. And that's just for testing
purposes.
Great. Now we want to actually
load the relevant collection. So to load
the collection we go collection
and once again we are inferring its type
by the returned value from
a method which we'll write
right now and we actually need the
client object at this point.
So outside of all of our methods we want
to create a client object based on the
DB instance.
So the client returned from here will be
our client object.
So let's call this client
and it's of type mongo.client
equals to the return value from our DB
instance method here.
Great.
So we can now use this client object
to open a collection.
So let's do that. Let's
go client
database and we've read in our database
name. So we can use this variable which
should contain our database name and
pass it in to the brackets here.
and then dot collection and then we want
to pass in whatever collection name has
been passed into the open collection
method. So we can copy that and paste it
in there like that.
If collection
equals to null obviously something has
gone wrong.
So we'll return null
to the calling client code. But if it
gets to this point, we'll return the
actual
collection.
This has only one L. I keep giving null
two L's. Sorry about that. So that's now
corrected. So now we have our
um open collection code written in this
method here. We are able to connect our
client which was set up through our DB
instance method here to the MongoDB
database from within our web API
component. Excellent. So, we're in good
shape here, and we're ready to
write the code for the get movies
function that will connect to MongoDB,
query the movies collection, and return
that data to a calling client via HTTP.
And we're going to do that with the
assistance of the Jingonic framework.
So, this is the context variable here.
and we're going to use that to help us
uh return a response to the client.
Okay. So firstly we want to include this
line of code here,
cancel and this is done so that no
matter what happens in this method
that our query that we're about to write
will
time out
and clear up any
resources that are hanging about.
So this is really just to ensure that
housekeeping is automatically conducted
no matter what happens in this method.
So context dobackground and we can set
the time out here to
100 seconds
second like that. Okay. And
you can see that because I'm referenced
the time
object here, it included the relevant
package automatically within the imports
block here. Within the import block
here.
Okay. And these are underlined because
we're not yet using them. These
variables here returned from the with
timeout method. And we also need to
include this line of code. Defer cancel
like that.
And then let's define a variable called
movies
which will store an array
of movie models a movie strcts. So
firstly we actually need to import our
models package from here.
Okay. Okay. And to do that,
we can like we did here where we're
importing the
controller
controllers package, which is one of our
packages. We can do a similar thing
here. I'm just going to copy that and
change the relevant bits. So, let's go
to
movie.controller
movie_controller.go
and include the local import here. And
instead of controllers,
this will be models like that. So we're
now importing our models which will
contain a reference to our movie strct.
So we can go models dot movie like that
and define our movie movies array
as models.mov. So this array can store a
number of
movie struts within it. Okay. And then
let's create a cursor
for the purpose of querying and
returning data from our MongoDB
database. So
firstly we actually need to create we
need to connect to our movies
collection. And because we've written
all this reusable code here, we can use
this open collection method here to
connect to our
MongoDB database. So we also need to
import the database package into our
controller file here, movie controller
file here. So let's make a copy of that.
Copy that. Paste it here. and replace
models with database
like that there and then we can use the
exportable methods
within the database package within our
get movies function handler method here.
So let's create outside of any method
here. Let's create a connection to our
movies collection. And we can do that
with this code var
movie collection.
And let's And our movie collection is of
type mongo.olction.
Oops.
Like that. Equals to. And then we can
refer to database because we're now
importing our database package dot open
collection which is our
method that we created within our
database package
and we can pass in the movies collection
here as a string. So we pass in the
movies name which is the name of our of
the collection we want to connect to
here.
Excellent. So it's running our code
here.
to connect to the movies collection. So
let's go back to movie controller there
and let's
use our movie collection variable here
which is been deliberately coded outside
of the method so we can access so we can
access it with any method within this
file. So let's paste movie collection
there
dot and let's call the find method and
we're actually calling the find method
on a collection object a MongoDB
collection object. So, we're using the
MongoDB forgo driver here.
And let's pass in the context ctx and
then bson
m
like this.
And we need to create a reference to
bson here
in order for that to work. So, we've
actually already done that in the movie
model file. So, we do the same here. We
import the BSON
package here.
Okay.
And that's now working. And of course,
we are not using any of these variables.
That's why we have these three problems
here. And these are underlined. Okay.
So, let's move on to that. The next
code,
let's check the error object. So if it's
not equal to null and I will get it
right this time, null 1L. Okay, we can
use the context and write JSON to the
response
like this. And we want to include HTTP
500 status error.
And we can use this value here
for our internal server error. So we're
saying an internal error, an error has
occurred. If the error is not null here,
an internal error has occurred within
our web API component. We're sending
that to the client. And we can use jin.h
here
to send back a JSON response
error.
failed to fetch
movies like that.
Okay, excellent. Now, if it gets to this
point, so no errors have occurred there.
We'll use the defer keyword, and I'll
explain what this all means a little bit
later.
And then we close our cursor.
So no matter what happens within this
method, the cursor
gets closed and any of the resources
that the cursor uses will be cleared up.
So this is really for memory management
that we're using this line of code here
and this line of code here of course.
Okay.
So let's
now
create an if statement
if equals cursor. Let's use our cursor
here. Do all
method called on our cursor. Let's pass
in the context. This is for resource
management. This context up here
resource management purposes. And then
we want to pass the cursor into
our movies
array like that.
So we want to pass it in at this memory
address. So this amperand
means that we're passing it directly
into
a particular memory address in memory
into this movies array here. So now
let's put a semicolon there.
And if error is not equal to null, we'll
explain what the semicolon is in just a
bit. But let's just first write out our
code.
Okay. So if that equals to null, let's
also pass back a
status
of
internal server error.
Okay.
Uh, and the message here should be
failed to decode movies. So, it failed
to insert the movies cursor. Oops, we
have a problem there.
Failed
to insert the movie's cursor into this
movies array variable.
Okay. If an error is returned at this
point here,
sorry, no, if an error occurs at this
point here where we're placing the
cursor into the movies array here.
Great. Now if it reaches this
great and now if it reaches this point
here we can return
using our context context our C variable
which is the context object from Gonic
passed into this method from the
gingonic web framework. We can use this
C method, the C variable, the C
parameter or argument
to call the JSON method and pass back a
status
of okay.
So that' be 200. Okay, sorry. Status.
Okay,
this must be capitalized.
And then we can pass the movies array
and it will be passed down to the client
in JSON format.
Excellent. No problems. That's what we
like to see. And we can actually launch
this and test this all. So let's try
that. Let's go to the terminal here and
run our code. Go run dot.
And it's now listening
on port 8080. So let's see what happens
when we navigate to the movies endpoint.
Let's see if we get any results.
Okay, let's fire up Chrome
http localhost port 8080
slash
movies. Look at that. All our data has
been returned. And of course it looks
rather ugly at the moment. But when we
create the React part of our code using
Bootstrap 5, React Bootstrap, we can
make it look something like this. I'll
show you right now what we'll make it
look like. And there we go. So this is
what it's going to look like. The end
result will look like. So it looks
pretty cool.
Our movies returned to the browser will
look like this. Okay. So let's write the
code for another endpoint handler
function that retrieves and returns data
for just one movie this time from the
movies collection in our magic stream
movies database. Okay, you can see here
we've got the get movies handler
function. And now we're going to create
a function that is for the purpose of
retrieving just one movie. Rather than
returning all the movies from the movies
collection, we're going to find one
specific movie within the movies
collection. And that query will be based
on the IMDb ID for the relevant movie.
Okay. So let's write the code. So it's
funk
get movie like that. Open and close
brackets. And then we want to return a
function just as we did in the get
movies handler function. We want to
return a function of type handler funk
like this. And this is a way of hooking
into the gingonic web framework.
Okay. Uh so to hook into the gingonic
framework, we are going to return a
function of type jin.andlerf funk. So
let's employ the return keyword here.
And let's write an anonymous function
here. So when I say anonymous function,
of course, I mean it doesn't have a
name. So to do that, we type funk open
and close brackets like that. And then
we can pass in the context of the
jinggonic web framework um through this
parameter the C parameter here which is
of type star jin.ext
like that. So this will give us context
essentially from the web framework and
will make it easy for us to well for
example return a response to the calling
client code or reading a parameter.
You'll see we are able to read a
parameter from the URL which is
something we're going to do in this
particular function and handle requests
handle request data and that sort of
thing. So the Gengoni web framework is
making that a lot simpler for us.
Okay, perfect. So let's write the code.
The first thing we want to do is create
the context for our query
to the MongoDB database. So to do that,
we type ctx and it's returning two
values. So we also want cancel here. And
then let's use our assignment operator
context dot and then of course the
method with cancel
and we want with cancel time out here
and this is for the purpose of cleaning
up resources.
Okay, within memory dot background
and then comma and we wanted to time out
in 100 seconds.
100
times time dot second
and
we have a problem here. Uh it's not with
cancel timeout, it's with timeout.
That's the method we want to call here.
And then we need to include this line of
code defer cancel like that.
As you saw earlier when we created the
get movies endpoint function handler, we
are returning a function of type
jin.andlerf funk which provides us a way
to hook if you like into the gingonic
web framework. So this for example makes
it easy for us to map a HTTP endpoint
route to the relevant HTTP endpoint
function handler. This also makes it
easy for us to handle HTTP requests and
create HTTP responses from within the
relevant endpoint function handler.
So you can see here we are calling the
contextwith timeout method like this.
And this defer cancel code like this.
This code creates a new context ctx that
automatically cancels after a specified
duration. So here the timeout will occur
after 100 seconds. The function returns
ctx. CT ctx is the new context that
carries the timeout. Cancel is a
function to manually cancel the context
before the timeout if necessary. Defer
delays the execution of cancel until the
surrounding function returns. This
ensures that the context is properly
cleaned up and resources are released
even if the function exits early due to
an error. Okay, so now we're going to
use the C argument here, the jin frame,
the sorry the jin context or jin gonic
context here
to read in a parameter from the URL and
we'll see the details of how this
parameter value is passed in through the
URL when we map this handler function to
a particular endpoint and we're going to
call the endpoint for/mov instead of
for/movies which is mapped to the get
movies function there. But we'll do that
in just a bit. For now, we just want to
read in
the movie ID, which is going to be the
IMDb ID for a particular movie stored
within our database. And then we can use
the C object here param method like
this. And then pass in the name of the
parameter we want to read from the URL
from the end point path if you like. imb
ID. Excellent. So that should populate
our movie ID variable. So the next thing
we want to do is just check that we have
in fact retrieved a value from the URL.
So let's create an if statement to do
that. Movie id equals. So if movie ID
equals to an empty string,
let's pass back to the client using the
C gen context object and the JSON
method. Let's pass back to the client a
HTTP
status of bad request. status
bad
request.
And then
let's include a message jin.h. And we
can pass back valid JSON to the client
like this. So error
colon and then an appropriate error
message. Let's just say movie
ID is required. So no movie ID was
passed in with the
endpoint path.
So we need to fail this function call
here. And let's include the return
keyword so that the execution of code
discontinues at this point. Okay. And
let's now create a variable named movie
var movie. So this is a local variable
of type mo models do movie. And this is
a userdefined type that we created a
strct that we created within our models
within our movie model.go go file here
that we can use to store movie data.
Great. So, let's go back to our movie
controller.
And the next thing we want to do is
check if an error has occurred
while we attempt to query our movie
movies collection with this
with this code.
and we're using the find one method.
So we are employing the functionality
within the driver for go package.
As you can see here, if we go to movie
collection which this is what we this is
the reasonable connection functionality
we created in the previous part of this
course.
Here we are returning a mongo.colction
collection and we're connecting to a
particular collection through this
reusable code here. So if we go back to
movie_controller
we can see here
that we are connecting to that
particular collection the movies
collection here. So now with this code
using find one we want to
query the our database the movie
collection specifically for a particular
movie and the query will be based on a
unique identifier which will be the IMDb
ID retrieved from the IMDb ID parameter.
Okay. So find one and then let's pass in
the context which is for the purpose of
ensuring that resources are cleaned up.
So even if an error occurs within this
function the relevant resources
associated with the query will be
automatically cleared up. So this is a
housekeeping
mechanism that we need in place so that
uh to prevent things like memory leaks
for example. Okay. So find one ctx bson
m and now we're creating a filter here
on the IMDb ID parameter value
like this
colon movie
ID. Of course you can see here we've set
we're setting and that shouldn't be
capitalized. Sorry.
You can see here we're setting this
local variable. We're declaring it and
setting this local variable to the
parameter value passed in through the
URL to the our handler function get
movie. Yeah, our handler function called
get movie.
So movie ID equals to the IMDb
ID parameter and we're using that here
to query the movie collection for a
particular movie. Okay. Okay. And then
we can then the next thing we must do is
pop the ref pop's probably the wrong
word. Um to put the
relevant movie data if it's found by the
find one method into our
movie variable here. So let's do that.
Movie. And we want to place it at a
particular memory address of our movie
variable. So let's include an amperand
preceding the movie variable name here
defined as our movie strct.
So we want it to be placed inside a
particular memory location here. Okay,
great. And let's check if that error is
null.
So if it's not null, an error has
occurred
and we need to handle the error. And all
we want to do is pass back an
appropriate message to the client and
the relevant HTTP status. And we can do
that through the C.JSON method like
we've done before. So let's pass back
the status. http
status
not
found. So this means that the resource
that we're looking for within our
database was not found. So we need to
pass that back to the client with an
appropriate JSON message and we can
implement the relevant JSON message
using jin h like this. So we're
employing the ginonic framework for this
purpose here.
And so error Movie
not found.
Okay. Error. Sorry, it shouldn't be a
comma. That should be a colon like that
because it's a JSON
type format here.
Okay.
So, we also want to include the return
keyword because we want the execution of
code to stop at this point. Then if no
error has occurred with this through
this line of code where we are finding
the movie document based on the IMDb ID
value then we can return that movie data
through this line of code
to the calling client code. So we want
to pass back a HTTP
status of okay
like this.
And then we can just pass the movie data
back like that through this second
argument here
using the JSON
method called on the C context object
passed into our
handler function. our endpoint handler
function through the Jinggonic web
framework here.
Brilliant. You may have noticed the
colon equals operator and you might have
correctly inferred that this is an
assignment operator where the relevant
parameter value named IMDb ID is
assigned to a variable named movie ID.
The colon simply means the type of the
variable is inferred based on the value
being assigned to the variable. So colon
equals is a concise way to declare and
initialize variables in go. It infers
the type automatically. It's only valid
inside functions. It's a Go idiom used
often and preferred by local variable
declarations.
Right? So we're now ready to map our
handler function here, get movie to an
appropriate endpoint. So to do that,
let's go to main.go.
Let's just duplicate this line of code
here firstly and change the appropriate
parts. So here we want to change this to
movie because we just want to return a
singular movie to the client code and we
also need to change get movies to get
movie here. But the real difference
between this routter.get get method and
this one here is that we need to pass in
a parameter value through the URL when
this particular endpoint is called
because the get movie functionality
depends on the relevant IMDb ID value.
So to do that it's actually very simple.
we include forward slash
colon and then the name of the parameter
which is im db
id like that. So if we go back to our
get movie
method, go to definition, you can see
here we are able to
retrieve using this context object
passed in by the web framework. we are
able to retrieve the IMDb_ID
parameter value here and then we can
proceed with our query and the
functionality of this get movie method
and ultimately return the movie data to
the calling client. So let's go back to
the main.go go
file and within the main method
you can see that we have now created the
relevant route for our get movie handler
function. So the only thing left to do
is really is just test it. So let's
firstly go into the appropriate
directory so that we can run our code.
So cd server slashmagic
stream movies server like that and then
we just type go run dot
okay and
and it's listening on port 8080. So we
can navigate to our new endpoint through
our browser. So let's go to
let's use Google Chrome. Let's use
Chrome to navigate to the appropriate
endpoint. So http
localhost
colon at80
and this is the sort of thing we want.
We want to go to movie and then we need
to include a particular IMDb ID. And
let's find a particular IMDb ID through
compass.
So, I think we should use this one.
Let's uh query for the Empire Strikes
Back.
Star Wars: The Empire Strikes Back. An
absolute classic, of course. And so,
let's go here and include TT000080684,
which is the IMDb ID for Star Wars: The
Empire Strikes Back. Let's press the
enter key, and it returns the
appropriate data. We can actually verify
this visually by copying the poster path
here within our JSON data to another tab
here. And let's see if it returns. And
there it is. Look at that. Episode five,
the Empire Strikes Back. So that is
working. Brilliant.
Okay, let's find another one. Let's go
to Compass and let's do Star Trek the
Undiscovered Country. So let's copy that
ID to our clipboards
and paste it in here.
So you can see how the IMDb ID is passed
in through the relevant endpoint.
And there it is. Star Trek the
undiscovered country. Let's copy the
poster image here. The path to the
poster image which is a publicly
available
image address. And let's paste that here
and press the enter key. And there we
go. Star Trek: The Undiscovered Country.
And for good measure, why not? Let's do
another one. And I love the movie
Unforgiven. So, I'm going to copy this
IMDb ID and paste it here.
Let's see if it finds Unforgiven. And
there it is. And let's confirm that by
copying the poster image into the oops
into the next t tab like this.
And what a movie this was. Rest in peace
Gene Hackman.
But this was a fantastic Clint Eastwood
classic. And of course Gene Hackman was
amazing in it as always. Morgan Freeman
was fantastic
and Richard Harris. So what a cast.
Excellent movie. Great. So anyway, we
are absolutely getting there now. I'm
very happy with what we've done so far.
I hope you are too. Great. So we've
created two endpoint handler functions
for two HTTP get requests. One that
retrieves data for the entire movies
collection and one that returns data for
a specific movie. So we have covered
creating genonic endpoint handler
functions for HTTP get requests. So
let's create a handler function and
let's name this function add movie
like this
and returns a type of jin. Handler funk
because it's a handler function that
hooks into the gingonic framework. Let's
open our curly braces like that. And
let's include the return keyword here.
And then an anonymous function that will
contain our actual implemented logic.
And we want to pass in a parameter named
C of type
star
jin.
Context like this. And this is all for
the purpose of hooking into the
Jinggonic framework which makes it
easier for
handling requests and creating
responses.
So here we we are declaring a function
named add movie which will handle the
functionality for an endpoint that we'll
map to this function once we have
written the logic for the function. It
returns a jin. funk type which is just a
type alias for type handler funk funk
and in parentheses star context.
So this is the actual handler function
that jyn or jin gone will call when a
request hits the root it's attached to c
star jin.context
gives you access to the request response
path forward/query params form data etc.
So our function is integrated with the
Jinggonic web framework which makes it
easy to access the request response path
for/query params form data etc. This
function is going to handle a post
request because our function will add
movie data passed in from the client to
the movies collection within our MongoDB
database. In our web API solution, we
are appropriately using HTTP methods
like get and post. We have so far
implemented codes to handle get requests
to retrieve resources from our MongoDB
database and send them back to the
client in an appropriate HTTP response
using the restful architecture. When
adding a resource or resources to our
database, it is appropriate to use a
HTTP post request. So we are using the
restful architecture in our Genonic web
API solution. REST or representational
state transfer is a software
architectural style used for designing
web services and APIs. It defines a set
of rules and conventions for how clients
like web apps or mobile apps and servers
should communicate over HTTP. A restful
API uses standard HTTP methods get,
post, put, delete, etc. to perform
operations on resources which are
typically represented by URLs. So our
get movies and get movie endpoint
handler functions hook into the gingonic
web framework to handle HTTP get
requests with our add movie endpoint
handler function. We are going to add a
resource to the movies collection within
our MongoDB database. So we are going to
use a post request. We are going to map
the add movie endpoint handler function
to an endpoint that handles a post
request. So let's add the code to handle
the context of our interaction with the
MongoDB database. So it returns two
values from this particular method.
one which is the context and the other
one which allows us to
cancel
the action that the context will be
mapped to. So we need to call the
context dot
with
timeout method like this.
Pass in context dot background like
this. And
the amount of time we want to lapse
before
the action that this context is mapped
to is forced to stop.
So we want that time lapse to be 100
seconds. And we can implement that code
like this.
Okay great.
Okay, that looks better.
contextwidth timeout.
Um, okay. Excellent. So, the red
squiggly lines under these two variables
is because they are currently not used.
So, the next line of code we want to
include is defer
cancel like that. Excellent.
Okay. So, that red squiggly line has
gone away. and then we'll use the ctx
variable a little bit later. So, as we
did in our last two endpoint handler
functions, we implemented code using
go's context package to create a timeout
context. And it's typically used in Jin
and other Go web frameworks to control
how long an operation is allowed to run
before being forcefully cancelled. This
is useful for setting a deadline on
longunning operations like database
queries, HTTP requests or any blocking
operations. When the timeout expires,
the CTX or context is cancelled and any
operations listening on that context
will stop. Context.background
is the base context often used at the
top level of an application or request.
It's not cancelellable by itself, so we
wrap it with a timeout context. Cancel
is a function that manually cancels the
context before the timeout. Defer cancel
ensures that when your function exits,
it frees up resources associated with
the context. It's important to call
cancel to prevent context leaks. For
example, go routines waiting on a
context that never gets cancelled. Note
that a Go routine is a lightweight
thread of execution in the Go
programming language. It's one of the
key features that makes Go well suited
for concurrent programming. Go routines
allow you to run functions concurrently.
They are much more lightweight than
traditional threads. Creating thousands
of Go routines is feasible and common in
Go. Unlike threads managed by the OS, Go
routines are managed by the Go
runtimeuler,
which multipplexes thousands of Go
routines onto a small number of OS
threads. You start a Go routine by using
the go keyword before a function call.
So note that
even though you haven't seen me use the
go keyword explicitly in this web API
project, the R.Run.
Yeah, we've got rout.run.
The r.run method call starts a HTTP
server using go standard net http
package. the net HTTP package itself
spawns a new go routine for each
incoming HTTP request. So every time a
client makes a request, it is handled
concurrently in its own go routine. So
let's look what happens under the hood.
Jin is built on top of Go's net HTTP
functionality. The HTTP listen and serve
function used internally by R.Run run
listens for connections and calls a
handler in a new go routine for each
request. This allows multiple requests
to be processed concurrently and
efficiently. Okay, let's go back to our
add movie function. So, let's create a
variable for the purpose of storing the
movie data passed in from the client in
memory on the server.
Okay. Var movie
models
do movie like this. So this is the
userdefined strct that we created
earlier on within the movie_model.go
file here. So this is the type that we
are going to use or the strct we are
going to use to store the incoming
client data in memory on the server.
Excellent. And it's just got a red
squiggly there because
declared but not used. Same with the ctx
at the moment. Okay, let's press the
enter key. So here we have created a
variable of type models.ov
and this variable will store the
relevant movie data passed in from the
client. The models domov type is of
course our userdefined type that we
created earlier. Let's write code to
attempt to save the movie data passed in
from the client to our add movie
endpoint. And we want to save this data
within our movie strct. So let's write
the code for this. So
then our assignment operator here, we're
just checking for an error returned from
C.ind.
We want JSON. So it's C shouldbind JSON
like this. and then the amperand
character because we want to locate a
specific part of memory. So we use the
amperand for this. So it's a pointer to
a memory location when we put an
amperand. It's a reference to a memory
location rather than the data itself. It
points to the data amperand movie like
this. And then we're going to check the
error by adding a colon there and going
not equal to null. So if the error is
not equal to null, we need to handle an
error because an error has occurred. And
so let's type c the context the jonic
context JSON method like this. And we
want to pass back a status of bad
requests. So status bad request like
this, which I believe is represented by
the number 400
or 403. No, it's 400 I think. HTTP
status bad request jin and this is just
a shortcut for creating
a JSON object.
So jin.h and we're using the gingonic
framework for that purpose. So error and
then let's map the
key value pair the key error to a value
of
invalid
input. Whoops. Input like this.
Okay. And then we want to include the
return keyword. So it will stop
execution at this point because an error
has occurred and we want to just handle
that error. Send back an error message
to the client and then terminate the
execution of the function. The functions
basically failed at this point. So let's
just go over this code again quickly. So
C is of course the jinggonic context
object which represents the context of
the current HTTP request should bind
JSON and then in brackets amperand movie
attempts to pass the incoming JSON
payload from the request body into the
movie strct. If there's an error during
binding for example the JSON is
malformed or doesn't match the expected
strruct fields the error variable or the
error variable will not be null. So the
code inside the if block will execute.
If there was an error, this line returns
a 400 bad request HTTP response. Jin.h
error invalid input is a shortcut for
creating a JSON object with a key error
and a value invalid input. The return
keyword ensures the function exits
immediately after returning the error
response. No further processing occurs.
This code safely handles JSON input by
validating and binding it to a go
strruct movie. If the input is invalid,
it sends a clear error response and
holds further execution. The amperand
character in amperand movie is the
address of operator and go. This gets
the memory address of the movie variable
i.e. it passes a pointer to movie not
the actual value. So now remember we
included the declarative tags in our
movie model here
for validation purposes like validate
required. So to validate the movie data
now stored within our movie variable we
need to firstly install a go package
named go playground. So to do that make
sure that you're in the current
directory. Uh rule of thumb is make sure
you're just in the same directory as
where the go.mod file has been stored.
And then we type this
command to install go playground. So
it's just go get. Remember we're using
go playground to validate
our movie variable. So whatever the
client has passed into the add movie
method, we're validating based on this
criteria here that we included within
back to characters here. And we need
this particular package to perform the
validation. So we're installing a
validation package commonly used in
Gengonic applications. So we type this
command to do that. Go get and then it's
github.com
slash
goy-N playground
hyphen valid
day tour slashv10 like this and let's
press the enter key
and something is happening
Brilliant. That's a good sign. So, we've
installed our Go Playground package
and we can verify that by going to
go.mod here.
We should be able to see it somewhere
here.
There it is. Validator. Our playground
packages. The references to the
playground package are here. Brilliant.
So let's go back to our code and let's
include the relevant validation code.
So to do that let's type if
assignment operator
validate. But you see we haven't got
this validate object set up yet. So we
need to do two things. We need to import
the go playground package firstly. So we
can do that here like this within
quotations.
Okay. So let's type github.com
slash Whoops. github
slashgoy-enplay.
Whoops. Gosh. Go hyphen.
playground
slash validator
slashv10
and we're importing our go playground
validator package like that and then the
next thing we want to do is create the
validator object
and we can do that with this simple line
of code we type var
validate attor
equals to
validator
dot new like that.
Great. And now we can use our validator
which is which is provided to us by the
go playground package here and we can
use it now to validate our data.
Great. So validate
we've called
We've called the object validate and
then dot
hang on. Oh, we've called it validator.
So, let's call this validate. Okay.
See if we can get some intellisense.
Now, we've got the name correct.
Validate dot strct.
Then we pass in movie like this.
And then we can include a semicolon and
check the error. So if the error is not
null, we know an error has occurred
and then we can handle the error
appropriately
and we want to send back an error to the
client with a HTTP
status
of internal.
Actually no, we want it to be a bad
request. So it's a bad request 400 we're
going to send back. And then we'll use
Jin Garnic or Jin to
create a JSON response
error response. Oops.
We type error colon and our error
message
could be the following. Validation
failed.
Oops.
failed
comma
and details
comma
dot error. So the actual error message.
So hopefully this can be useful to our
client code in the event that an error
occurs at this point in the code.
Okay.
Great. And then let's use the return
keyword here
to stop execution of the logic within
the add movie
function here. Okay, brilliant.
So if we get to this point in code, we
want to then insert the actual data
within the MongoDB database. Not sure
why we're getting all this. Okay. Before
new line, missing comma before new line.
What? Oh,
I haven't included the if here. Sorry.
Having a bad day.
The go playground validator is typically
used in a genonic web application to
validate a strct like movie after it's
been bound from JSON. It uses the Go
playground validator v10 package. A
powerful and widely used validation
library in Go. Validate is an instance
of the validator. Validate.ruct
movie checks the movie strct against any
validation tags. For example, validate
required on its field. If any field
fails the validation, it returns an
error not null and the if block runs.
This code ensures the movie strct is
valid before proceeding. The validation
is based on our declarative validation
instructions that we included between
backtick characters for the movie
structure. For example, if we go to our
movie models file and we check some of
these, we've got validate required.
We've got min max. We've got limitations
on the minimum amount of characters we
want stored in the title and the maximum
amount of characters we want stored in
the title field. Here we've got a URL
validator here for the poster path.
So we can include declarative code here
to create rules for our fields within
the movie strct. So our validation
functionality here will validate the
rules that we've established between
backtick characters for each of the
fields within our movie strct basically
and then we are handling that error
here.
Okay. Excellent. Right. So if any field
fails validation based on its strruct
tags, the API responds with a 400 status
and a helpful error message. So let's
write the code that actually inserts the
data passed in from the client. Now that
it's been validated, we want to insert
it into the database. So let's write the
code for that. So we type result,
so two values can be returned, the
result and an error if an error occurs.
So let's assign that to the actual
action and we want to use the movie
collection
and then we want to call the insert
one method to insert a resource into our
database a movie resource. Now we can
map the context
that we created up here. We want to map
that context to this action here context
and then we want to insert movie into
the database. So we do this and then we
let's check our error. So if error is
not equal to null.
Oops.
So if error is not equal to null c.json
and let's pass a relevant
error back to the client. So let's pass
a status of internal
500
to the client
internal survey error like this. And
let's use jin.h here
to pass wellformed JSON back to the
client. Let's include an error key
and its value
failed.
to add movie. Great. That looks pretty
good. And then include the return
keyword to stop execution because it's
failed at this point. So this go code
snippet is inserting a document, a movie
into a MongoDB collection using the
MongoDB go driver. This is a MongoDB
collection object connected to the
collection where you want to store the
movie data, movies. The insert one
function inserts one document into the
collection. CTX is a context used for
timeout, cancellation or passing
metadata. Movie is the go strruct or map
that will be converted into a bon
document and inserted into the MongoDB
movies collection. Insert one returns a
result which contains information about
the insert operation such as the
inserted document ID.
is an error object which is nil if the
insertion was successful. If insert one
fails for example due to a connection
issue bad data etc it responds with
HTTP500
internal server error and a JSON object
with a error message failed to add movie
then returns to the client. The return
keyword halts further execution. So this
code tries to insert a movie into
MongoDB. If successful, the code
execution continues. If it fails, it
sends a 500 response with an error
message. Lastly, if the code reaches
this point, we want to send a success
message to the client, a HTTP success
message. So, we type C.json
and then within parenthesis HTTP
status created and then we want to send
the result back to the client. Great. So
that is pretty much our logic finished
for the add movie function. So the next
step is to map the add movie handler
function to an appropriate endpoint an
appropriate route. So let's map an
endpoint route to our new add movie
endpoint handler function. So to do that
let's go to the main method and let's
just make a copy of this here. this code
here, but this time we want to use a
post HTTP method. And then let's map
our
handler function method to a end point.
And let's call this one forward slash
add movie like that. So that's the path
to our endpoint
that we'll add to the base address
of our web application. And then we go
controller
add movie like that.
And that's it. We've mapped our add
movie handler function to the add movie
endpoint here. So let's test our new add
movie functionality. But now it's not so
easy to test this functionality as we
did for the get movie and get movies
functionality. The get movie and get
movies functionality are both mapped to
HTTP get requests which makes it easy to
just invoke the relevant endpoints
through the browser. The results are
returned and we can see the displayed
results within our browser in JSON
format. However, we need to map our add
movie handler function to a HTTP post
request where we need to pass movie data
in JSON format through the body of the
relevant HTTP message. So rather than
for example create our own HTML form on
the client that can run within our
browser for testing the add movie
functionality an easier option is to use
a free tool called Postman for this
purpose. This is a great tool that we
can use for testing our restful web API
endpoint. So let's navigate to
www.postman.com.
Right. So let's navigate to
www.postman.com.
So this is where you can download this
free tool called Postman and we use this
tool to test our web API endpoints. Then
you want to click on this menu option
here. Let's just accept all cookies. And
then we want to click on this
header menu option here and then click
download Postman so that we can install
our free version of Postman. And you can
see here it's detected
my platform Windows 64 and then please
just go through the relevant
instructions. Download the relevant
install file. Double click on it once
it's downloaded to your downloads folder
and go through the install process. And
this is what
Postman looks like. Okay. So
now we want to
test
our ad movie endpoint using Postman. So
I'm going to invoke Postman here.
Brilliant. Okay. And there we have it.
So this is what we want. We want instead
of get, we want post to be selected
here. And then we've got the path to the
ad movie endpoint here.
Okay. In order to test our result. And
then we want to go to body here and then
raw like that.
Okay. And let's just minimize that.
Let's go to let's go back to our browser
here. Open up a new tab. And I'm just
going to go to GitHub here because you
can download the relevant JSON data that
we're going to use to test our post
request here
from
this location. We've got a file called
add test movie doc. We're going to add
Highlander 2. One of the worst movies
ever created. We're going to add to our
database. That's just my opinion.
Horrible movie. I really loved the first
one, especially when I was a teenager,
but this movie diabolical. Anyway, I'm
going to copy the data for it
and then we can just so you can copy it
from this location from my GitHub
repository, Magic Stream GitHub
repository. Copy it and then paste it
within the body of our Postman request
here. So, it's body raw and then copy it
into this
text area box here. Copy the data from
GitHub here.
Then of course we need to make sure
that our serverside code is running. Our
test serverside code is running. So
let's go back here and to run our code
we just type go
run dot.
Okay. And it should be listening on port
8080. Excellent. And now
through Postman we can test whether we
can add Highlander 2 which is an
absolute abomination
to our database. Okay.
So let's do that. Let's press the send
button to send this data
to our add movie endpoint.
Let's see what happens. 500 internal
error. Oh dear. What's happened there?
undefined validation function min. Okay,
let's go to our movie model here
and let's check genre name
min equals to two
max equals to 100.
Okay.
So, let's take out the gaps here. I
think it is a bit funny with formatting
here. It's a bit
temperamental with formatting. And let's
save that. Let's try again. Go run dot
enter.
Okay, it's listening on 8080 there.
Let's send that through.
400 bad request. Okay. Well, we've gone
a little bit further. Validation failed.
Key movie admin field validation failed
on the required tag. Okay.
Okay. Because we got no right. Okay. So,
so we've already said that the ranking
is bad. This movie was really really
bad. So our field validation is a is
working. That's what that proves. So we
go to 400 bad request which is correct
because we hadn't included any field
value for the admin review field here.
And now we have So this should work.
Let's send that through. 201 created. So
we've created Highlander 2 within our
movies collection.
Okay. I'm not too happy with that. Oh,
right. Okay. Right. All right. So,
Highlander 2 has been created within our
movies database. Let's double check
that.
Okay.
Reload data.
And let's see if we can find Highlander
2. There it is.
But that doesn't look right there. So,
I'll have to look at that. But it has
created the
has created a resource but it hasn't
created a unique identifier as I would
have expected. So I need to just have a
look into that.
Okay. So the reason why that ID within
compass was just a series of zeros and
not what I would have expected to see.
for example,
a unique ID like this is because I
hadn't included
a particular keyword within the back to
characters here. So, we need to include
this keyword here
omit
empty
there in the bon and I'm also going to
include it in the JSON. So if we include
that in the bon
the bon criteria here it will insert a
unique identifier
within the database if the ID field does
not have an does not
store a value when it is inserted when
that data is inserted into the database.
So this will create a new unique
identifier like this by default for us.
Okay. So, let's take that there. Same
code. Omit empty
like that. And place it here also like
that. There. And now if we test our code
again, let's type go run dot to run our
code.
Okay, great. It's listening on port
8080. Let's go back to Postman.
We've still got everything set up here
and ready to go. I actually deleted the
previous record offscreen. I deleted the
Highlander that we had here. So, we're
deleting a new So, we're inserting a new
record into the database, and it should
include a unique object ID for this
resource here. So, let's test that.
Let's press the send button.
Okay, we've got a 200 created status
sent back to us, which is great. And
inserted ID, and that's what I would
have expected to see. So it's created a
unique identifier for our resource
within the MongoDB database. So let's
check that. If we go to compass and
let's refresh. Let's reload data. And
there we have Highlander the quickening
and it's got a an appropriate unique
identifier now included here for the ID
field.
Excellent. So our code is now working.
I'm actually going to remove
this particular validation from admin
review because initially when we create
the resource we actually don't need this
to be to have a value. So I'm going to
take this validation
away from this field here.
And you'll see why I'm doing that in
just a bit when we create the the code
for using the AI functionality to read
the admin review and extract the ranking
from that admin review using AI. And
we'll see how that works in just a bit.
Excellent. We're in good shape.
Okay. So, when a user first accesses our
Magic Stream website, on the homepage,
they will see some of the movies that
are available to be streamed. However,
when they click one of the movie items
on the homepage, they will be prompted
with a login screen. For example, we
click on Shaw Shank Redemption. Here, we
are prompted to log in because we can't
use their services. We can't use the
services provided by Magic Stream
without authenticating first. So, we
have to first
log on. So, I've actually seeded the
users collection with a user named Bob
Jones. So, if I go bobjones@hotmail.com,
that's Bob's email address, and I enter
his password,
and I log in,
we can now
we can now stream the Shaw Shank
Redemption. And this is just a trailer
played through YouTube, so it's not
obviously the actual movie.
The gun and then stop to reload.
So,
So, if we go back here.
So, now we're we're able to actually
stream the movies. And of course, I'm
just playing the trailers through
YouTube to
that acts as a placeholder
for the actual streaming service.
Great. So, let's log out. Anyone can see
the actual movies provided by Magic
Stream. But if you want to stream one of
the movies, you have to be
authenticated. The user must firstly
sign up or register for a Magic Stream
account. The user must firstly be
identified by the users's registered
details, the user's credentials before
the user is able to make use of the
services offered by Magic Stream. So the
user must log in firstly to the website
and be authenticated based on the user's
authentication details which will be the
user's email address and a password that
the user chooses when registering with
Magic Stream. So if we log in here, we
don't have an account. We can click this
link here
to register and we have this
registration form presented to us. So we
can also access the registration form
through this button register and we can
register our relevant user details and
our credentials for example our unique
email address and a password here. So
the first step in creating this
authentication functionality is to
create a model representing user
registration details. So let's go to
Visual Studio Code
and let's create within the models
folder here. Let's create a new file and
let's call this user_model.go.
[Music]
So this is the file user_model.go
that we're creating in order to include
a user strct which will represent a
users model. a user model that will
contain the users's details, the user's
authentication details and general
details. Even though we are in a
different file to the movie_model.go
file where code within the
movie_model.go
file is part of the models package. We
can also make any code we write within
the user_model.go Go file part of the
models package simply by including this
declarative code package
models
[Music]
like that.
And you can see we've got the same
declaration at the top of the
movie_model
file here. Package models. So anything
we write in here will also be part of
the package
part of the package named models which
will make it easy for us to import
whatever exportable
code we have here into other packages
and other applications. Okay, great. So
all exportable code for example a model
like the movie model where its name
starts with a capital letter can be
imported and used by another application
or from within another package. For
example, if we go to the movie
controller.go file the
movie_controller.go
file
we can see here we are importing the
models package. So we have the root path
here which we established within the
go.mod file by naming the module
with this path on GitHub that where we
will upload our code to eventually.
So in movie controller we have that as
our root path the name of the actual
module and models is the package name
that we are the package that we are
importing here so that we can use for
example the movie model within our code.
So when we create our user
strct we'll created it pretty much in
the same way the same basic way that
we've created the movie strct. So
firstly let's create an import block
here and then we'll create our user
strct.
Okay. And we need to import the go.
MongoDB.org
slashmongo-driver/v2
slashbson package like this. and it's
got a red squiggly line because we're
not yet using the relevant functionality
within this package. Okay, so let's
create our user strct and we can do that
by typing type
user strruct like this.
Oops.
And then within the curly braces, we're
going to create our first field, which
is an ID field that uniquely identifies
a particular user within the users
collection. And we'll type this as
bon.object
id.
Excellent. So the reason we need to
import the
go.mongodb.org/mongo-driver/v2/bent
or/mongo-driver/v2/bon
package is so that we can make the id
property of the user strct of type
bson.object ID. When a new document is
added to a collection in our magic
stream movies database, a unique
identity is automatically generated for
that document. So this is what this ID
field represents in our user model. A
field that uniquely identifies the
document in the collection. So this is a
field that uniquely identifies a user
resource in the users's collection.
Let's also create a field that uniquely
identifies the user. So let's create a
field named user ID for this purpose.
So user ID
and this is of type string. This field
is of type string. Let's create a first
name field
like this of type string and a
last name field like this also of type
string. Let's create a field named email
and this is also of type string. Let's
create a password field also of type
string.
Note that we will include hashing
functionality to obuscate or hash the
password before it is entered into the
database. It is of course not good
practice in terms of security to save
the password as is to the database.
Let's create a field named RO
and this is also of type string. So this
is also of type string but must either
contain a value of admin or a value of
user. We're only going to have two roles
available. So we could extend the role
to enable multiple types of roles to be
stored in the ROS field. But for now we
are going to restrict the values to
either admin or user for this field. Of
course only administrators will be added
to the admin role which will mean they
will have administrative privileges to
for example in our application to add
reviews to movies. A user is only able
to stream movies and in our application
will not be able to review the movies.
I've deliberately made it like this for
simplicity, but you can of course extend
the functionality so that the users can
enter reviews for the movies. So for
auditing purposes, let's create a field
named created at like this. And this
field is of time dot time like that.
And you can see that we've got a red
squiggly line here undefined because we
need to import the relevant package. And
we can do that here like this.
And there the red squiggly line has
disappeared. Then let's update our model
with the update at field which is also
for auditing purposes. And this field is
updated when a user's details are
updated. For example, if the user's
password changes etc. So let's create
that field. So update
at and this is also of type time dot
time like that. Brilliant. Then let's
create a field named token and this is
of type string.
We must have it with a capital letter
because this is exportable code. So
token string like this.
Excellent. We are going to use JWT or
JSON web tokens for authentication and
authorization purposes in this
application. So we are going to persist
the values for the relevant tokens
within the document that is represented
in code by this user strct. A JWT JSON
web token is a compact URL safe token
used to securely transmit information
between parties as a JSON object. It's
commonly used for authentication and
authorization in web applications. A JWT
has three parts separated by dots. A
header part which specifies the type of
token and the signing algorithm for
example HS 256.
Payload contains the claims user data or
metadata like user ID roles or
expiration time. Signature verifies the
tokens authenticity using a secret key
or public private key. So let's look at
how JWT is used for authentication. A
user logs in with their credentials. The
server verifies the credentials and
generates a JWT containing user
information. The JWT is sent back to the
client, usually stored in local storage
on the client or a cookie. We're going
to firstly look at storing the the JWT
in local storage, but we'll discuss why
this is not the most secure solution.
And then we will eventually store it in
what's known as a HTTP only cookie,
which means the cookie cannot be read on
the client by JavaScript code, making it
more secure and less and less
susceptible to XSS attacks. We'll look
at the details of this a bit later. For
future requests, the client includes the
JWT in the authorization header. This is
in the case where the token is stored on
the client and then is added to the
authorization header when for example a
request is made to a web API endpoint.
But we are we we'll first initially look
at this technique. But as I said it's
not the most secure technique. We'll
eventually we'll implement our code
using HTTP only cookies which is a much
better and more secure way of passing
the tokens from client to server. For
example, the server verifies the token
signature and grants or denies access
based on the embedded claims. The
benefits of using a JWT are as follows.
Stateless, no session storage needed on
the server. Scalable, easily used in
distributed systems. can be passed
between systems and services securely.
Then it is good practice to also create
a refresh token so that the client is
able to authenticate without the user
needing to log in again once the token's
expiry date is reached. We'll discuss
this further later in the course. But
let's for now just include a refresh
token field like this.
And this is of type string just like the
token field. And the last field stores
an array of our userdefined type genre
which is based on our genre strct that
we created within our movie_model.go
file. And you can see it here with all
its validation that we also added in
these tags here. And we're going to use
this within our user_model.go
file. And the reason we can just use it
straight away is because this code here
is part of the models package just like
the code in the movie_model.go
file. So we got package models and we
got this declaration at the top here. So
we can just use that strct automatically
here because they're part of the same
package. So let's do that. I'm going to
call this field favorite
genres like this. And this is an array
of genre strcts. So we can just
automatically include this type here
because our code here, our user strct is
part of the same package. Even though
they're in separate files,
even though the genre strct is in a
separate file to the user strct, we can
access the genre type because they are
part of the same models package. So it
stores an array of genres. So when the
user registers to use our magic stream
movies application, the user will be
presented with a list of genres and the
user must select one or more of these
genres to provide our application with
insights so that our application can
recommend movies to the user that the
user is most likely to want to stream.
We'll discuss our AI recommendation
feature a bit later in the course. So
let's quickly look at the registration
form for the prototype app that I
created when I while preparing for this
course. So you can see here when the
user registers with the system they can
select their favorite genres and this
will become part of the recommendation
functionality that we'll add a little
bit later where um this whatever they've
chosen here will be instrumental in how
we recommend movies to a particular
user. So I mean if we log on as Bob
Jones for example
joneshotmail.com
like that there if we log on you can see
that if we go to the recommend
recommended tab here it's recommended
five movies and in part this is due to
the genres that Bob selected when he
registered with the application.
So you can see for example he's more
than likely uh when he registered he
selected comedy, sci-fi, thriller for
example or drama or fantasy. And you can
see it's ordering the recommended videos
in terms of sentiments. So excellent and
you can see okay. So the rest are okay.
So it's ordered by excellent down to the
okays here. But if for example if Harry
Potter had a bad sentiment that would be
displayed after airplane which has an
okay sentiment. So this is how we are
going to recommend the movies to
specific users. But we'll look at the
details of this in just a bit. It's
quite an exciting part of the course
because we're going to be using AI to
extract the sentiment from the movie
reviews. But we'll see the details of
this a little bit later. Okay. So let's
minimize that. Let's go to
let's go back to our code here. And we
want to include validation code for each
of these fields.
So firstly, let's open our back tick
characters here. Oops. Okay. And now for
the JSON,
we want the field to look like this. So
it's just underscore ID. So in the JSON
that gets sent back to the client, the
field will be underscore ID. Okay. And
then we're going to include this omit
empty
keyword here.
And then we'll get back to what that
means in just a bit. So these need to be
wrapped in quotations here. And then
let's address the BSON format. So let's
include BSON here. And this is the way
the actual field is represented
within the database. So it's underscore
id. The field will be underscore id like
that. And now if you'll recall we need
to
include this omit empty
keyword. So if for example just these
fields are included in the client's
payload which is what we're going to do.
So it's just these fields and no ID is
specified.
MongoDB based on this keyword here will
automatically create a unique identifier
for the relevant user within the
database and that's what we want and
that's the reason we're including omit
empty here right so let's include the
let's include the back characters next
to user ID and we want
so let's say the JSON we want the JSON
for the user ID to
be user
ID like this. Okay.
And the BSON will be the same as the
JSON in this particular case. So we want
within the JSON, we want within the
BSON, we want the BSON field in the
MongoDB database to also be user
ID like that.
Brilliant. Okay, let's include the back
tick characters next to the first name
field. So Jason, we want the JSON to
look like this.
first underscore name
and the BSON is the same. So in all of
these fields, the BSON and the JSON are
going to look the same.
It's just the way we're setting it up,
but they could look differently if we
chose to do so.
And then let's address the validation
for the first name field. So include
validate keyword here colon and then
within quotations let's include the
required
the required uh keyword like this comma
min equals 2. So at least two characters
must be stored within this field and max
100. So we don't want more than 100
characters stored in this field. So
that's our validation for the first name
field and let's just make a copy of that
because the last name field is very
similar to the first name field and
let's just paste that here for the last
name field like this and let's just
change the bits that we have to last
name
last name
and the validation is exactly the same
required min= 2 max= to 100 and then the
email field let's do that. So,
let's include our oops, let's include
our JSON
criteria here. And we want the JSON
field to be email with a lower case like
that.
And then the BSON we want also to just
be email like this.
Oops, like this. I mean, okay. And then
um let's include the valid date keyword.
Okay, this is a required. So there must
a user must provide a valid email or
must provide an email. That's what the
required keyword signifies. But it must
be a valid email. And this is what email
signifies. So always remember that I
think gaps may introduce an error. So we
must remove the gaps like this. So these
two must not have gaps just be comma
delimited like that. Great. Okay. And
then for the password
let's include the declarative rules for
the password. So JSON
password will just be oops will just be
password like this.
Okay. VSON
also just be password like that and then
validate will be
required
and let's just keep this simple for now.
The password must have a minimum of six
characters. Okay, so very simple for the
password and then the ro. So this one
will be a little bit more different to
the other fields and you'll see why in
just a bit. So JSON and we want this to
just be called roll like that and then
bson going to be the same
roll like that and then
validate keyword. Now this is
interesting. So we want the validation
to validate that this value is either
admin or user. So to do that we can use
the one of
keyword like that. One of
like that equals
admin
space. So this is space delimited for
the one of keyword or the one of
criteria admin and then user like that.
So it can only be one of these two
values included within the role field
because we only have two roles currently
in the system admin or user.
Okay. So the next one is created at and
we'll just keep these ones quite simple.
So we just go JSON and then
let's
say created at like that. And then the
BSON is very similar to the JSON
bon. Whoops.
BSON created at and that's all the
validation we want to include there.
Let's make a copy of that.
So no validation but we want it we want
the JSON to look like this and the BON
to look like that. And then for this one
we obviously need to change the way the
field will look in JSON. So updated at
all lowerase updated at all or lowercase
for the BSON. So the BSON and the JSON
once again are exactly the same. Okay.
And then for the token
I'm actually not going to include any
validation for the token.
So we'll just include the way the token
field should look in JSON and bon
token. And that's pretty much the same.
Oops. So this doesn't have back tick
here. We need to put a sorry back tick
there. And then of course we need to
name this appropriately. Refresh
token. Copy that. Refresh token for the
bon. And then here for the genre, this
also requires special sort of validation
because we're actually going to be
diving into the relevant genre strcts
that will be stored in the favorite
genres field here. So the validation of
importance here is the validation that
we created on each of the fields within
the genre strct. So let's firstly
include what the JSON will look like
or the JSON sent back to the client. So
we want this to be called favorite
genre
all lower case favorite genres all
lowerase and then the bison
will be the same. So this is how it will
appear in the MongoDB database
bson like that there. So this field
can't be empty. So let's include
required then comma. Now this is the
important part dive. So it dives into
the value stored within the array and it
val validates each genre which the
validation criteria has been set up.
Of course not there here.
That's the validation that will be
used to validate the genre, the various
genres stored in the favorite genres
array.
So just to go over this again, the omit
empty tag is used to indicate that a
field should be omitted when serializing
if it has the zero value for its type.
So also remember when we tried to add a
movie to the movies collection in our
MongoDB database, the underscore ID
field was set to a series of zeros
because the relevant ID field did not
include the omit empty tag. This keyword
omit empty will ensure that if when we
add a user resource to the users
collection that a unique ID is
automatically provided for the newly
added user resource to the ID field. So
when we add a client payload
for a user's resource, we're only
actually going to be including these
fields. So we won't include a value for
the ID field. So we need this omit empty
tag to be present so that MongoDB will
automatically add a unique a unique
identifier to the relevant user resource
within the MongoDB users collection.
Great. And that's it. We've created our
user model.
Excellent.
Great. So now we have created the user
model. Let's create the code for
inserting the users's details entered
during the user registration process
into our Magic Stream Movies MongoDB
database. Once we have finished the code
for inserting a user's registration
details into our MongoDB database, we'll
test the functionality with Postman. I
feel that in the previous sections of
the course, we covered quite a lot of
theory and have gone over a few detailed
explanations regarding the code we are
writing. So this part of the course is
going to be more about implementing code
and we are going to move at a fairly
fast pace here so that we can get the
code written and tested for adding a new
user to the users collection within our
MongoDB database. Right, let's get
going. Right. So, let's add a file to
our controllers folder
and let's call this file
user_ontroller.go.
At the top of the file, let's declare
the code that will be added to this file
as part of the controllers package. So,
let's type package controllers
at the top of this file like this.
Excellent. So now the code within the
movie_controller.go
file is part of the controllers package
2. You can see we have declared
that the code within this file the
exportable code within this file is part
of the controllers package and we're
going to and we've done the same within
the user controller file here.
So any exportable Kobe we write here
will also be part of the controllers
package. Let's include an import section
like that.
Let's import the following packages. So
let's import this package.
gethub.com/gavlon
[Music]
digital
slashmagic
stream movies
slashserver
slashmagic
stream movies
server
for oops forward slashmodels
like that.
Okay. And it's got a red squiggly line
because we're not yet using
the relevant model within our code which
will be our user model.
Okay, great. And we can just check that
that path is correct by looking in the
go.mod file. And you can see the root of
that needs to be the same as this here.
The root of that path needs to be the
same as the name of this module. So if
we go back there,
we could actually just do this. Paste
that in there and then for slash models
to make sure that that's 100% accurate.
Okay. Okay, we are going to need a
reference to the Genonic web framework
package. So, let's include the gingonic
web framework package within our import
section like this. github.com/jin
dash Whoops. gonic/jin
like that. Excellent. And we the red
squiggly line is
appropriate at this point because we're
not using any of the functionality quite
yet within the Genonic package.
We are going to want to validate the
user registration data passed in from
client to server. So let's include the
github.com go playground validator v10
package.
Okay. So, github.com/go-playground.
[Music]
Okay. SL validator
slashv10.
Great.
Excellent. and it's dimmed out and we're
getting red squiggly line under the
package reference here because of course
we're not using the validator just yet
but we will do in just a bit. So we'll
add the other import references as we
develop the register user endpoint
handler function. So let's create the
basic structure for the register user
endpoint handler function like this. So
let's type funk register
user. We want this to be exportable. So
the first letter in the name of this
function is capitalized the R and then
let's include open and close round
brackets and then jin whoops dot
handler funk to declare this as a
handler funk an endpoint handler
function like that a jinonic endpoint
handler function if you like and then
let's return
an anonymous function that we'll
implement where we'll implement our
logic for this handler function and we
want to include the C
parameter which is of type jin.context
which will help us implement the ginonic
functionality. So for creating requests
returning responses etc. Let's declare a
variable named user of type models do
user which is of course the user strct
that we implemented that we just
implemented within the user_model.go go
file this user strct here and the next
step is to create an if condition if C
that's the generic context dot should
bind
shouldbind JSON is the one we want
should bind JSON so let's select that
there we include the amperand user
because we want to reference a
particular address in memory so where
that reference it's a pointer to memory
rather than than the actual data at at
this point. So we need to pass a
reference to a memory address rather
than the actual data. That's what the
amperand means preceding the user
variable name there. And then colon and
let's include an object there and an if
condition.
So if it's not if the
variable is not equal to null that means
an error occurred during the binding
process.
So let's send back an appropriate
message, a HTTP status of status bad
request to the client
and then an appropriate error message.
And we'll use Jin
to help us send back oops nil should
have one L here and we'll use Jin to
send back well-formed JSON to the
client. So
error and then colon and then let's send
back an appropriate message invalid
[Music]
input data. So if it can't bind
successfully here it's invalid data and
then of course we don't want code to
continue execution because the handler
function would have failed at this
point. So we include the return keyword
which makes sure that the code stops at
this point once the relevant error
message has been successfully returned
to the client.
Okay. And then let's create a new
validator. So to do that we go validate
and then colon equals the assignment
operator. And it also declares a
variable of the relevant type at the
same time. That's why we have a colon
preceding the equals there. So we go
validator
dot new like this and then we do we
check we check an error object
if the validation so validate
strruct if the valid if this validate
code
fails it will return an error. So we
need to include an object here
and then user our variable name. So this
code will return an error value
and assign that error value to this
variable here. And then we can continue
the code by adding a semicolon here and
checking the for a value to see if an
error has actually occurred during this
validation.
process. Okay. So if it has, we want to
output
an appropriate error message to the
client. So we want the status to be HTTP
dot
status. So it would be a bad request if
the inputed if the client user data
passed to this handler function fails
validation.
We deem this as a HTTP bad request. So
we send that back to the client as well
as an appropriate error message. So
jin.h.
So we can include well-formed JSON. So
we can send well-formed JSON back to the
client if indeed the validation fails.
Let's include the main error message
which will be validation
failed. And then we also want to include
the details of
why the validation failed.
So details
like this. And then we can include this
code error. And then the error object
will have an error property that we can
leverage here
in our code.
So it's an error method. It's an error
function. So it will return an
appropriate message which will contain
the details of why
the validation failed. So we can send
that back to the client and then we
include the return
keyword here to end execution of the
function because the function would have
the handler function would have failed
at this point here.
Okay excellent.
And now a very important part of the
code before we insert because if it's
past validation we want to insert it
into the database into the users
collection but
it will contain the user's chosen
password. The user variable will contain
the user's chosen password and we don't
want that password to be saved as is to
the user's collection within our MongoDB
database because that's a security risk.
So what we want to do is hash the
password. So we're going to use a
particular package to hash that
password. So let's install the relevant
package. But we first need to make sure
that we're in the root of our
application where our gomod file exists.
So we go cd server and then mad
stream
movies server like that.
Check that that's correct. Yep. And then
let's press the enter key. So we're in
the appropriate directory and now we can
use the go get command to install the
relevant package that we'll leverage in
order to hash our password before the
user data is saved to our database. So
let's go let's uh use the goget command.
Go get
golang.org
org/x/crypto
slashb crypt corrupt crypt not corrupt
okay so bcryptrypt so
golang.org/x/crypto org/x/crypto/bcrypt.
Press the enter key.
Okay. And it's doing its thing. It's
installing the relevant package.
Downloading go.org xc crypto the
appropriate version.
Excellent. Patience is a virtue,
I've been told.
So, we're getting there. And then we'll
be able to you leverage the
functionality within this package to
encrypt the password which has been
passed through within the user data
passed in from the client. Okay, great.
So let's clear the screen.
And now let's create a function
called hash password that will hash our
password that will encrypt our password
before it is saved to the users
collection. So funk hash
pass word. This is the name of our
method and we're going to pass in
whatever is passed in through the client
from the client to the server and it's a
string value and this returns a string
and an error object. So that's what
we're declaring here. Here's our return
types, a string and an object. Let's
open the curly braces like that. And
let's go hash password. So this is one
of the return values and
these are the return values from our B
crypt
dot generate from password and then we
want to include an array of bytes. So
we're type casting the password into an
array of bytes like this
password which gets passed in as an
argument here
and then
we tell it what we want to it to do. So
b
cryptdefault
cost.
Okay. And then if
so, we're checking the error. If an
error occurred during this hashing
process, we want to handle it.
And let's return.
So we're not we're going to return an
empty string here for this type here if
an error occurs as well as the error
object itself there. And then if all is
successful, we want to type cast the
hash password into a string. So we can
do that with this code string
hash password.
And the error that we'll return this
object here
should be null.
So we can just return null like that.
And that's our hash password code
written. It's as simple as that. So we
can now use that within our register
user code to hash our password before we
save the relevant user data to the users
collection. So let's go back down here
and let's implement the relevant code.
So we go hashed
whoops hashed password,
colon equals 2. And then we can call our
function that we've just written and
pass in user password like that.
And that's the password that the user of
course has passed in to our server code
here that needs to be hashed before the
user data is saved to our database. And
now let's create the context
that helps us clear up resources for
this function.
resources created to
interact with our MongoDB database. So
we're familiar with this code now
because we've implemented quite a lot
already in this course. So with timeout
and then context dot background and then
100 times. So it's going to time out.
The code will time out
in 100 seconds. So if something goes
wrong, the resources will still be
cleared up in one. And of course, we
need to include the defer cancel code
here like this.
Okay, brilliant. And we haven't included
the time package. So we need to do that.
Let's include the time package here.
Time.
And it's seems to have automatically
added context and net/http
automatically. So make sure you also
include these imports within the import
section here. Great. So that's
excellent. And then the next thing we
want to do is make sure that the email
address passed in for the particular
user for the specific user is unique i.e
has not been saved to the users's
collection. So everybody's
so members of the magic stream website
must have a unique email address if they
want to register with our system. So we
need to check that we need to validate
that.
So to do that
we firstly need to include the users
collection. So if you'll recall we've
got this code here which which opens the
relevant collection. So this is the
movies collection in this particular
case, but we want to open the users
collection within our MongoDB database
so that we can save the relevant user
data to the users collection. So I'm
just going to make a copy of that
because the code is very similar and
then just change the relevant bits.
Okay, so let's do that just outside of
any particular function. We can include
that code and we'll go user collection
like that. MongoDB collection. So
we actually need to include
our MongoDB
driver for Go packages
here in order to make use of this code.
So let's replace that with users because
we want to reference
the users collection here and we need to
now import the relevant packages here.
So we can actually copy the relevant
code here to do that. So we've already
included the relevant importation code
here within the movie_controller.go
file. So let's just copy that code. Go
back to the user controller.go code and
include those package references the
import the relevant importation code
here. Right? And you can see when I
saved that it actually automatically
imported our database package and that's
the database package that we created
based on the code that we written here.
We have a reference to database here. So
when I saved it Visual Studio Code
actually did the automatically included
the relevant importation code within the
import section here which includes our
database package. So if we look at our
database package here,
now all the exportable
code within this package can be
leveraged from within our
user_controller
code. And that's what we're doing here.
That's what we're doing here. So now
that we've got the user collection
variable, we can use that to interact
with the users collection within our
MongoDB database. So
we want to count
how many documents contain
the email that has been passed in from
the client to server. So passed in for
the relevant users details from the
client to our server code here. So we're
going to now leverage the users
collection variable here.
We don't want replace one. We want
to leverage this method count documents
ctx. So we include our context here so
that the resources will be managed
appropriately based on the code that
we've included here in this section.
Then we can filter our data using this
code. So bon like that
dot m like that. And I think we need to
include another package here.
Yeah, we need to include another
package. So, let's go back. Let's go to
the movie_controller
go code here.
Oh, no, we've already Okay, so we have
included the BSON package.
No, we haven't.
Oh, okay. So for some reason it Okay. So
when I saved the file, it actually
removed that package automatically that
I had included here because we're not
leveraging any of its function. We
weren't leveraging any of its
functionality at that point. So I'm
going to just copy that package again to
the user_controller file here. Like
that. And let's save that. And now that
we're using it, it won't delete it.
Okay, so we can bon oops bon m
and we want to use bon.mm to filter
our
result by the email address passed in
from client to server. So we go user
email here
right.
Okay. So we need to as always check the
error if
not equal to null. We need to handle
that.
So c.json JSON
http
and this will be a status 500 internal
server error.
If this code has failed, we want to
throw a status internal server error 500
HTTP error and send that to the client
with some well-formed JSON
giving a bit of context to the error
scenario. Okay, so error and then
user
already
exists. So they got to choose a unique
email address.
Okay. And then let's include the return
keyword here so that it halts execution
of this handler function.
Brilliant. And then the next thing to do
is just
Oops. No, that's the wrong error
message.
Sorry. So this error message is
failed to check existing user. So sorry
wrong error message. So failed failed to
check existing user here. So if it fails
at this point, it's actually failed to
check the email.
So the code itself has failed. It hasn't
actually performed the it has it's been
unable to to perform the appropriate
count of email addresses currently saved
to the users's collection. So this is an
appropriate error message. Okay. So now
we'll check to see if the count is
greater than zero. And so if the count
is greater than zero, we cannot add the
user
data passed in because
the user's email address already exists
within the users collection within our
MongoDB database. So let's now include
an appropriate
status which in this particular case
will be HTTP status conflict because the
email address conflicts with an already
existing email address in the users
collection jin.h H and now this error
will be user all
ready
exists like that.
Okay, so that's where we'll include the
user already exists error if the count
is greater than zero meaning that that
email address already exists in the
database. And then of course we want to
return we want to include the return
keyword here so that the code holds
because this handler function would have
failed at that point.
Great.
Okay. And then I'm going to include a
unique user ID and I'm going to
say user ID equals to bson dot we're
going to leverage the new object new
object ID.hex hex method to just create
a unique user ID for the user.
Okay,
great.
And then usercreate
at will equal to time dotn now. So we're
just
going to include what the time is now
for this particular field at this point
at the point where the user data is
being added to the users collection. And
the user do update at property or uh
field should also be set to whoops not
that.
Copy that time.now.
Great. And then
lastly,
we want to
add it. Add the user data. If it code
gets to this point, we're in good shape.
And we want to use the user collection
variable
and the insert one method to insert
the user
into the database.
Okay, brilliant. And then we need to
check the error object. If error is not
equal to null, yeah. Oops.
If is not equal to null,
let's handle the relevant error. So
c.json
http
status
in internal server error and let's
include let's use jin gonic to include
an appropriate error message. So it's
going to be
error colon and then failed
to create user like that. And then of
course we include the return keyword. So
that execution holds at this point. And
you can see there's a red squiggly line
under result because we're not yet using
the variable. And at this point we can
return the result to the user to the
client calling code like this.
JSON
http
status
created. So it'll be an HTTP 2011
status that we'll return to the client
as well as the result there. And you can
see the red squiggly line has
disappeared.
Okay.
And one thing we didn't do is we didn't
assign the hashed password.
We hashed our password, but we didn't
assign it to the actual
relevant user password field. So we need
to do this equals to hashed password.
So that was nearly
a mistake there. Okay. So it's got a
warning. Let's just check this value of
error is never used. Ah, okay. So, we're
not checking the error at this point.
Yeah.
Okay. So, we need to check the error.
Okay. So, if
error not equal to null close brackets
and we can
that would be a status internal server
error. So let's just take make a copy of
that and paste that here. Error
unable to hash password
and the warning squiggly lines, the
yellow squiggly line disappears. And all
looks good with our function. And that's
it. We're in good shape. So next we're
going to test our function through
Postman.
Great. So firstly, let's create a route
for our register user handler function.
Let's go to the
main.go file here. Let's just create a
duplicate of this.
Copy paste and let's map our
register endpoint. So for slashregister
is our endpoint here.
And we want to map it to the function
we've just created
which is register user like this. There
we go. Excellent. So we're now ready to
test our register user functionality.
the functionality we just created here,
which essentially
saves a user's registered details to the
users collection within our MongoDB
database. And we're doing lots of other
things here. We're hashing the password
before the users details get saved to
the users collection. And we're also
checking for any conflicts in terms of
the user's email. Each user must have a
unique email. Great. So now we are ready
to test our register user functionality
through Postman. So let's launch Postman
here. I've got Postman loaded here
already. Let's set the drop-down list
here to post because we're about to test
a post request.
Let's enter the URL. So our end point is
for slashregister. We've got the base
address here
already in place. And then we just need
to include for/register which is the end
point maps to our register user handler
function. And then we just select the
body option here and raw. And we need to
populate this text box with the relevant
JSON
to register a new user. Now I've
actually already
created some sample data that we can use
to test our functionality. So let's go
to GitHub
specifically my GitHub repository.
GitHub and then we want to go to so this
would be github.com/gavlon
digital and then we want to go gavlon
digital magic stream here and then click
on the magic stream seed data
folder here and then I have prepared
this data here. So add test user doc and
all we want to do here is copy
this code oh sorry this data here into
our text box here and then to test our
functionality we simply press the send
button. Let's do that.
And
of course that's not going to work
because I haven't run the code.
Okay, so let's go back there and let's
run the code. So let's go to our
terminal window here and type go run
dot.
Now the code is running. Excellent.
Listening on port 8080. And now let's
try that again. So let's go to Postman.
Press the send button.
201 created. That looks good. Ah, okay.
We have a problem. So, we got a series
of zeros here again. And why hasn't that
worked? I thought we had taken care of
this bug. So, let's go and look at our
user strct and see if there's anything
funny there.
Okay.
User model.
And if we look here, there's a gap
between this comma and omit empty.
Remember this was the problem last time
when we tried to save a movie to the
movies collection. It did the same
thing. It created a series of zeros for
the ID and we want this to be a unique
ID field. So
what's actually happening here is
because the formatting here of these
tags is very temperamental.
It's caused a problem because we've got
a gap between this comma here and the
omit empty tag. So let's just remove
that gap.
Um, save that. I'm just going to cancel
this by pressing Ctrl C. Let's clear the
screen. Let's run the code again. Go run
dot press the enter key. And now we're
running it again. And hopefully this is
will resolve our issue and we'll the
result will
contain a unique identifier for the
user that we are saving to the database.
But firstly, let's delete the data that
was created cuz it did actually create
a document within the user collection.
You'll see here reload data,
but this object ID is incorrect. This ID
that uniquely identifies the user is a
series of zeros and that's not what we
want. So let's delete this record by
pressing delete there. And then
confirming the deletion. You see here
document flagged for deletion. And then
let's confirm the deletion by pressing
the delete button here. And now that
record's gone. So if we reload data, we
can't see that record anymore for Craig
Denton. Great. So let's go back to
Postman.
You can see I'm still running the code
here. I haven't stopped the code from
running.
And let's try that again. And we're all
set up here. Our data doesn't change.
That data is fine. And see the password
here. Password one
exclamation mark. Now this password I'm
using for all the users just to make
things simple. So for all the users,
you'll be able to log on with this
password here. But you'll see when it
goes to the database, it will be hashed
i.e. encrypted
as a
security measure. You shouldn't save the
password as is to the database. So, we
want this to be encrypted and um we've
done that through this encryption code
here.
We're actually hashing the password. We
created a hash password function here
using the b-rypt package here to hash
the password. So when we look at the
database, we want to see that the
password has been hashed once we've
saved the relevant data to the database,
of course. So let's go back to Postman
and all we need to do, we're all set up
here. We've got the post drop-down set.
We've got the correct path to the
endpoint, the register endpoint, and the
data we copied from GitHub. And we're
ready to test. So let's press the send
button here.
201 created. That looks good. But is our
ID unique now? And it is. So that did
fix the problem. And of course the
problem was
this silly little gap was caus a gap
between a comma here and omit empty tag
was causing the problem. It's a bit
temperamental in terms of formatting of
these tags. So we got to be mindful of
that with this logic here. Okay.
Brilliant. So let's take a look at the
at compass to see if our data has been
created. Our resource has been created
within the users collection. So let's
reload the data by going view reload
data. And there it is. And it's got a
unique ID here, which is fantastic.
Great. So we're in good shape. That's
worked.
One more thing I'd like to test is if we
now try to add that same record,
we should get a conflict with the email
because each user within the collection
within the users collection must have a
unique email address. And of course,
we've just seen that Craig Denton with
this email address has been saved to the
users's collection as it were. So if we
send that same data to this endpoint, we
should witness a conflict here. So it
should send back a HTTP conflict
response, HTTP response to us,
uh HTTP status response and it and that
will result in the data not being
duplicated within the users's
collection. So we don't want to save
another document that contains an email
address that already exists in the
users's collection. So a conflict should
occur when we hit the send button here
because this data contains an email
address that already exists within the
users's collection. So let's try that.
Let's press the send button.
409 conflict. Brilliant. So that's
working. user already exists as our
error message sent in JSON format to us
to the client code. So
you can see that here 409 post it's
resulted in a 409 conflict. Brilliant.
So the data won't be duplicated if let's
just confirm that by going to compass
here and we should only have one record
for Craig Denton.
Excellent. So our code that handles the
conflict has executed successfully.
And let's look over here register user.
You can see here the code for that
count documents filtering on email. If
the count is greater than zero, error
user already exists. Brilliant. So now
we're ready to write the code to write
the login code on the server side.
Excellent.
So now that we've registered a user
successfully in the system, let's create
the user login functionality. So let's
first open the user_model.go
go file here
and let's create a model for storing the
login data that will be passed from
client to server once the user has
submitted the user's login details in
order to authenticate the user's
security details with the magic stream
website. So let's create a strct named
user login. Here we do that by typing
type.
User login is the name of our strruct
and then we need to include the strruct
keyword here. Open our curly braces and
we want an email field like this as
string and then we can include the
relevant tags for our email field. So
let's open back to characters like this.
Include the JSON keyword here and then
the name of the field that will appear
within the JSON data and then validate.
Let's include some basic validation
here.
So it's required
field is required, email must be in a
valid email format. And then let's
include the password field like this
string open back tick characters. So the
field will appear as password within the
JSON data validate.
So it's obviously required,
min equals to six. So must contain a
minimum of six characters the password
in order for it to be valid. Okay. Okay,
so let's create the user response
strruct which will be a subset. It's a
DTO and it's a contains a subset of the
fields contained within the user strct.
So let's type type the name of the strct
is user response
like that and then of course the strct
keyword open and close curly braces like
that. This will not actually map to a
BSON field. So I'm going to remove that.
So the BSON code doesn't need to be
included here because it's doesn't map
directly.
It's kind of a subset of the user strct
and doesn't map directly to a document
or a collection of documents within the
MongoDB database. So we don't need that
bon those that bon tag there. But yes,
we do need the JSON tag to stipulate how
we want our JSON data to look. Okay,
perfect. So, right. So, within the user
response, let's create a field called
user ID as string
as string. Let's open the back tick
characters like this. And let's include
JSON and within
quotations user
ID like this. Let's include a first name
field like that. Camel case is what
we're using. Not camel case. We're using
Pascal case where each word has its
first letter capitalized. First name.
Each word within the variable name or
the field name in this case has its
first letter capitalized. This is Pascal
case. Okay. String. And let's open
our back to characters and include the
JSON tag here. And we want our JSON to
look like this. First underscore
whoops name. Let's just create a copy of
this. Paste it just below. And this will
be
last last name.
Okay. And let's alter this tag to be
last name.
And then let's include a field named
as string. Let's open back to characters
here. JSON
colon email like that.
Okay. And then roll
string
open back to characters JSON.
We want the JSON to look like this. Roll
all in lower case. And then favorite
genres.
Okay. And this is an array of the genre
type. This is our userdefined strruct
that we created earlier in the course.
Okay. And let's include the relevant
JSON tag here.
And we want it to look like this within
the we want this field to be named
like this within
the JSON data sent back to the client.
This model is a DTO which stands for
data transfer object. So DTO stands for
data transfer object. It's a design
pattern used to transfer data between
software application layers such as
between a backend and front end or
between services in a distributed
system. Here are some key
characteristics of a DTO. It holds data
only. It typically contains fields,
properties, getters and setters and no
business logic. It is a flat structure
usually simpler and flatter than domain
models. It is serializable designed to
be easily serialized to JSON, XML, etc.
for transmission over the network. Some
common use cases are as follows. API
responses returning data to clients
without exposing the internal domain
model. So in this case the internal
domain model would be user
and this is our DTO user response
data aggregation combining data from
multiple sources into a single object
input validation accepting structured
input from external sources for example
form submissions. So by using a DTO
we're only exposing the data that needs
to be exposed to the client. Great. So
let's write the code for our login user
HTTP endpoint handler function. So let's
open the user_controller
file here and let's create the
infrastructure for our handler function.
So let's type funk
login user like this
then jin dot
handler funk. So we're using gingonic
to establish this function as a handler
function an endpoint function handler
and we're leveraging gingonic the
jinggonic web framework for this purpose
and then let's return the logic for this
particular handler function. So we do
that by return funk and let's open
brackets c and then this is of type star
jin. context like this. Let's open and
close curly brackets for our anonymous
function. We're now going to implement
the anonymous function that we're
returning from the parent login user
function.
We need to make sure that
whoops we haven't got open brackets
there. So we're going to open that
bracket there and close it down here.
Okay. And then let's create a variable
that's named user login. And this is now
in camel case where user has a lowerase
letter starts with a lowerase letter and
the next word login starts with a
uppercase letter. So this is camel case
and we're defining it as type models dot
user login like this. And of course user
login we've just defined
within our user_model.go
file. It's a userdefined type that we've
created.
Okay,
great. So we've created our variable
user login. And let's bind the data sent
in by the client which should only be
the user's email address and password
to the variable we just created named
user login.
Okay, so we go if so we're going to
check the return of a method provided to
us by the context of jinggonic which is
called should
bind whoops bind JSON. So we're checking
the error
of this binding functionality and of
course the amperand
user login points to a particular
address in memory rather than containing
the data itself. This refers to a
pointer in memory and that's denoted by
this amperand character preceding the
variable name. And let's include a
semicolon
and then an if condition. If error,
we're just checking if the return value
is null. If it's not null, we need to
handle the error. Should bind JSON.
Sorry, this has to be capitalized.
JSON. The red squiggly line's gone away.
Okay, so C.JSON, let's handle the
exception.
http
dot we want to send back a status of bad
request here
comma jin.h
error
and the message we'll send back is input
oh sorry invalid
input data
like that. Okay. Okay. And then we need
to include the return keyword so that
execution ends at this point here. The
function would have failed at this point
here. If there's a problem with binding
the data passed in from the client,
which is the user's credentials, the
email address, the user's unique email
address, and the user's password is
passed in here. And we're binding that
data to our user login strct here. The
variable defined as the user login strct
here. And if it fails, we're handling
the exception there. So the next step is
to find the user within our users
collection within our MongoDB database
based on the user's credentials. Let's
define a variable named found user. But
I'm actually going to set up the context
for our query where we're finding the
user within the users collection. So
we've done we've written this code many
times and this is just for handling
resources. So even if our query fails
for example within this function that
the relevant resources are freed up with
timeout like this open brackets
there and let's include context dot
background
and let's so we want all the resources
to be cleared up in 100 seconds. So time
dot second like this
and then we also need to defer
cancel with this code here.
So let's define a variable named found
user as the models do user type. So we
type var
found user
models do user like this.
Okay. And then let's include our query
to our MongoDB database. So we can do
that like this. So
[Music]
assignment operator
users collection.
Oops, we don't want that. So it's users
collection.
Or was it user collection? What have I
defined it up here as?
User collection. Okay. So it's not users
collection. And it's user collection.
User collection dot
find one. We want to find one document
within our user collection. Let's
include the context here. And then let's
filter
our query
m on
the email the the user's unique email
which is being passed in from the client
because the user is logging on in this
scenario. So while the user so when the
user logs on and presses the login
button the user's email address and
password are sent to this function here
and we are filtering on the user's
unique email data
in order to find that user within the
MongoDB database within the users
collection. So find one email and then
user login. We've bound email. We've
bound the what's inputed from the
client, the email and the password to
this memory address here. We've bound to
this variable. So it's pointing at a
particular memory address with the
relevant client data.
And we're now querying on the email
address to find the data. And if it's
found, we want to decode that into found
user. Our found user strct
like that, our found user variable
email. Of course, the comma is the issue
there. So, let's replace the comma with
the colon. Okay. And then let's check
the error
and handle it if an error indeed exists.
So, error. So this is the error caused
potentially caused by the find one
functionality here where we're trying to
find the relevant user within the users
collection. So
if error null if error is not equal to
null whoops
open our
curly braces and let's handle that
particular exception. So we want to send
back HTTP
dot status
unauthorized.
Okay, we can't authorize this user
because we can't find the user within
the database.
And then let's use jin.h
to hand to send back an appropriate
error in JSON format to the user.
invalid
email or password and then let's include
the return keyword here. So that
execution is halted at this point.
So the function would have failed. If
this method fails, it's pointless
carrying on with the execution of this
code. So we handling the exception and
sending back an appropriate error
message to the client. Invalid email or
password. Great.
So the next step is to compare the
password passed in from the user during
the login process to the user's saved
password within the MongoDB database.
But of course the user's password is
stored in the database as encrypted
data. So we can actually see that if we
go to compass,
let's look at our users collection
quickly before we carry on writing the
code.
Let's look at our users collection
quickly. So let's go to Magic Stream
Movies. If we go to the users collection
here, you can see that the password is
hashed. So this is pretty meaningless to
a human. You the user can't log in with
this password. It's deliberately being
encrypted to increase security. So
nobody can steal the password if they
somehow get access to the back end to
the
data store here.
Okay.
But we can use a particular package
the brypt package to compare the hashed
password with the password passed in.
And let's look. I think we've already
imported the relevant package. Let's
have a look up here. And here it is
brypt. So we don't need to install
anything or import anything. It's
already done for us. We've used it
before. So let's write the password
comparison code. So
equals to b crypt that's the package
we're using for this comparison code
compare hash and password. So it's doing
it for us. So we don't have to do
anything write anything fancy because
it's already encapsulated within this
functionality here within the package
that we're using here. So we want to
type cast
found user password into a bite array
here.
Comma
and then this is the use this is what's
being passed in. This is the password
that has been passed in from the client
during the login process
dot password and we need to also
convert this into a byte array and we're
using this code to do that.
So we're comparing like for like here
essentially compare hash and password
and that takes care of all the details
of comparing a hashed password to a to a
password that hasn't been hashed. So
it's been done for us using the brypt
package here.
Okay. And then let's write the code to
check the error if something went wrong
during this hash comparison process.
Nil. Let's go here. So let's type c.json
and let's pass back http
unauthorized.
If the password doesn't match up,
this user cannot be authenticated and we
sending back
an appropriate message to the user as
well as a HTTP status of unauthorized.
So error like that
and it's the same error message
essentially as before. Invalid email or
password like that. And of course we
need to include the return keyword here
so that execution halts at this point
because there's no point in executing
any code that might exist after this
code here because an error occurred
during the comparison process. is the
compare comparison of the client's
password
that the client has passed in during the
login process to the password saved to
the relevant users document within the
users collection.
So it will fail at this point and no
further code will execute.
If no error occurred and the user's
password matches the password saved for
the user in the user's collection within
the MongoDB database, the user is
authenticated and our code must now pass
back to the client an access token. So
what we are going to do now is create
code for generating an access token and
a refresh token which are generated once
the user has been authenticated. So the
access token is analogous to a key that
can open the doors to protected
resources.
So this is great for when you have an
application with disparate loosely
coupled components that need to securely
communicate with each other. In our
application here, we have two disparit
loosely coupled components. A client web
application front end which we'll create
later using React and a serverside web
API component. This is the component
that we are currently creating using go
engine gonic. So when the user logs in
and is authenticated an access token is
generated on the server. This is
functionality we are about to create.
The token generating functionality if
you like. The tokens are then passed to
the client. The client code can then
send the access token to the server
along with the relevant HTTP messages in
subsequent interactions and won't need
to provide the user's credentials i.e.
email and password with every
interaction with the server because the
access token can now be passed from
client to server which means through the
access token the server code will know
as it were what authorization privileges
the relevant client has in relation to
the relevant endpoints some of which
will be protected. So we are going to
write the code for generating the access
tokens within a separate file. So let's
create a file within the utils folder
named token_util.go
like this. Okay. So we've got the utils
folder here. Let's right click go new
file and we are calling this token
util like this
go.
Great. So at the top of the file let's
declare the name of this package which
will be util. So we just type package
utils like that. Let's create an import
section like this. So import and we've
done all this before and then we just
open round brackets like this and we can
include our imports here. Let's import
the MongoDB driver for Go packages here
like this. So we're going to we've
already imported these packages before.
So let's just go to the movie_controller
file and just copy these packages, these
package references where we're importing
these MongoDB for Go driver related
packages here. Let's copy that to our
clipboards. Let's go to token util here
and paste the relevant importation code
here. Excellent. Okay. Okay. And we've
got red squiggly lines under here
because we're not currently using any of
the functionality associated with these
to these packages here. We want to also
import a package that references our
database
functionality here where we're
connecting to the relevant database and
opening a connection to the relevant
collections here with this reusable
code. So we want to include a reference
or we want to import this package into
our token util file here or our utils
package rather. Okay. So let's press
enter here and then we're already
importing the relevant
database related
package here.
So let's just copy this. Let's go back
to our token utils file and paste it
here.
Great. And to generate the tokens, we
need this package.
github.com/golang-jwt/jwt/v5.
Okay. So, we actually need to install
this package. So, firstly, let's
navigate to where the go.od file is. So,
cd
server
slash
mad whoops. Yep. magic stream movies
server like this and then let's run the
relevant goget command. So go get
GitHub here's the path to the package
that we want to install com/go
and we need this particular package
JWT and we need this particular package
for generating our access tokens our JWT
or Jot tokens jwt
slashv5
five and let's press the enter key. That
looks good.
And hopefully that installation process
will go as expected.
And then we can proceed with generating
our tokens or writing the code for
generating our relevant tokens. And that
looks great. Excellent. So let's clear
the screen here and let's import that
package now. So to import the package,
we just include this line of code here.
github.com/golangjwt/jwt
[Music]
slashv5
like that. Okay, so we're creating a
model here and we're going to call this
sign
details. This is all part of generating
our token. So just bear with me here and
follow along please. So we got email as
string
whoops
first name string
la whoops last name string. And these
details will all be encoded within the
relevant JWT. This is why we're creating
the strct. Then we got RO. So this can
be admin or user. So let's define it as
string. is the enter key. User ID as
string and then lastly
JWT
dot registered claims. So we're
including a strruct within a structure.
Basically this is the
JWT
strct
within this package here. So actually we
need to alias this here. So let's
include JWT here just for readability
purposes. So we can reference that
within our code here. So JWT
dotregistered claims and we're basically
including another strct within a strct
and that strruct resides within this
package here. So our token is going to
be generated from this information.
email, first name, last name, RO, and
user ID. And then registered claims,
which is actually a strct that we're
including within our signed details
strct here. JWT.registered claims is a
strct provided by the
github.com/golangjwt/jwt/v5
package. It contains a set of standard
claims defined by the JWT specification
RFC 5719.
And these fields include the issuer,
which is who issued the token, the
subject, the subject of the token,
usually a user ID, audience, the
intended recipients of the token,
expires at when the token should expire,
not before, when the token becomes
valid, issued at when the token was
issued. ID unique identifier for the
token can be used to prevent replay
attacks. Okay, so let's create code to
read a secret from an environment
variable.
So to do that,
let's go to the env file and include an
appropriate environment variable.
And our secret key will just be
secret
underscore key equals to and we're just
going to call this your
secret
key. You obviously want to include
something a little bit more
a little bit better than this. But for
now, this is just a placeholder to
indicate where we are storing a
particular secret key which will be used
to encode our token to create our token
as part of the token creation process
and you'll see how we do that in just a
bit. Okay, so we've included our secret
key here. Let's go back to our
token_util.go
file here. So let's create a variable
called secret key as string and set it
to our secret key which has now been set
within our env file. Okay. So
we go var
C whit
key
as string equals to and then we need to
include the OS package
dot
get env
and then
the environment variable that we want to
read is secret key. So we can just copy
that. Go back to our token util.go file
and copy it there. So we're now reading
the secret_key
environment variable
and saving it in memory within this
variable here which has been defined as
string. Now we need to import the OS
package. So let's go up here and within
quotations include OS like that. Okay.
Excellent. Great.
So, I've noticed it does this annoying
thing when you save your file and some
of these packages are not being used
that it actually just uh Visual Studio
just deletes them automatically. So, if
I go save, it deletes them because
they're not being used. So, um
that can be a little bit annoying
because we haven't had a chance to use
the functionality within those packages.
So I'm just going to undo that and just
just be aware of of the fact that it
will remove those if they're not being
used and you go Ctrl S to save the file.
Okay. So I want to keep those packages
there and we're going to start using the
functionality within these packages. So
those red squiggly lines will disappear.
Great. So let's create the generate all
tokens function which contains the
parameters email as string first name as
string last name as string ro as string
user ID as string and returns signed
token as string signed refresh token as
string and as error. So let's create
that function definition. So let's type
funk like this generate
all token. So it'll be an access token
and a refresh token. We'll discuss what
a refresh token is a little bit later,
but for now let's just create the
definition for our function email.
So, comma first name. And you can see
all these parameters
are established using camelc case last
name
ro
user ID.
Okay.
And we can include string. So we only
have to define it for the last
parameter. And all of these parameters
are automatically defined as string
because we've defined the last one as
string and we haven't included
types for the preceding parameters here
within our function definition. And then
open brackets we go string string,
error. So we're returning two strings
here and an error object from this
function. Let's open curly braces like
that. Press enter like that. Let's
create a claims variable and assign it
to the science details structure with
its field set to the appropriate values
passed in to the generate all tokens
function. So let's go claims. So we're
defining a claims variable using the
assignment operator like this which both
defines the variable as the relevant
type. So it infers the type as well as
it's also doubles as an assignment
operator. So let's include what we're
assigning this variable to and that
would be amperand and we know the
amperand is refers to a memory address
signed
details like that. open the curly braces
like this
email and then let's assign that email
field to the email value passed as an
argument to this function.
Okay. And then let's go first name like
this colon and then the first name
parameter value. Do the same for last
name.
last name
and then we want to assign the role
like this and then user ID
oops
user ID
and that should not be capitalized ID
like that
and then registered claims and we can
actually define the registered claims
that we want to include
registered claims. So this is a strct
and we can define what our registered
claims should be here. So we've defined
the strct we want to include and we're
going to include the relevant fields
that we want to include within our
claims here. So as you can see what's
happening with this token is we are
encoding the users specific
details into the actual token.
Okay. So the issuer is the app that's
issuing the token. So we go magic
stream. It's not issuers, it's issuer
like that. And then issue issued at like
this colon. Then let's use our alias for
our package that we recently imported.
New numeric date time dot now.
Okay. Okay. And let's just make sure
that time
time should in fact be
imported here. So let's include the time
package here
before it complains. So we got time now
and then comma here and then let's go
expires at and include the relevant
expires at value like this time do now.
And let's add 24 hours.
And this is when when it will expire. So
it expires within 24 hours. We might
change this a little bit later, but for
now, let's just say 24
multiplied by time dot hour. So it
expires in 24 hours. This particular
token.
Okay.
And that looks good.
Excellent. And the underline there is
because we're not yet using it.
Okay.
And then let's create a variable under
this claims variable. Let's create a
variable called token.
Token
assignment operator JWT. I know this is
very involved, but just bear with me.
This is pretty much a standard way to
generate the tokens. It'll be worth it
in the end. Okay. So new with claims and
we're now passing our claims which just
contains relevant details about the user
sign and this is the algorithm that
we're going to be signing our token with
which is
uh
ES256
here
and we actually want HS I think. So
let's go sign. So HS 256. So this is the
algorithm that we're using to sign the
claims.
And then let's include the claims
variable there. So this is our token
generation code. And then we want a
variable called signed
token
like this,
cuz this method is going to return two
values.
then our assignment and declaration
operator and we go
token dot
sign string. So now we're going to sign
what we've got in the claims
with
our secret key
byte. So we are
converting the secret key from a string
into a byte array for this method to
work. So sign string accepts a byte
array. So we must convert the secret key
string into a byte array. And that's
what we're doing here.
Great. So we're getting somewhere. And
then and then and then we do the usual
go thing. We check for errors. So not
equal to null.
And this is the thing I had to get used
to coming from C to Go is the way errors
are checked. It's very procedural rather
than using try catch code like you would
in JavaScript C++ or C. Okay. So let's
write return
[Music]
and then okay so if an error occurred
we're we're returning
nil nothing an empty string for the
first
one. So the token will have nothing in
it there. And the refresh token will
also be set to an empty string. So an
empty string for the token, an empty
string for refresh token, and then the
error object which will be useful for
the client code. So the client code
calling this function will be able to
handle the relevant error sent back in
the case where an error occurs.
Great. So then for the refresh token is
very similar. Okay. Okay. So, we're
first just going to go to the N file
here and we're going to do a similar
thing here. So, we're just going to
create a key for the refresh token.
Secret
reresh
key like this. Your refresh key.
Of course, you'll probably want to
include something a little bit better
than this, but for now, it will serve
our purposes. And let's go back to this
file
here.
And then all we what we can do here now
is just duplicate this code for the
refresh token.
Okay, so let's do that.
So actually we can just duplicate this
code. I know this isn't very good, but
just for now to get to the next step.
So, we can just generate our tokens. We
can always come back and clean up this
code a little bit later. So, let's call
this refresh claims. It's obviously
complaining because we got duplicate
variable names now because we've
duplicated the code to generate the
token. We're generating the refresh
token now and we're duplicating the code
that we created for generating the
access token. So we just need to change
these variable names so that all these
red squiggly lines will go away. It's
obviously complaining about duplicate
variable names. Okay. So then we can
just change that there. We can call this
refresh
token and use camel case for our refresh
token variable and then signed refresh
token here
token and then of course
our secret key which we need to read
here now. So we've called it something
different. So go secret
re
refresh
key. Let's just double check what we
called it there. See? Yep. Secret
refresh key. And we can just paste that
like that there.
Okay.
Token refresh token. That's these red
squiggly lines are just because we we're
not currently using the variable. And
same with that variable there.
Okay. All of that looks pretty good. So
that needs to change to refresh token.
Refresh claims signed refresh token.
Okay, we're checking the error there to
make sure no errors have occurred during
the refresh token generation process if
you like. Okay, so then
um
checking the error.
So if no errors have occurred, the only
thing left to do here is return the
token, refresh token, and hopefully a
null for the error. But if there's an
error, the tokens will
contain an empty string and the error
will contain a value. But we're hoping
that the
tokens,
the signed tokens,
the access token, the signed access
token and the signed refresh token do
contain values in which case the error
will be null and no error has occurred.
Signed refresh token null. There we go.
Whil
and now do we have any red squiggly
lines? No. And so it's cleared out some
of our references here because I saved
it. So that those were the
MongoDB for Go driver related packages.
Let's cleared those out cuz I haven't
yet used those. But we will be using
those because we want to save these
tokens ultimately save these tokens to
the database next to the relevant user
within the relevant user document within
the users collection. Okay. So let's
actually write the functionality for
that now because we want to include it
within this file. So we're going to
write the the actual database related
functionality now. So I'm just going to
bring back those package references.
It's a bit annoying that it just deletes
them um automatically
uh because we're currently not using
them when you save the file. But okay,
great. And see I just saved it and it
went away. So undo that. We got our
packages here and I just want to open
and it's also deleted the database
reference too which we need. So let's go
here
database.
So we just need this database
importation code here and let's copy
that here. And then let's open
a connection to the users collection.
Actually, we've got the code here
already.
Um here.
So, let's copy this code here. Let's go
to token_youutos.go.
Paste it there. So, now we are using our
database package. So, let's create the
function to save
the token. So, we we've generated the
tokens in this code here. We're going to
be calling that from the relevant
controller code. We'll generate the
tokens and then we want to update
the users
collection with the tokens, the relevant
user document within the users
collection. So let's create our
function. We're going to call this
function update all tokens. So let's
type funk
update all
tokens like this open brackets. And we
can go user ID like that. Token like
this,
comma, refresh. Whoops. Refresh
token like that. And then just define
this last one as string. This last
parameter string, which by default will
automatically define these parameters
also as string.
And then we just want to return an error
object. So go as error like this.
Open our curly brackets and let's write
the logic for this. So firstly I'm just
going to create the usual
resource clearing code if you like the
housekeeping code that when time when
the timeout is reached it clears up all
the resources
created when using the forgo
driver related code.
So width
time out.
Then we want context.
Oops, not tudo dot background. We of
course need the context package
imported which which is it's done that
automatically for me there.
Okay.
And then let's establish the timeout.
And let's just use our usual 100
times time dot second for the timeout.
Okay, so it'll time out in 100 seconds.
And then we need to include the defer
cancel code here. Okay, brilliant.
Okay, and then
date
at
we don't need it to return the second
value here. So we just include a
placeholder here which is an underscore
character and then we want to go time
dot
pars
time dot
r
and this is just the standard
rf
c 3339 like that. Let's go time dot now
time dotn now
dotformat
and then
we want it in a particular standard
format like this. Okay. Now we're going
to create a variable that assigns the
relevant values to the relevant fields
within our relevant user document. So
update data let's define those fields
and we can go bson dot oops bson dot m
like this
and then use the set command like this
which is dollar symbol set like that
colon and then another bson
m
like this and then we can assign the
relevant values to the database fields
like this. Token tokens being passed in
as a parameter value here and then of
course refresh token
like this.
Okay, refresh token like that
and then update at which we've
established here
and this is really for auditing
purposes. So when a document is updated
in the database, we have the timestamp
next to it when it was updated
and we can go updated at like that
there. So we've defined which fields
will be updated in the database and with
which values. Okay. And we need a comma
here. Great. So we're using this
variable to interact with the relevant
database
collection which is our users
collection. So let's include underscore.
We don't need a value returned for the
first
return value but we want the error
returned if an error occurs during this
update process. Let's go update one like
this. So we're calling the update one
method using the MongoDB forgo driver
related functionality. So let's include
our context here. And then
user I whoops user
ID field. We're filtering
this particular update. We're filtering
on the user's ID. So we're targeting a
specific document within the users
collection based on the user's ID.
Great. And then the update
data variable
that contains how we want our document
updated, the fields and the values for
those fields.
So this code here, this set command will
set the relevant fields in our document
with the relevant values. and we're
using the user ID to target a specific
document. So it's a specific document
for a specific user and we're updating
that particular users tokens, the access
token and the refresh token. So that's
what this update one functionality is
doing here. And then the usual go thing
where we check the error.
Hopefully, it'll be null, but if it's
not, we'll just return the error to the
client calling code. And then
if it gets this far, we return null for
the error. Like that. And that should be
good enough to update our database.
Okay. Excellent. So we've now written
the functionality for updating the
relevant document filtered on the
relevant users ID
and we are ready to create our calling
code within the user controller. So
let's go back to the user controller and
back to the
login user function and we can proceed
from here. So we can now call our
generate all tokens
method
from within our
login user function
to generate the relevant tokens. So
let's firstly do that. So to do that
create a variable called token and then
refresh
token
like this and then the operator the
assignment operator u utils
dot and I assume it's already imported
utils for us here.
Okay. No, we haven't yet imported the
utils
package. So to do that, let's copy that
there. Enter
utils like that. There. Great.
So now we should be able to go utils dot
generate all tokens like this. And then
we can pass in the found users relevant
fields like this. So found user email.
So these are the fields that are used
for creating the relevant tokens. So
then found user dot first name
found oops
found user.loast name found user
dot
roll
like this.
and then found
user do user ID. And that's the last
parameter we need to include there. And
there's a red squiggly line because
we're not currently accounting for the
third
return value, which is the error if one
has occurred. Okay, perfect. So that
looks good. And then we've got to go
through the motions here and check the
error. So if
not equal to null. So that obviously
means an error has occurred. We can
handle that using the
gingonic context. So go there http
status
internal
server error and then we can return
wellformed JSON using jin.h like this.
go
error
and then fail to generate tokens.
So hopefully the code doesn't reach this
point and the tokens are generated as
expected. And then we use the return
keyword at this point because we don't
want the code to continue executing.
Okay. And then at this point in in the
code, we actually want to update
the tokens within the relevant user
document. So to do that, we go utils and
we wrote update all tokens. That was the
last function that we wrote within the
utils package. So update all tokens. We
need to pass in the user ID. So found
user user ID like that.
and then token and refresh token token
and refresh token like that. So that the
relevant tokens are saved to the
relevant
user document within the users
collection.
Okay. And then once the user's logged
in, we want to pass the user response.
Now this is the DTO we created
earlier. So if we go to models here,
user model, we've got this user response
which with which is our DTO. And what I
didn't include in this DTO are the
tokens. At this point in the application
development process, we're going to
return the tokens. But in subsequent
iterations of the function, we're
actually going to save the tokens to
what's known as HTTP only cookies. But
for now, we'll return the tokens to the
client through the user response DTO.
And I'll explain more about why we are
doing this for security reasons a little
bit later in the course. So let's
include token here
as string and we want the JSON to be
straightforward
just
JSON
colon
and token
and then we want refresh token which is
very similar
like this
and
of course this will be called refresh
token and we want the JSON to look like
this using snake case with an underscore
separating the words and this is of
course Pascal case where each word
each word's letter is capitalized. Okay.
And I think that's looking pretty good.
And then we can go back to our user
controller.go go file and we can return
that using the genonic context. We can
return
that user response
strruct to the user in JSON format. So
to do that we type c.json. So if the
code gets to this point all has been
successful. We probably should be
returning an error at this point and
handling it actually. So let's just go
update all tokens. Let's go to that
definition.
Yeah. So, we're returning an error
there. So, we need to handle that error
first. So, let's go back to
our code here and let's handle the error
first. So, if error not equal to null
like this.
And how should we handle this? We should
just handle it in a very similar way.
How we're handling it there.
failed to update tokens let's say here.
Okay. So we're passing an appropriate
error and an appropriate
HTTP status back to the user here which
would be internal server error 500.
Okay. But if the code gets here all is
successful and we can return a HTTP
status of okay. So http dot status
okay like that comma models dot and then
our user response which is our DTOR here
and then open curly braces and user ID
found user dot user ID
first name this is what we're returning
to the client react code
found
user
dot first name and then last name.
Found user.ast name
and then email.
Found user dot
dot email and then roll
found. Whoops. Found user dot
dot roll.
And then favorite genres. We also want
to return these found user.
And you guessed it, dot favorite genres
like that. But we also need to return
our token. So token
and we've got our tokens here. So token
token like that. comma
refresh token.
Refresh token. And that
sure that was quite a lot of code, but
that's how we generate our tokens.
That's how we authenticate the user and
generate our tokens. And at the at
present, we're just going to send them
back to the client. But later on we'll
look at how we can save the tokens to
HTTP only cookies because it's a more
secure method of passing these tokens
between the client and the server.
Excellent.
All right. So we now are ready to test
our login functionality. But something
doesn't look right here. Looks like we
got a warning here.
Oh, okay. So I forgot to assign the
return value from update all tokens to
an error variable. So let's assign the
return value from update all tokens to
this error variable here. And the
warning underline has gone away now and
we're good to carry on. So now we want
to test our login functionality, our
login user functionality here.
So to do that let's first run our code.
Ah, we got to do we haven't mapped our
um
we haven't mapped our login user handler
function to an appropriate route yet. So
we've got to do that first. So let's go
to main.go here. Let's create a
duplicate of this code here just below
it. And let's call this endpoint login.
Our handler function is of course login
user like that.
And now we have mapped our login
endpoint to log the login user handler
function and save that and then let's
run our code. So to do that let's do the
usual type go run dot here and it should
be listening. Yep, it is on port 8080.
Let's minimize that and then let's
launch Postman.
We're going to test this through Postman
because we're doing a post request here.
We can easily test get get requests
through um our browsers, but post
requests we should use a tool like
Postman makes it a lot easier.
So firstly, we want to make sure that
this is set to post and then let's
include the appropriate path. So the
path to our endpoint is login, not
register in this case. So let's adjust
this URL here to the appropriate
path. So for/lo is our endpoint and this
is of course the base address of our web
API where it's currently running on our
local machines. And then we want to make
sure that we select body here and raw.
And now we can include JSON data that we
want to include within the body of the
HTTP message. So let's open our curly
braces and include the appropriate JSON
data. Let's just quickly go to compass
here and look at our data.
Okay, let's connect. We want to look at
our user data because we recently
registered a user named Craig Denton. So
here you can see Craig Denton, the user
that we recently registered and the
tokens are currently set to empty
strings. So when we log in using Craig
Denton's credentials through Postman,
these tokens should be populated with
the appropriate access token for the
token field here and the refresh token
for the refresh token field here. So
that's what we want to do here. Verify
through Compass that the login has been
successful once we've tested the login
user functionality. So let's go back to
Postman.
Okay. And we're all almost set to test
our login endpoint. Let's just go and
get the the appropriate data from
Compass.
Okay. Um so let's copy we can copy this
email address for Craig Denton.
So the email address within the
users collection uniquely identifies
each user.
And let's type email here colon to
include our in fact that should be
within
quotations. So we're including
appropriate JSON here. And then within
quotations, let's paste the appropriate
email address for Craig Denton. And then
let's include a comma here. And then we
want to include Craig Densson's
password,
which is he chose this password because
we just want to keep things simple for
testing purposes. So it's just password.
It's obviously not the most secure
password in the world, but for testing
purposes, this is okay. And this looks
pretty good. Let's just verify that the
JSON is appropriately formed for what we
want to do here. So let's go back to our
code and let's go to the user model
user_model.go file here and look at user
login the user login model. Okay. So
email is validated as it's got to be
validated as a
as a properly formatted email address.
And then we've got required here. So
email is a required field for the user
login strct and password is required and
must be must have a minimum of six
characters and that looks good. So you
can see by this JSON tag that our email
and password are appropriately named
or are appropriately named within
Postman. Let's go back to Postman.
email, password, and we've got the
appropriate values next to each of these
fields here within our JSON. And I think
we're ready to test the login pretty
much. So, let's do that. Let's press the
send button and see if our login works.
Look at that. 200. Okay. And we've
logged in successfully. And we can
verify that because it's created our
access token and our refresh token. And
of course, this is just encrypted data
based on the users's claims and various
settings that we went through in this
section of the course a little bit
earlier. So let's go to compass to
refresh the data. We go view reload
data.
And now
we can see token refresh token have the
appropriate values that has been
returned
from our end point. And you can see that
within the result returns to us via HTTP
from our login user handler function.
Excellent. So now the user if this say
was a JavaScript or React client to
access protected endpoints. These
protected endpoints haven't yet been
created yet, but we will soon be
creating these protected endpoints. And
we can use these tokens. We can use this
access token to access the endpoint
because this verifies the relevant user
has logged on and been authenticated
successfully. So can now access the
appropriate protected endpoints.
Excellent. Okay. So I've actually
noticed that the access token denoted by
the token field value here is exactly
the same as the refresh token. So
something is not quite right with our
code. So I want to go into the code and
just see what could have caused that. We
want those tokens to be unique. So okay,
let's have a look. We want to go to tok
to token_util.go
and
because we duplicated the code for
deriving the access token, we duplicated
it in order to create the refresh token
because the code is very similar. I
suspect that's the reason why we're
getting the same token generated. So
first of all, I can see here this should
be secret refresh key and let's replace
secret key with secret refresh key
because we are signing the refresh token
and not the access token here. So let
make sure that's secret refresh key.
And
what else? Okay, here we could up the
expiry at field. So, let's just multiply
by 7. Whoops. Multiply by 7. Multiply by
time dot hour. We've got a week before
this refresh token expires. And we'll
look at refresh tokens in more detail a
little bit later. But I think that looks
pretty good. Let's just test that. That
now is creating two unique tokens. One
for the access token and one for the
refresh token. So, I'm just going to run
this code. Go run dot.
Okay,
great. It's listening on port 8080. I'm
going to go to Postman now and just run
this again.
Okay, and we've logged in successfully.
And let's just compare the refresh
token. And you can see here that this is
now different. This is a different
value. So the refresh token and the
access token are different. So that
looks much better. And we did find some
slight issues in our code that we've now
corrected. Okay, brilliant. So, we've
got the login functionality now sorted.
Let's move on
to the authorization code. So, we want
to create our middleware that will
authorize the user, authorize the client
each time the client makes a call to one
of our endpoints. So some of the
endpoints are not protected but others
are protected in which case we need to
check that the user has been
appropriately authenticated. So this is
the code we're going to create now and
we're going to create that in our
middleware folder here. So let's create
a file within our middleware folder and
let's name that file
or
underscore
middleware like this.go go. It's ago
file. Press the enter key.
And we're going to
make this part of a package
called middleware.
Excellent.
And then let's create the import
section.
Open and close round brackets. Okay.
Okay. And let's firstly make sure that
we import
our utils package. So to do that, we
need to include the root path first,
which is the root path of where the code
will eventually be pushed to on GitHub.
So it's github.com/gavanlon
digital. Of course, this will be
whatever your
GitHub account is called. So mine's
Gavinon digital
slashmagic
[Music]
stream Movies
slashserver
slashmagic
streammov
server.
I think that's correct. Let me just
double check that. So, if I go to go
domod,
we can see it here. So, it's actually
magic stream movie server. So, let's I'm
just going to copy that straight from
there. And you can do the same to make
sure it's 100% accurate.
Paste it here
and then forward slash
utils like that. And now we've imported
our utils package. Brilliant. So, let's
create a function called middleware
called O middleware.
So, or middleware where like that.
Oops. Open and close curly braces. And
this is a jinonic handler function, but
will not be used in the same way as the
other handler functions are used. We'll
talk about the details of what I mean by
this in a bit. For now, it is important
to understand that this function will be
used to validate the access token and
grant access or prohibit access to
protected endpoints. Jin. So we need to
hook into the jin gonic functionality.
So it's a handler function and that
means we also need to return an
anonymous function like this that
accepts an argument of type jin.contextc
and we have star jin. cont whoops
context like this. So, we're hooking
into the Genonic functionality here. So,
we have access to Ginggonics context and
you can see that the Genonic package has
automatically been imported up here. So,
make sure that this Gingonic package has
been imported in order for us to hook
into the Genonic functionality here.
Okay, let's start writing our logic for
the or middleware function. So the first
thing we actually need to do is create a
function to extract the token from the
header of the incoming HTTP request. So
I'm going to create that function in the
token_youutils.go
file. So let's go there and create a
function for that purpose. So within
token_util.go,
Go. Let's create a function called
get access token like this which accepts
a an argument of type
jin.context. So this is the jinggonic
context object
and it returns a string and an error. So
the string will represent the string
will be the token and the error will be
the error object. the typical go error
object that we need to return to the
client so the client can handle the
relevant error
saying jin is undefined here.
Okay, so we might not be importing the
gingonic package here. Okay, so we need
to import the gingonic package. So let's
go back to or middleware, copy this
where we're importing the gingonic
package, go to token utils and import it
here. Okay, so that red squiggly should
go away now. Vanished. Yep. No red
squiggly there. Excellent.
But we've got a red squiggly here. And
what's that saying? Missing return
compiler. Missing return. Okay. All
right. Okay.
So that's because we're not returning
anything from the function at the
moment. But let's go through the logic
step by step.
So firstly let's create a variable
called token
string and camel case like this. It's
just a local variable for the error
object and then our assignment and decl
declaration operator. C dot
request because we're going to be
reading the token from the header. So
C.reest request header.get
and then
we want the authorization name. So these
are name value pairs within the header
of the HTTP message and we're reading
the author authorization.
[Music]
Um we actually just want to return an
orth header object here. So let's call
this or header. So we're returning a
reference to this name value pair
within the all authorization
setting within the header. So we need to
return an object here. Firstly press the
enter key. So if header
is empty
then let's
return
nothing for the token. So an empty
string for the token and then an
appropriate error. So we go errors the
errors object dot new.
So that should have imported errors
here. So we need to import errors here
firstly for that to work. So we've
imported errors.
Okay.
Errors dot
new
and then within brackets
we can include an appropriate meth
message and this method message can just
be authorization
[Music]
header is required. Okay, perfect. That
looks good.
Okay, and then if we get to this line of
code here, we can create our token
string here. Now
token string
assignment and declaration operator
and then or header. Now we can read the
header
and at the same time because the header
will contain bearer in the first part of
this authorization value. we need to
extract
um the token and leave out the part that
has the literal text bearer space in it.
So we can do that through this code
here. So len the length of bearer
of the literal string bearer space and
then include a colon here.
Okay, so this should be a little L here
len like that and that should work. So
here we're extracting
the token from the authorization
setting within the header like this.
It's a name value pair. So authorization
is the name. So we just want the token
part of the authorization setting within
the header here. So token string
assignment operator or header and then
we're extracting the token with this
line of code here. Okay, great. And then
we can just check if
token string is equal to empty
then let's handle the fact that token
string is empty. So we've we haven't
been able to extract the token from the
authorization header. So it's empty. So
let's return
an empty token. So no token. And then
followed by errors new. And let's return
an appropriate error to the calling
code. So we're going to go bearer
token
is required. So it's called a bearer
token. So bearer token is required will
be sent back to the client within the
error object.
else if all has gone well and we've
managed to extract a token from the
authorization part of the header we can
return it to the calling client. So
return
token string and then
an error object
which will be null. So we return null
for the error object here. Okay,
brilliant. So that should extract our
tokens appropriately. Okay, so get
access token has now been written. Let's
go back to our orth middleware.go file.
So middleware
and then we can call our newly created
get access token function like this. So
token,
and then assignment operator utils utils
dot get access token like that. And we
pass in the ginonic context to get
access token here.
And as always, we need to handle an
error if if one has occurred. So we go
if error is not equal to nil,
then an error has occurred. So let's use
the context to pass valid JSON
and uh an appropriate HTTP status
response to the client. So we go status
unauthorized in this case because we
have received an error trying to extract
the token from the header. So status
oops
status
unauthorized
comma jin.h says pass wellformed JSON
back to the client with an appropriate
error message error. And we'll just pass
what's whatever is in the error object
like this.
Okay.
Something is a miss. And it's of course
because I keep putting a comma there.
And that should be a colon. Okay. And
then we can terminate the execution of
this function by including the return
keyword like this. Brilliant.
Okay. Now we want to check for an empty
token string. And we can do that by
going token equals to empty string. So
if it's an empty string,
we can return a similar error. I'm
actually just going to make a duplicate
of this.
And instead of error and then returning
the error like that, let's return a
different message here. And we'll go no
token provided for this for this error
message.
no to token provided like that we
actually need to use the gingonic
context object to abort this function
and you'll see the significance of this
uh a little bit later but so it's the
middleware it's the the endpoint that we
are calling before any of our publicly
exposed endpoints are called by a client
so here we just go see abort so it won't
continue. For example, if the user is
not properly authenticated, it won't
continue to call the targeted endpoint
because we we are using C.abort here
before that endpoint can be called. And
I think what I'm talking about here will
become clearer uh as we progress with
this part of the course.
Okay. And then we can extract the claims
from the token.
But we need to validate
our token first. So we need to create a
method called validate token. So we're
going to go back to token_util.go
and create another function. And this
one will be called validate token. Okay.
And I'm just going to fast forward the
creation of this code for you. And then
I'll explain what's going on here.
Okay. And that looks pretty good. Let me
briefly explain what's going on there.
So science details is a strct we've
defined that embeds or extends
jwt.registered
claims. It's where token claims will be
decoded into. This uses the
jwt.parswithclaims pass with claims
function from the
github.com/golangjwt/jwt/v5
package. It pares the token string into
a token object. Decodes its claims into
your claims variable.
Uses a callback function to provide the
secret key used to verify the signature.
you return it as a bite slice which is
required. This checks that the signed
algorithm used is HMAC like HS 256. This
is a critical security step. Attackers
can try to spoof tokens using a
different algorithm if you don't check
this. So then we check the expiry date
here. So this checks whether the expiry
at claim a time field is before the
current time. If the token has expired,
it returns an error. Great. Let's go now
back to the calling client code
here.
So we want to call our validation code
now
colon equals to utils dot validate token
and then let's pass in the token here.
Okay. Then if an error is returned,
let's handle that case.
If error
is not equal to nil,
C.JSON
http
status
unauthorized
and then gen.h
H
error
and then invalid
[Music]
token.
Okay. And then we can abort
this by typing abort like this.
Okay. We should actually also include a
return
keyword for each of these error handling
cases like this
so that the code doesn't continue. So
we're aborting it from going to the next
calling the next end point here. But
then we want to also abort this function
from continuing to
execute here.
Okay, great. And then we've got access
to our claims now. So we've decoded our
claims from the token and we can do
something like this which is quite cool.
We can set the
we can use the gingonic context object
and set certain values like this and
we'll look at the significance of this a
bit later. Claims do user ID. So we'll
be able to retrieve the user ID from the
Genonic context object in subsequent
code when it calls when the when the
relevant target endpoint is called we'll
be able to within that code extract the
user ID from the Jonic context because
we're setting it here once the user has
been authenticated
see so the once the token has been
validated which means the user has been
authenticated ro and let's also include
ude the roll from the claims here within
a name value pair named roll here.
So claims roll
here like that and then instead of this
is the opposite of C.abort we can
include C do next. So what that means is
it will continue to execute the targeted
endpoint which may which will be a
protected endpoint.
Excellent.
And that's our middleware created.
Brilliant.
Okay. So the next step is to tidy up our
routter related code. So we're going to
sort out the code here.
We want to create code for the
facilitation of separating our
unprotected roots from the protected
roots.
The difference of course between these
types of roots is that protected roots
require the user to be authenticated
before accessing a protected route and
an unprotected route does not require
authentication before the relevant user
can access the unprotected route. So
let's say we want to protect the movie
endpoint here the and the ad movie
endpoint and the register and the login
and the movies endpoints
are
unprotected roots. The movies actually
are displayed as soon as our website is
accessed. So we want those to be
publicly available.
Um and of course in order for a user to
register this has to be publicly
accessible and of course to log to the
website to actually authenticate the
user needs to have access to the login
functionality. So these this one and
these two and this endpoint must be
unprotected while these should be
protected. So we only want users with
special privileges to be able to access
these endpoints here.
So to separate our two types of roots,
protected and unprotected, let's create
two separate files within the roots
folder. So within the roots folder,
let's create a file called
protected
roots like this.go.
Okay. And that will house the
functionality to protect
our protected roots. And let's create a
file called
unprotected roots. Unprotected
underscore roots
like this.go. Okay. So unprotected
roots. And let's go to the protected
roots
file. And the first line of code will be
this package.
roots because we want this functionality
to be part of a package called roots and
we'll do the same for the unprotected
roots. They must be part of the same
package called root. So let's go back to
protected roots. Let's create an import
section here.
Okay. So firstly we want to include our
controllers package within the import
section. So, so let's go to the main.go
file. And I think we've already got a
reference here. So, let's just copy this
code to our clipboards. Let's go back to
the protected roots file and include the
same importation code within the
roots package here within the
protected_roots.go
file here. And you can see we're
aliasing this package with the term
controller. And because we want to
protect our roots that we're going to
configure in this file, we also want to
import our middleware package. So to do
that, we can simply go to go domod, copy
this root path here,
copy this root path here, go back to
protected roots, and paste it here. Put
these within quotations
and then forward slash middleware
like that. And we can just verify
that that is our package name by going
to the relevant file. Yep. Middleware.
Okay. Go back here. Make sure we've got
middleware. We are importing middleware
here. And that looks correct. And of
course, we also
must make sure that we've got the
ginonic package installed. So, if we go
to one of our controllers,
one of our controller files, we should
have the Jin Gonic package being
imported here. And we do. And that's the
code we want there. Let's copy that to
our clipboards, go back to unprotected
roots, and paste it here. Great. So, now
we've we're importing the appropriate
packages so that we can create our
protected root configuration code. Let's
create a function called
setup protected roots. So to do that,
let's type funk
setup protected roots like this. And
then
this uh this method signature or
function signature must include a
parameter of type jin.
Like this. Okay.
And then let's firstly
configure the fact that we are
protecting our roots. And we can do that
using the routter
variable the routter sorry the routter
argument that's being passed into this
method.
And we use the use method like this. And
then we pass in middleware dot
or middleware. And of course our orth
middleware is the functionality we wrote
in the last section of this course which
will protect the relevant roots because
it'll abort if the token is invalid. If
the token is deemed as invalid or the
token has for example expired, it will
send an unauthorized
HTTP status to the client and with an
appropriate error message and then it
will prevent the targeted endpoint from
being called through this line of code
here C.abort and that's called on the
Ginggonic context. So that we use we're
leveraging the Jinggonic web frameworks
context to abort the execution of
further code the execution of the
targeted endpoint like that.
So this is how we are protecting our
protected endpoints through this
middleware. So it intercepts the code.
You can see we're intercepting. We're
calling this before
we're configuring any of our protected
endpoints to ensure that the token is
valid. Great. So now the next step is
just to configure the other endpoints.
And we've actually already written the
code for those protected endpoints. So
let's just we can just cut that there.
This is one of our protected endpoints
and we can paste it here. And now we're
configuring our movie endpoint here,
which is a protected endpoint. And it is
protected by this line of code here. The
execution of code will abort if the
token is invalid. So it won't get to
this line of code here.
Okay. And the other one we want
protected is
the add movie
endpoint. So let's cut that. Let's go
back to our protected endpoint
configuration code here and paste it
there.
Brilliant.
So that's pretty much it. That's our
configuration code
set up for the protected endpoints like
that there. And now we want to move on
to our unprotected endpoint. So actually
what I'm going to do to make this simple
is to just copy this code here and okay
we can just select all here and paste it
in here and the only one we don't
actually need is the middleware. So we
can remove that importation code here
because these are unprotected endpoints
and this function should be called setup
unprotected roots and of course these
roots are protected roots. So we don't
want these configured here. We're
already configuring them here within
setup protected routes. So setup
unprotected roots. Let's firstly remove
those. And then let's go back to our
main.go method where we are currently
configuring our roots. Let's copy the
unprotected roots or cut the unprotected
routes. So register and login and let's
place these here. And we've got one more
unprotected route to configure and that
is the movies endpoint which is publicly
available on the homepage of our web
application which just displays all the
movies
that are currently available to be
streamed on our Magic Stream website. So
let's go back to unprotected and let's
configure that here. And there we go.
And that's pretty much it. So the next
step of course we have to call these two
functions from main.go. So we need to
import our roots package in main.go
firstly. So let's do that.
So we can just make a copy of this here
and replace controllers with roots which
is the name of the package
that we
want to import here. roots. So now we
just need to call the
routter dot setup unprotected roots
method
like this and pass in the routter
object here
which is from the Genonic web framework.
So this code is enabling us to manage
our roots through the Genonic through
leveraging the Genonic framework.
Um,
so I'm calling routter here. That's why
I've got a red squiggly line. This
should be roots because it's the package
name setup unprotected roots, not the
routter
um, returned from the Genonic, not the
routter we're leveraging from the
Genonic web framework. This is our roots
package
and this is the method we are calling
here. So let's go back to main.go Go
roots dots setup
unprotected roots and then we want to
call roots
do setup
protected roots like that there. Okay.
So now we want to test our
functionality.
So let's run our code. So let's go go
run dot to run our code.
And let's see if we've protected those
roots.
effectively. Okay, so it's listening on
port 8080. Let's launch Postman.
Let's launch Postman. Okay. And I've
already got this set up here. But um
firstly, I just want to do a test that
fails. So if we go to movies,
sorry, movie, and then forward slash,
and then we want a particular movie. So
let's choose the hangover which has an
IMDb ID of TT111
9646 9646 like this.
Okay, forget the body here. This is
going to be a get. We're going to make a
get request. So um
so this body won't have any relevance
here. What we've got we've still got
this left over from when we logged into
the system when we tested the login
functionality.
Um, so I'm just going to go out of that
there. So we're doing a test for a get
request to the movie endpoint and we're
passing as a parameter to the movie
endpoint this IMDb ID. So it should
return the relevant data for the
hangover the movie the hangover
which is uh which has this unique IMDb
ID. So let's try that. Let's press the
send button and see what happens.
Okay. And that's what I would have
expected unauthorized. It's because we
have protected the movie endpoint. So
you can see here protected roots and
this functionality is being called this
functionality to validate a token which
we haven't which we were not sending
through with the last call. That's why
we got this unauthorized 401 status
response HTTP status response from our
code here because basically there was no
token. So it would have aborted no token
provided. Let's have a look at the error
that we got. Let's have a look at the
error we got in Postman. Authorization
header is required. So that's right.
There would have been nothing in the
header. We haven't configured that. So
in order to configure the header
correctly whereby we pass in a valid
access token, we need to firstly log
into the system, extract the relevant
token and then include that in the
authorization header and we can do that
with this setting here from within
Postman to test the authentication
functionality through a bearer token.
Okay. So to get the bearer token, we
need to firstly log in to the system. So
let's go to the login
endpoint and we need to perform a post
request here. Let's go to our body here
and we've already got the relevant
credentials we can use to login already
here for us in JSON format within the
body raw setting here within Postman. So
we got Craig Denton's credentials. So
let's log in.
Okay, brilliant. So, that's brought back
Craig Denton's
details for us. And now we have we
should have a valid token here because
we've authenticated with our system
through Craig Denton's credentials.
We can now use this access token. So,
let's copy this access token to our
clipboards. I'm just going to paste that
into Notepad here. Now let's um include
the relevant URL here for the movie
endpoint. And let's try again. And so TT
111
96
46 this is the IMDb sorry 46. This is
the IMDb number for the movie The
Hangover. So we want to we want the
details for the movie The Hangover to be
returned from this request. And of
course it's a get request.
Okay. And then we can configure our
authorization header
by going to the authorization tab here
and within bearer token. So we need to
select bearer token here and then within
this token field we can include our
bearer token. This is the token that we
just pasted into notepad
after logging in as Craig Denton. So,
let's copy this token.
Let's go back to
Postman here and let's paste that token
within this field here. And of course,
make sure that this O type field is set
to bear a token. So, make sure this
dropown is set to bear a token. Then,
paste in the token that has been
returned to you once you have
authenticated with the system through
Postman. Just copy that, paste it into
bearer token. Make sure that you're
calling the correct endpoint here, which
we are here, movie, and then the IMDb
number. This is a protected endpoint.
So, we need to include a bearer token, a
valid bearer token. Now, let's see if
that works. So we've already proved that
our orth middleware has protected our
endpoint because it returned a 401
unauthorized error to us when we tried
to call this endpoint because we didn't
include a valid access token and we are
doing that now through this mechanism
here provided to us within Postman.
Okay. So now we've included the token
and let's see if it gets validated and
returns to us the relevant details of
the movie hangover denoted by this IMDb
unique identifier here. So let's press
the send button. Look at that. 200.
Okay. So our token authentication
functionality is now working. Excellent.
And we've cleaned up our root code.
We've cleaned up our root related code
here by separating protected roots
which we'll continue to include within
this function here from unprotected
roots which we'll configure within the
setup unprotected roots function here.
Excellent. Okay. So we've separated our
endpoint roots into two main categories.
Protected roots and unprotected roots.
Protected endpoints can be accessed once
a user has been authenticated and has
the appropriate security privileges. The
protected endpoints we have dealt with
so far are automatically accessible once
a user has authenticated. But for the
endpoint that we are going to work on
now, a user must not only be
authenticated but must also be a member
of the admin role. This endpoint will be
created to enable administrators to
update a movie review for a specific
movie. For the sake of simplicity, only
one movie review will be created per
movie and this must be done by an
administrator.
The exciting part of this functionality
that we are about to create is that we
are going to use a technology called
lang chain go to interact with open AI
and extract the sentiment of the movie
review which of course will be written
using natural language. So basically
we'll prompt the AI to use one of five
words excellent, good, okay, bad or
terrible to describe the sentiment
behind a movie review written in natural
language. I've deliberately kept this
example simple so that the basic
principle of leveraging AI to extract a
quantifiable metric from natural
language can be easily understood. So to
elaborate on the significance of the use
of AI here, basically each of the words
I have chosen to describe the sentiment
behind a movie review has an associated
number. Excellent has the associated
numeric value of one. Good has the
associated numeric value of two. Okay
has the associated numeric value of
three. Bad has the associated numeric
value of four. And lastly, terrible has
the associated numeric value of five. If
we look at the movie documents in the
movies collection of our magic stream
MongoDB database, we can see we have the
admin review field which contains an
example of an administrator's movie
review which is of course written in
natural language. Then we have this
ranking JSON object here that has two
fields namely ranking value and ranking
name. Ranking name will contain the word
that through the use of AI will be
extracted from the administrator's movie
review to describe the sentiment behind
the relevant movie review. Ranking value
will contain the numeric value
associated with the word extracted from
the relevant movie review to describe
the sentiment behind the review. So a
simplistic example would be the
administrator's review says something
like I loved this movie. The acting was
simply sublime. In this particular case,
we've got I did not like this movie. So,
an even more simple example. And the
ranking value is four and the sentiment
is bad, which is denoted by the ranking
name. So, the AI has extracted the word
bad to describe the sentiment behind the
movie review. So, bad is included as the
value for the ranking name and the value
of four associated with bad is included
as the ranking value. So the ranking
value essentially can be used as a
quantifiable metric pertaining to the
sentiment behind the review written in
natural language. If however the review
went something like this, I did not love
this movie but I did not hate this movie
either. The associated ranking name
would probably be okay and the ranking
value would therefore be three which is
associated with the sentiment denoted by
the word okay. So here we have a similar
admin review to the one I've just
described. This movie wasn't great, but
it wasn't bad either. And of course, the
ranking value for that is three
associated with the sentiment, which is
denoted by the ranking name of okay. And
we're using AI to extract these values
from a review written in natural
language. And this is how we can
quantify a review written in natural
language. And you'll see later how this
pertains to the recommendation service
that we provide through the magic stream
website. Okay, great. So let's write the
relevant endpoint HTTP handler function
code. Right, so let's go into our code
and start writing the logic for the
relevant code which is to do with
updating a review for a particular
movie. So we're updating the admin
review for the movie. Then a call is
made to open AAI with which will contain
a prompt and the review. And then the
functionality that we'll leverage
through lang chain go will extract the
ranking value and the ranking name from
AI
which basically describes the sentiment
behind a particular movie review. Let's
go to the movie controller here. Let's
go to the bottom here. Okay, I'm going
to call this function
admin
review update
like this. Let's hook into Jgonic as
we've done many times before. Now,
handler funk like this. Oops. Let's
create our anonymous
function that we'll return from this
function.
Okay, let's include the context as a
parameter here
like that.
And let's write our logic. So firstly,
let's create a variable named movie ID.
Let's make this camel case.
Movie ID.
Let's use our assignment operator here,
which also declares the type of the
movie ID variable based on the inferred
type from the parameter passed through
to our function handler here or our
handler function. So, it's going to pass
in a parameter named imbore
id.
So movie ID, this will be the IMDb
unique identifier for the movie that
will be passed in with the URL to our
handler function here.
Okay.
And then we firstly need to check
whether our movie ID indeed contains a
value. So if movie id
is equal to an empty string
then let's handle this specific case. So
we want to send back a particular HTTP
response
to the user. So HTTP status bad request.
So this would be a bad request by the
client
if the IMDb ID parameter
value is empty is not included within
the URL. Okay. And let's send back
wellformed JSON. So jin.h
open the curly braces there. include
error here which is the name and the
value of the error is movie
ID
required. So it hasn't been included for
whatever reason here and we are
returning an appropriate message via
HTTP.
Okay, so we need to include this code of
course here
um within the return function. Okay. So,
and then we can include the return
keyword here so that the code halts at
this point. If no IMDb
parameter value was included in the URL,
we don't want to continue with the code.
Okay, let's create a local strruct named
req. We'll go var
like this strruct
and let's define the strct and it just
contains the admin review
an admin review field of type string and
let's create the JSON
tag here
because we want this field to look like
this within
admin code within sorry within JSON code
review like that.
Okay. And the red squiggly line just
means we're not currently using it.
Okay. And then we let's create another
strct, another local strct. And let's
call this one
rest short for response.
Strruct like this.
And then ranking name is our first field
string. And we want ranking name to look
like this within the JSON.
So ranking
name like that
and then admin review.
We're also going to be returning to the
user
and you'll see the significance of this
when we write the client code
client react code JSON and then
admin
review like that.
Perfect.
Okay. And now we're going to bind
the passed in
body. This is going to be related to a
patch HTTP request from the client. So
the body will contain
the field and fields and values that we
want to update within the movie
within a movie document within a
targeted movie document denoted by the
unique IMDb_ID
parameter value. So let's type if
declaration C. Let's use the context and
then we've used this before. Should
bind
and then let's include the amperand
which relates to a specific part of
memory where the data for this strct is
stored. So amperand w
colon error we need to check the error
now in a if condition error is null
sorry is not null then we want to handle
the exception so we go c.json JSON and
send back an appropriate HTTP response
which is the same as the previous error
handling function.
Uh so it's a bad request here
and
jin.h
error
invalid request body.
So we were unable to bind it to our
wreck strct here, whatever was included
in the body. So we must send back
a bad request response from our server
code with an appropriate message.
Invalid request body there. And let's as
usual halt execution with the return
keyword like that. Okay,
great. Now we're getting to the exciting
bit where we're going to use AI
to extract the sentiment of the admin
review that will be passed in to
ultimately bound to our strct here which
is the admin review be passed in through
the HTTP body through the HTTP message
within the body of that message and
we're going to bind it to the wreck
which we're doing here. And if it gets
to this code, the binding has been
successful.
Okay. And now we want to pass that admin
review, sorry, this admin review here
into open AI using lang chain go. We're
going to use lang chain go for this. And
I'm going to show you how we can do that
now. And then it will return based on
our prompt. It will return the
sentiment.
Okay. Okay. So firstly, let's sort out
our prompting code.
Okay. So let's go to env. We're going to
store a prompt, a base prompt because
the the ultimate prompt is going to
include our base prompt and the
administrator's review. So let's first
include our base prompt here. This will
this base prompt will be included with
every prompt.
So it's a generic part of our prompt.
And then the other part of the prompt
will be the actual admin review. So, so
let's create the base prompt within our
env file here. Okay. So, let's call this
base prompt template
like this. Equals.
And then the prompt is return
a response
using one of these words
and then within curly braces we include
a placeholder called rankings and this
will be added dynamically. We'll first
query
the database for all the rankings that
are available. So you can see here not
ranked as a out of limits value 999 not
ranked
and then we've got one excellent, two
good, three okay, four bad, and five
terrible. So this makes it extensible.
So we can add to that ranking list if we
want to within our database. And then
that will be added to our prompt
dynamically through our code. Okay,
great. Let's finish our prompt.
And then we want to say
the response
should be
a single word.
And oops. And
should
not contain
any other text. So, we're being very
specific in our prompt here.
The response
should be
based on the following
review and then colon and then we'll
include the review. We'll concatenate
the review to the string
uh denoted by the base prompt here.
We'll concatenate the administrator's
review to the base prompt and send that
through lang chain go to open AAI and
that will return a response a single
word which will either be excellent,
good, okay, bad, terrible and you can
extend the values included within the
prompt by adding it to this collection
here, the rankings collection here. At
the moment, we've got one excellent, two
good, three okay,
four bad, and five terrible. Okay,
excellent.
Great. So, let's go back to our code
here.
So, let's create our function that will
encapsulate the functionality for
prompting the AI. So, let's create a
function down here.
Funk. Let's call this
get
review
ranking like this. And then let's
include the admin review as a parameter
here.
A string and we want to return
a string
which will be the sentiment
denoted by one word. Excellent, good,
okay, bad, terrible. int will be the
value associated with the relevant word
that denotes the sentiment and then of
course an error object like this if one
occurs that will be relevant to the way
the client handles the return from this
function and now we need to create a new
function which will get all the
rankings.
So, it's going to query our rankings
collection and get all of these rankings
and so that we can add them to our
prompt, our base prompt within
the placeholder here denoting the
rankings. So, we want to get a list of
those and we're going to add them as a
comma delimited string within our prompt
here.
Okay.
Okay. So let's go back to our code.
Yeah. So we need to create a function
now here called get rankings. Let's go
funk.
Get rankings like this.
Okay. So this get rankings function will
return an array
of models.ranking
ranking
to the calling code
and an error object.
So let's define a local variable called
rankings and this will contain a
collection of all the rankings that are
saved to our rankings collection within
the MongoDB database. So models
dotranking like that.
Okay,
let's do the usual. Let's create the
context the resource handling
functionality. So ctx
cancel equals to context dot
with
timeout
and then context dot background. Okay.
And then let's include the defer
cancel
code here like this. And now let's
create a cursor variable
because we want to return the collection
from
our rankings collection within the
MongoDB database. So we want to return
all of these documents. So we want to
include all of the items within this
rankings collection, all of the
documents
from a particular query. So let's go
ranking
collection. and we haven't created the
ranking collection yet. So to do that,
let's go up here
and let's create the ranking connection
variable. And similar to this code, but
not exactly the same. So we call this
ranking collection here. And we're
querying the rankings collection within
our MongoDB database.
Let's take that. Let's copy that to our
clipboards here.
So ranking collection. so that we can
query the ranking the rankings
collection within our MongoDB database
dot find like this. Let's pass in the
context sorry pass in the context and
bon
m which means we want
all the documents returned
and put into our cursor here. all the
documents returned from the rankings
collection and put into our cursor
variable there like that. Let's check
for any errors like this. If error not
equal to null
then we go return
null and like that
and then we can write this functionality
to make sure that the cursor is closed.
So this is also housekeeping
functionality to make sure that
resources
are cleared up
pertaining to the cursor variable.
So cursor dot close and
ctx like this.
Okay.
And then
if
assignment operator cursor do all
cct.
So we're putting in the cursor whatever
is extracted from the cursor. We are now
putting that what whatever is in our
cursor we are now putting that into our
rankings variable at a particular memory
address.
Okay. Okay. So that's the rankings
variable we defined up here as the
strruct ranking.
Okay.
And then let's check if is not equal to
null. Let's handle that error. And we
just return
null because we can't return anything if
an error occurred. And let's return the
error object to the client. And then if
it gets to this point in the code that
means all has been successful and we
have successfully extracted our
collection of documents ranking
documents and put them into our rankings
array here. So we want to return that to
the client and we can do that with this
code here rankings
nil like that. Okay, brilliant. And then
let's go back to our calling code which
will be the get review
ranking function. So, rankings whoops
rankings,
uh, operator equals to get
rankings
like that.
Okay. And then let's check the error
object.
And if it does have an error, we want to
return
nothing to the calling code
zero and error like that.
Okay. However, if an error hasn't
occurred, we're going to create a
variable called sentiment delimited
like this. Colon equal let's just
initialize it to an empty string. And
then let's create a for loop. So for
underscore ranking
operator equals to range. So we want a
range of values. Rankings
the range of values stored within our
rankings variable.
Open that there. If ranking dotranking
value
is not equal to nine. Whoops. 999.
Then we want to
include that
within our sentiment delimited, which is
a comma delimited string. We want to
include the ranking name within our
sentiment delimited variable. So you can
do sentiment delimited equals to
sentiment delimited plus ranking
ranking name like this plus and then a
comma like that.
Great. Okay, that's looking good.
Okay. And then we want to trim off the
last comma and we can do that with this
line of code. So we can just go oops
sentiment delimited equals to strings
like this
strings.trim
and I don't think we've yet included
strings within our importation code. So
we must do that.
Yeah. So let's include strings here.
Okay. Go back here
to our code.
strings.trim
and then sentiment delimited and we want
to trim off the last comma like that.
Excellent. Okay. So now what I want to
do is load
the base prompt into memory. So we're
going to read this value here into
memory into a variable in memory. Right?
So let's do that. Let's go
equals to
go dot
env.load
like this.
We got to load our environment file
here. Environment variable file. So we
need to import the package with this
functionality to read the env file. So
let's go up to our importation code here
and include
this. We need to include this particular
package here like that. Go.v
here.
Okay. And we're loading the environment
file if it exists.
And then we can just do a quick check to
see
if the file exists.
This actually only applies to when we're
running our code locally. We're using
thev file, but when we deploy this,
we'll use the hosts facilities to set up
the environment variables in the cloud.
So, this only applies, this code here
only applies when we're running it
locally for test and development
purposes. So,
this error will actually occur when the
code is running in the cloud.
So all we want to do is just log the
fact we don't need to stop the code or
anything like that because it's not a
mission critical error. So it's just a
warning we'll give. It's just a warning
that we'll log here.
ENV
file not
found.
So in some cases this will be an
expected error and we need to import the
log package here and we can do that with
this code like that.
Okay, excellent. We now want to read a
particular environment variable which is
actually going to contain our API key.
We we have to have a valid API key in
order to communicate with open AI. So,
we're going to be sending our prompt to
OpenAI in order to extract the relevant
word, the sentiment associated with
particular movie reviews. So, if you
haven't done so, you must sign up
for a
an OpenAI
platform account. So, to do that, we go
to openai.com
like this.
And then we go to the login dropdown
here and then we go API platform. So you
must log you must sign up for an API
platform account. And to do that we
click this particular option here.
Okay. And if you haven't yet signed up
just go to the sign up here. The only
downside to this is you're going to have
to pay a minimum of $5. But the good
news is that $5 lasts a very very long
time. So with each request you get
debited a tiny really tiny amount. So
your $5 will last you a very long time.
So that's the base amount, the minimum
amount you have to pay to uh
to be able to prompt the AI in the way
that we want to in this application. So,
you will need to sign up for an account
and pay a minimum amount of $5 in order
to use the the facilities provided by
OpenAI platform here. So, I've actually
already signed up for an account. So,
I'm going to log in with Google with my
Google credentials.
Okay? And then what you want to do is
once you've signed up, you'll have a
dashboard. So you go to the dashboard
option here
and then you want to create a new API
key and this is what we're going to
configure within ourv file an API key
that we can use to communicate with open
AI uh and we're going to do that via a
technology called lang chain go. So you
can see I've created a few keys here uh
just to for demonstration purposes I'll
create another one. So, you'd go create
new secret key.
And let's I'm just going to name this um
uh I'm just going to name this magic
stream key. So, you can name it whatever
you want.
And then you just go create secret key
like this.
And then you copy that secret key
to your clipboard like that.
And then let's go back to our code
here. And we can configure that within
thev file here. I'm just going to call
this setting
open
AI
API
key equals. And then I'm going to paste
that key in here like that. And uh so
obviously I'm going to deactivate this
key once the course is being finished.
So you need to create your own a your
own new API key through the open AI
website
so that you can follow along with the
next part of the course where we're
going to prompt the AI using a custom
prompt in order to extract the sentiment
from a movie review. So I urge you to
create your own API key and then
configure it here. Let's go back to our
code. Okay. So firstly we need to
install the lang chain go package.
So to do that we use the go get command
and then followed by the path to the
relevant package which is
github.com/tmc/lang
[Music]
chain go
uh slm/op
ai like that. press the enter key.
So we're installing the lang chain go
package and that is installed
successfully.
And then we can just include the
relevant
package importation code which is this
code here.
Then we are importing lang chain go here
like this.
So this is the code for importing
blank chain go into our application and
we're about to use that package now to
prompt
open AAI.
Okay.
So let's continue.
Okay. Okay, so we're now going to
communicate with OpenAI and we need our
unique key which we have just saved
within the ENV file here.
Okay, let's go back here.
Okay.
Okay. And let's write the code to read
that key. So, open AI
API
key
operator assigned operator OS dot and
we're reading the environment. We're
reading the particular
environment variable which we've named
open AI
API_key
like that.
Okay,
it's saying that OS is undefined. So, we
need to import OS also.
Okay,
great. Okay, so that will read the
environment variable into
our open AI API key variable there.
Let's just handle
the case where no environment variable
is available for this particular
environment variable name. If no value
is available, we need to handle that. So
if open AI API key is equal to empty
string,
let's return
the following to the calling client code
zero
errors dot new
could
not read and then
the name of our environment variable.
able here like that
errors and we don't have errors
imported. Let's go up here and import
errors and we'll import it here. errors
and let's now because we we should have
our open API key now read in from our
environment variable
then we should be able to communicate
with
the AI using
the lang chain go package. So we go open
AI new like this and then open
AI and then oh sorry AI
with
token like this and then we can include
our open AI key here
in order to communicate with open AI and
you leverage their functionality.
So we've signed up for a valid Open AI
key and we are now passing that through
lang chain go
to open AAI so that we can leverage
their services here so that we can
extract a sentiment from a movie review
which is denoted
which is going to be included within a
custom prompt that we'll use to prompt
open AAI. Okay. And then we check if
error is not equal to null.
Return empty string
zero error. So the ranking name we
haven't found a ranking name because an
error has occurred. Zero is the ranking
value and then the actual error will be
returned to the calling client. Now we
want to read our base prompt template.
So we want to read
we want to read in
this value here base prompt template
like that we want to read. So we want to
read in this value here the value here
for the base prompt template environment
variable. So let's go back to our code
here.
Let's create a variable called base
prompt template
and then assignment operator OS
dot get env.
Okay. And then let's include our key
which is base
prompt
template like that.
Okay.
And now
we want to now that we've read in a
delimited string containing all of the
available ranking names or the
sentiments available excellent good okay
bad terrible and a delimited string we
want to replace
let's go to env here we want to replace
this with the actual delimited list of
sentiments or ranking names So let's go
back to our code here and let's do that.
So we're extracting the base prompt
here. And now we're going to replace
that value with the value extracted here
that we got from the get rankings
function that we created. Okay. So let's
do that.
Let's go base prompt. So we got a new
variable base prompt. This is the
template and we're going to replace in
curly braces rankings with the delimited
list of actual ranking names.
It should be okay. And let's go strings
dot and let's use the replace function.
And the first argument to the replace
function is the base prompt template
variable followed by the value that we
want to replace within our base prompt.
So rankings like this and then the
delimited string sentiment delimited
one like that. And that is how we
replace this placeholder value within
our base prompt with the actual list of
delimited
ranking names or the various sentiments.
Excellent good okay bad and
terrible. Okay.
Now, we're going to actually call
OpenAI.
We're going to leverage their API
functionality through Langchain Go. So,
we go llm.all
context background. Oops, that's not
what I meant to do.
Context dot background. That's more like
it. And then we pass in our base prompt.
But we also we don't just want the base
prompt because if you look here at the
prompt itself, the last thing we're
saying is the response should be based
on the following review. So we want to
concatenate
the actual review, an actual movie
review. Okay. So let's go back here and
let's do that. And we can concatenate a
string by using the plus operator. And
then the value
that we want to concatenate is the one
passed in as a parameter value to this
get review ranking function.
So we do this. Whoops. Where am I? plus
review like that.
Okay. And then we want to do the usual
thing and check if an error was sent
back.
If it's null, if it's not equal to null,
let's handle the error. And we just want
to return
an empty string. Zero for the ranking
value. So the empty string represents
that there's no ranking value extracted
here. Zero. No uh no ranking name
extracted here. Zero representing no
ranking value and the actual error
object gets passed back to the calling
client like that. And let's initialize
rank val here to zero.
Okay. And we need we're going to do a
verification here with a for loop to
check
that the ranking sent back from the
prompt that we've sent to Open AI is
indeed in our rankings collection. So
we're going to loop through the range of
rankings like this. So all the rankings
excellent, good, okay, bad, ex uh bad,
terrible
and we want to test through an if
statement whether that ranking exists,
whether the ranking name sent back, the
sentiment sent back from open AAI is
valid
response like this. So the response will
contain
the sentiment name or the ranking name.
Excellent good okay bad terrible.
And we need to loop through our
collection to make sure that what is
sent back is valid. And we go ranking.
And then we can also extract the ranking
val. So we're quantifying. This is how
we're quantifying
qualitative data, which is the movie
review. So the qualitative data gets
sent with the prompt. So ranking val
equals to ranking dotranking value like
that.
And then we want to break out when it's
found when the response is found. We
want to actually break out of this for
loop here. So we can use the break
keyword for that. And then we return
response,
rank val. and nil for exception because
if it gets to this point everything has
gone well and we can now return the
ranking value sorry the ranking name
excellent good okay bad terrible and its
associated value which is a value from 1
to five one being excellent terrible
being five and we're returning that to
the client there and that's our code for
the get review ranking so here we've
interfaced with open AAI using lang
chain
to return a quantifiable value from
qualitative
data in the form of a movie review. And
we're returning the ranking name and the
ranking value to the calling client
code. So let's go back up to the actual
admin review update function and let's
call our get review ranking function.
Okay, great. So let's call it sentiment.
So this is the
value we want returned the actual
sentiment
and this is the value the ranking value
and the error object
assignment operator and then let's call
our function get review
ranking like that and then wreck. So
this is passed in to our HTTP patch
request. The admin review is passed in
through the body of the HTTP message.
We've extracted it and we've bound it to
the recstruct here and we are using the
admin review field that is then passed
to the get review ranking function. So
this is part of the prompt. The admin
review is part of the actual prompt that
gets sent to OpenAI and it returns the
sentiment associated with that admin
review. Excellent. And also of course
the ranking value quantifiable value
from 1 to five. Then let's check for an
error
not equal to null like this. And then if
an error has occurred,
let's pass back an internal server error
or 500 HTTP response to the client.
Status internal server error. And then
let's pass back wellformed JSON for the
error message like this.
And the error is
error and the actual message is error.
Whoops.
Error getting review ranking like that.
Okay. Excellent. And then let's include
the return keyword here so that
execution halts if an error occurs at
this point here. Okay. Okay. So now
we're actually going to include the
update code that updates a movie
document within the MongoDB database.
And that movie document is based on the
IMDb parameter value that will be
included in the URL to target this
functionality here. add admin review
update functionality.
Okay, so let's create a filter.
We want to filter on the IMDb
parameter name. So bson
M open curly braces. Let's include the
parameter name IMDb ID and then our
movie ID variable which will be the
actual IMDb ID unique value that denotes
a particular movie on the internet and
let's go update. So this is the actual
update information that we're going to
be relaying to MongoDB
telling MongoDB which fields we want
updated within our database with which
values. Okay. And to do that we use the
set command like this. So it's dollar
set.
I've used percentage not dollar. That's
not good. We want dollar here. Okay. And
then colon bon
m oops m open curly braces
admin
review
colon rec and then the actual admin
review.
So we want to update the admin review
and then we also
want to update the ranking
object within the movie document. So we
are updating the ranking object with
this code. So we go bon
oops do m and we open the curly braces
and let's update the relevant fields. So
ranking
value
let's include the value which is rank
val here and then let's include ranking
name
colon and this will be the sentiment
extracted from openai we actually need
to include a comma here
and then a comma there and a comma there
like that and that's the update code.
And now we can actually include the code
to interface with our database.
So let's include the usual. I'm just
going to paste this in here because
we've written this so many times now.
And this is just for cleaning up the
resources after we've performed our
update functionality against the MongoDB
database. Okay. So we're going to use
the update one method on the movie
collection variable for this purpose for
the update to be executed. Result
assignment operator movie
collection dot and we go update one
because we're only updating one document
and we include the context.
cleaning up the resources filter to
target a particular document within the
movie movies collection and then the
actual update
information here we've stored in this
variable the update
that needs to be the update information
that needs to be passed to MongoDB to
describe which fields need to be updated
and with which values. Okay,
great.
And let's just check if any errors have
occurred
for the update.
So if error is not equal to null,
let's pass back an appropriate
error response to the client. So HTTP
response of status internal
server error 500 will be sent to the
client in this particular case. if the
update has failed. H so we go error
and then colon whoops
colon
error updating
movie like this
and then let's include the return
keyword there.
Okay. Okay. And we also need to check
the result returned from this operation
here. This update operation. So if
result
dot match count. So it hasn't found it
hasn't been able to target a particular
document based on the IMDb value passed
in as a parameter value to this handler
function method. It hasn't been able to
find that document. So if that equals to
zero, there's no match sent within the
result of this operation.
we need to handle that case. So C
do.json
and then the HTTP response is that the
resource was not found. So HTTP the
resource we wanted to update was not
found. So we pass back to the calling
client status not
found like this. And then let's include
an appropriate
error message. And let's use jin gonic
to pass back a well-formed
well-formed JSON data. Error colon movie
not found.
Brilliant. And then of course we need to
include return keyword so that
our code halts
because it hasn't been successful. If
the code gets here, our function
handler, our handler function method has
not been successful. If the code gets to
this point, the document wasn't found.
So, we haven't been able to update the
sentiment and the ranking value, the
ranking name and the ranking value
fields or the admin review within our
database.
However, if the code gets to this point,
everything has been successful, let's
create a response. So, risp dot
ranking name. So we're setting the
ranking name to the sentiment variable
and restpadmin
review equals to rec. And then we can
send back an appropriate response.
C.json
we want to send back a HTTP status.
Okay.
Response if the code gets here. All has
been successful. And then let's pass
back whatever is in the response strct.
which should be the ranking name which
would be excellent,
good,
okay, bad or terrible depending on the
sentiment
extracted
from the movie review extracted by the
open AI functionality that we've been
able to interact with through the lang
chain go technology.
Great. And that is our code written.
So in this part of the course, we're
going to create a movie recommendation
service for our Magic Stream web
application. We have just created a
facility to update reviews for movies
and we've also implemented the AI
functionality that extracts the
sentiment denoted by one word from the
movie review. So in our recommendation
service, we can use the ranking value to
recommend five movies and order how the
movies are displayed to the user by
those ranking values. So we can order
the way the movies are displayed to the
user in ascending order from one which
is mapped to the sentiment denoted by
the word excellent to five denoted by
the word terrible. So how the
recommendation service will basically
work is our code will query the movies
collection based on the user's favorite
genres that the user chose when
registering with the magic stream
application. Then we can select the top
five movies from those genres which will
be ordered by the ranking value
associated with each of the movies. So,
our recommendation service recommends
the top five movies in the users's
favorite genres and displays the movies
in a ranked order from one to five. So,
yes, it's a very basic recommendation
service that we are integrating into our
Magic Stream application. I thought this
would be a great opportunity to
integrate AI functionality that extracts
sentiment from a review written in
natural language and then quantifies
that sentiment and we can then use that
sentiment value to rank the relevant
movies and in doing that recommend the
top movies that pertain to the relevant
users favorite movie genres. As
described earlier, a review is added to
the system by an administrator and AI
chooses one word from a list of options
provided in our prompt to the AI. The
word to describe the sentiment at the
moment can be excellent, good, okay, bad
or terrible. Each of these ranking names
or sentiments has an associated value
stored in the ranking value field.
Excellent. The value for the ranking
name has a ranking value of one. Good
has a ranking value of two. Okay has a
ranking value of three. Bad has a
ranking value of four. And terrible has
a ranking value of five. And you can of
course extend the sentiment or ranking
options if you like. But for the sake of
simplicity, let's stick with the
aforementioned five rankings. Okay. So
let's go to the movie_controller.go
file.
And let's create an endpoint handler
function. Let's first go to the bottom
here and let's create an endpoint
gingonic handler function. And let's
name this
get recommended movies
like this.
handler. Whoops.
Handler funk.
Of course. Oops. And of course, we need
to return a function from here.
Funk that accepts
C star the genuine context as an
argument
dot context like that.
Open up curly braces and now we can
write the logic for our get recommended
movies or movies as I put here. Forgot
the V movies. Okay, a user has to be
logged in in order to use this
recommendation service. So let's first
extract the user's ID from the Genggonic
context. Remember in our middleware that
protects our endpoints. If the user
security token which is a JWT token is
validated, we are setting the named
value pair value within the context
object provided to us by the Genonic web
framework to store the user's user ID
value. So we can now use this value to
query the user's collection for the
user's favorite genres which the user
chose when registering with the
application. So if we look at our orth
middleware here, you can see that
we're getting the access token there and
then we're validating the token here.
And then if the token has been
validated, we're setting these two named
value pairs like this from the claims.
So we're setting the user ID, which
means we can extract the user ID and
subsequent code and use that user ID. In
this case, we're using that user ID to
query the database for the genres that
the user chose
when registering with the system.
Um, and we can do the same for the role.
So, we can check what role the user is
in also. Okay, great. So, let's go back
to user_controller.go
here and let's write the logic. sorry
movie_controller and let's write the
logic for our get recommended movies
function. So to extract the user id
value from the context let's create a
function named get user ID from context
within the token util file. So within
the utils package. So before we write
the logic here let's actually write a
reusable function within the token
util.go Go file here and let's call this
function
get user ID
from cont whoops context like this.
Okay. Oops.
So this function is fairly
straightforward.
So we need to pass in we need to include
a parameter of star jin.ontext context
here
so that we can use the context to
extract the user ID
from the named value pair that we saved.
Once the user is authenticated when the
user calls a protected endpoint and is
authenticated, we're saving the user's
ID from the claim stored in the token.
We're saving the user ID to the Gengonic
context. So now we can extract that uh
value from the Genonic context. So let's
do that. user ID,
comma exists.
So the exists, this is clearly going to
be a boolean value.
This exists value. So it's returning two
values from C.get
from the C.get method and then user ID
which is the name of the value that we
want to extract from Jinonx context
here. And then let's check the exist
boolean value to see if the user ID
value exists within the context.
Okay so
oops, sorry, no, we don't need that. And
then let's open our brackets. So we're
saying if not exists. So we're checking
a boolean value which can either be true
or false. So if it doesn't exist, return
an empty string.
And then let's return an error.
And the error we can return can be
something
like this. User ID does not exist in
this context. Okay.
Us and the reason why we're getting a
red squiggly here is because I haven't
included in the definition what we are
returning from this function. So we want
to return a string which will be the
user ID value and we want to return an
error object so that the client can
handle any errors that may occur during
this code. Okay,
great.
Then let's proceed. id, okay, so this
what we're going to do here
is return the user ID as a string. So we
can do that with this code.
And if all has gone okay, so we need to
check the okay boolean value here.
if not. Okay, we want to handle
the exception.
And
this code will be similar to the one
we've already already written. We're
just handling potential errors that may
occur here. And we'll just go unable to
retrieve user ID. Unable to
retrieve
user ID here. So if it's not okay, if
this code fails, we want to return an
exception to the calling client. Else if
it gets to this point in the code, we
can return the user ID.
And because no errors would have
occurred, we can return null for the
error object. So we're returning those
two values to the calling code. String
error, string will be the user ID, null
or error in this particular case there.
Okay, let's save that. And we can now go
back to the
logic here within the get recommended
movies and we can extract the user ID
from the gingonic context and we can do
that by calling the get user ID from
context function. Okay, so let's do
that. Let's go user id,
and then assignment variable utils.
We're going to use the utils package
which we have imported already into this
package. Get user ID from context and
we're passing in the gingonic context
here so that we can extract the user ID
value from the gingonic context. Okay.
And then we want to do the usual go
thing and check for any errors. So if
error not equal to null then let's
handle that. So an error has occurred. C
JSON
HTTP
status bad request and we've done this a
few times at this point and then let's
pass back an appropriate
error using jin.h.
So this will pass back wellformed JSON
to the client and the client can handle
the exception.
ID not found in context.
Excellent.
Okay, so now we want to query
the database for the user's favorite
genres. Now that we've got the user ID,
we can query using that user ID to
target a a specific user document within
the users collection.
And we're actually going to create a
method called get users favorite genres.
We're going to create a function called
get users favorite genres to extract the
users's favorite genres before we
proceed with the get recommended movies
logic. Let's create the new function
below. Get recommended movies.
Okay, let's create a function called get
users
favorit
genres like this which accepts one
argument of type string
named user ID and returns an array of
strings which will contain the user's
favorite genres and an error so the
client can handle any errors that may
occur. Okay,
excellent. And let's write the logic for
this. So let's first create
and I'm not going to explain this at
this point because we've done it so many
times in this course. We're just
creating a context which helps clean up
resources.
And then we're going to create a filter
for our query assignment operator bon.
We're creating a filter. And we want to
filter our query on the user's ID. So
user ID like this. And then we're
passing in the user ID here as an
argument. And we want to include that
within our filter here. Okay. And then
we want to create a projection variable
like this
bon
m.
Okay. Okay. And the projection must
include favorite
favorite
underscore genres whoops
dot genre
name. So favorite genres do genre name.
So we want to include that one
there. And we don't want to include the
underscore ID within the projection.
Okay. And we can set this to zero like
that. And these red squiggly lines just
mean that we've created we've declared
the variables, but we're not currently
using them, but we're about to use them
anyway. So, okay. And then let's set the
options.
So opts
assignment operator options
dot
find one and then set projection. So
we're setting the projection from the
variable we've created here
check.
Okay. And then
so options this might not yet be
imported into our code. We need to
import a package there for options. So
let's go up to our importation code and
import the relevant package so that we
can use the options functionality there.
And it's just a case of including
this package here which is part of the
MongoDB
forgo driver functionality. So we're
including that there and we can now use
the options functionality
down here within our query.
Okay. And you can see the red squiggly
line under options has now disappeared.
Okay. Brilliant.
Let's continue with the logic of our
code. Okay. And then we need to call
user collection.find
one. So we're targeting the user
collection. Oops.
We're targeting the user collection and
we want to find
the relevant data based on the criteria
we've established through these
variables, the options, the filter, the
projection. So let's pass those in to
the find one function. So let's pass in
the filter and let's pass an opt here.
And then we decode those results
into
a variable that we haven't yet defined
called result. So let's first define the
variable called result. So
var
result
bon
m.
So the this result variable is defined
as bon.mm. And we're the any results
that are found within our query here
will be decoded into this variable here.
So we're going decode and then oops and
then we want to include a pointer. So
this is in fact just a pointer denoted
by the amperand character preceding the
variable. So it means it's the data will
go into a specific location in memory
and the result will point
to that memory address. That's what the
amperand denotes here. Okay, brilliant.
And then let's check for any exceptions
that may have occurred. So if is not
equal to null,
let's and then we want to actually nest
an if statement here and check if a spec
for a specific error if
No documents. So if no documents
were retrieved, let's handle that. will
return
an empty array
and null. So it's not an exception, but
the user's favorite genres were not able
to be retrieved because they don't exist
within the database.
So that's what we're doing here.
Okay.
And now we want to
if it gets to this point
favorite genres array we want to
put the result in the fav genres array
variable.
So let's use the result to extract the
results we want.
So favorite
genres like this
bum. A like that.
And that's how we are able to define the
type of the variable as well as return
the favorite genres that the user chose
when the user first registered with the
system. Okay.
And then we can check if not okay. In
other words, if this code is not
returning the expected result that's
this boolean value is set to false. So
we need to handle that by returning
errors new
unable
to retrieve
favorite
genres
for user.
So if we were unable to extract the
users's favorite genres from the
database, we are handling that case here
by returning an error stating unable to
retrieve favorite genres for the user.
However, if our genres were retrieved,
we want to return an array of genre
names to the calling code. So, an array
of strings, which is going to be just
the names of the genre, is what we want
to return from get users favorite
genres. And these are the genres that
the user
chose when signing up to our Magic
Stream website. So, this code is pretty
involved.
So, just bear with me as I write this
code. It's just basically looping
through the results
and putting the genre name because the
uh the genre object contains a value for
the genre or an ID, sorry, for the genre
as well as the genre name. We're looping
through all the genre objects.
If we go to the database here, you can
see the genres here.
So, it's got a genre ID and a drama. and
we just would want drama, western,
fantasy, etc. We don't want the genre ID
returned. So, we want an array of genres
that the user chose when the user signed
up to use our magic stream website. And
you can see them here. Favorite genres.
For example, Ben Madison
chose comedy, thriller, sci-fi. we so if
it was Ben Madison that had logged on we
we would get three objects returned to
us from our query and we're now looping
through those objects those genre
objects favorite genre objects and we're
extracting just the genre name putting
them into an array and returning that
array to the calling code that's
basically what we're doing here so v
genre names this should be genre names
and it's a string array
okay let's create a for loop loop to
loop through the results. So underscore
just means no value needs to be returned
there.
So it means that the method we're about
to call returns two values, but we want
to omit the first value. We don't need
this value returned. We just want the
item returned. Okay. Range. That's the
range of values stored within favorite
genres array. fab genres array variable
here and then if
genre
map
okay
assignment operator item
so we're checking the type here bund
and we're these types come from the
MongoDB for go driver function
functionality. So it must be bon of oops
must be of type bon.d
and if that's okay. So this is a boolean
variable returned here. So if it's of
bon d the item is of bon.d
okay will be true and we want this code
to run.
So we do another for loop for
lum. uh the element operator
and then range genre
map. So genre map was returned from
this operation here
and then
we proceed with our logic here. LM dokey
we check the key must be equal to genre
name.
So of course genre name if we go to
compass here we can see the genre name
will be comedy for example thriller
sci-fi. So we're just checking
that
that is the name
of the field genre name. We're checking
that in this code here. Let's carry on.
So if all that checks out, name,
okay,
operator lm value
must be a string value. That's all.
Okay.
Then we want genre names.
We want to append the relevant value
extracted. So the relevant genre
extracted and put into our array
genre names
and name. So this here is being added to
the genre names array here.
Brilliant. And then
and then we can return that if it gets
to this code. So our for loop would have
been successful. We've basically
extracted the genre names from the array
which would contain an array of objects
which would also include the genre ID
but we want the genre names and we're
extracting that genre name and putting
it into the genre names array and we
want to return that array of genre names
to the calling code and we just go
return
genre names and null for exception
because if it gets to this
part here if it gets to this area here.
All has gone well and we can return an
array of genre names and no error
denoted by these definitions here within
the main definition of the get users
favorite genres function. So we're
returning an array of genre names and do
no exception.
Excellent. So we can go back to our
calling code. So, we're going back to
our get recommended movies function
here,
and we can now call the get users
favorite genres function
to get the users's favorite genres that
the user chose when signing up,
of course, to use our Magic Streams
website.
Okay.
Equals get users. Oops.
get users favorite genres and we want to
pass in the user ID to get users
favorite genres. So we've got our users
favorite genres. Now let's check if an
error occurred. If error not equal to
null,
let's handle the relevant exception like
this. http
status. So we'll send back a an HTTP
response
of status internal
server error 500,
[Music]
gen.h. And we're going to pass back
wellformed JSON
denoting the error that occurred here.
If one occurred at all errors. Okay,
like that.
Error, not errors.
And of course, I'll put a comma there
because I don't know, I'm out of
excuses. Should be a colon.
Okay. And then let's include the return
keyword here.
So that code halts at this point if the
favorite genres were not retrieved.
Okay.
And then let's include this code here.
Go. env.
Of course, if this is running in the
cloud, the env file may not exist. So,
we'll be checking for an error here
because we'll configure our environment
variables differently using the hosts
facilities. But when we're running it
locally, we're configuring our
environment variables within the env
file. So, what we want to do here, it's
not a fatal error. In fact, it's an
expected error if it's running in the
cloud. So, we'll just log that fact.
We'll just call it a warning here.
V file not found. Okay. So, it doesn't
really matter if the env file is not
found.
Okay. Var it will sorry, it will matter
if you're running it locally. Thatv file
must of course be found if um running
locally. So recall
let's create a variable called
recommended
movie
limit vile because we only want to bring
back five records from our query
64. So we're defining it as in N64 and
we're going to
assign it the value of five. We'll
configure this in a environment variable
at a later point because we don't want
magic numbers hanging about in our code.
It's just not good programming practice.
Okay.
So in fact let's do that now. I'm going
to go to the MV file here and let's
include and let's configure that
environment variable here
like that. So recommended movie limit
five. Okay, great. And now we can read
that value
within our code. Okay, assignment
operator OS.get
env. And we are reading
this environment variable here.
Okay,
that there.
Um, and the reason why that is kicking
up a fuss is because there's a type
mismatch here. Go is actually a strongly
typed language. And what we have here is
a type mismatch because this variable is
defined as N64.
And this will bring back a string. This
get env
code code here will bring back a string
and we're assigning a variable that I've
just defined as int 64 a string value.
So that's why we've got the red squiggly
line there. So this should actually be a
new variable. And I'm going to change
this to str. So it's a new variable. And
the red squiggly line has gone away
there. And this is because we're not
currently using the variable. But we're
using this to
to read
the string from the environment variable
into memory here. Okay.
So let's just check if recommended str
is not equal to what we can do is
convert
strr con using this str con package. So
we need to import that str conf.
So we're going to convert
the string red
from the environment variable. We're
going to convert it into an integer
using str conf using the str cov package
here.
Okay. So equal str
nv
dot par int. So we're parsing what we've
read
here into an int 64 data type.
Okay. And that looks good.
Great. Okay. Let's create the find
options variable now. Find options.
This is for our query that we're about
to send to our database
where we're going to find our
recommended movies. Five recommended
movies for the user based on the users's
chosen genres and
the ranking values that have been
assigned to each of the movies through
the use of AI through the use of lang
chain go. Okay. So find options
dot
set sort. So we're going to want to sort
the data by this key.
Oops.
By this key ranking dot ranking
value. So um okay.
And we want it in ascending order. So
value one for ascending order like that.
And we've got an issue here. P.
This is an object here. So we need to
include more curly brackets there like
that. And that's
now correct. Right. So the key is
ranking.ranking value. If we go to
compass,
you can see
if we go to the movies, if we go to the
movies collection, we've got the ranking
value
and we've got the ranking name. So,
we're sorting by ranking value here from
1 to five.
So for example,
if the this uh collection of movies
included Highlander 2 and Jack Reacher,
this would appear first because we're we
are sorting the movies
array. We are sorting the movies result
retrieved from the movies collection by
ranking value. So this would appear
first followed by Highlander 2 if for
example these were included within the
results. So that's all that is saying
there. Essentially we're we are ordering
the returned results by ranking value.
So the returned movie results by ranking
value in ascending order from one to
five. Great. And let's include the
filter for our query.
Okay. And we're querying
baston.
M
on genre name
and this must be in quotations genre
do genre
name
like that. Then bson
dom.
So we only want to print values back
from the movies collection
that include the relevant genres. The
user's chosen favorite genres
and you can sort of start to perhaps
understand this query now. So if the
user chose comedy, thriller, drama, we
only want movies returned from the
movies collection that are of those
genres. And this is basically what this
part of the query is establishing.
Okay. So then we want to
include this usual context code. I'm
just going to copy it there. This is the
cleanup related code that we include
when we use the MongoDB forgo driver
functionality.
Okay.
So let's create a cursor for our query
assignment variable movie collection.
We don't want to count the documents
dot find. And now we pass in
find options the filter and the context.
So firstly the context. Brilliant. And
then we can just check
the object. If not equal to
null,
let's handle the error. C.json
http
status
status internal. So we want to send back
an internal server error response to the
client. In this particular case, if our
query fails
and let's send back
an appropriate error message
in well-formed JSON format. And this can
just be oops, comma again. It's not
good. Replace that with a colon
error
fetching
recommended movies. Perfect.
Let's include the return keyword here.
Let's make sure that we clear up.
Let's use the defer method
to clear up our resources
within memory. So, we're doing a bit of
housekeeping here.
Okay.
Then var
recommended
movies.
Let's define this an array of
model
dot
dot
movie.
So we're returning we want to return an
array
of movies.
This should be models. Sorry, our
package is called models. That's what
that red line that's. That's why that
red line was there. So we're defining a
variable called recommended movies of
type models.mmov here. Okay. So if
assignment
equals cursor do all. So we're using the
all method to place all the results from
within the cursor
into
our recommended movies array. Okay. And
let's target
the memory address of recommended movies
like this. So this variable this here
points to a particular address in memory
and that's denoted by the amperand
character here. Okay,
then let's check the error. So if it is
not equal to null,
let's handle that error.
So I'm going to just copy this here
because it's quite similar
and
it's also going to be a status internal
server error if an error occurs there.
But we want to send a different error
message and we'll just send whatever is
returned by the dot error method. We'll
send that back to the client like that.
Excellent. And now we're in very good
shape.
We can now if it if the code gets to
this point, we can send back the results
to the client. JSON
http,
status.
Okay. So, we're sending a
status okay response. And of course,
okay, needs to be in capitals.
We're sending the a response of status,
okay, to the client. And then, of
course, our recommended movies array in
JSON format to the client. Brilliant.
And that's basically the recommended the
recommendation functionality
already written here.
Okay.
And it's got a problem with this declare
not used. Right? So I'm missing one
thing here. And I noticed that the
recommended movie limit
val had a red squiggly line under it.
Because it's declared and not used, we
of course need to use this. We're
reading it a limitation value of five.
And if that is not found within the
environment variables, we're actually
setting that limitation
um
by default here with this line of code.
But we're not actually setting the limit
for our query. Set sort, we're setting
the sort, but we need to set a limit.
And so after set sort here, because we
want to limit the results to five movies
for the recommendation for our user. So
to do that we can use the set limit
method like this. So find options dot
set limit and then we want to pass in
the integer value
here. This integer value stored within
this variable
here.
And where we're reading it from the
environment variables we're actually
converting it from a string to an
integer with this line of code here. So
we want to pass that integer into the
set limit method that we're using on the
find options variable here. So we're
setting we're using the set limit method
here to set a limit on the amount of
results that we want returned from our
query and we want five movie documents
returned and no more than five movie
documents. So we can do that with this
set limit method here and pass in the
limit that we've defined within by
default and within an environment
variable called recommended movie limit.
So if we go to env you can see we've
defined an environment variable named
recommended movie limit and we've set it
to a value of five.
Let's go back here
and we're passing that value to the set
limit method and that pretty much
means that our function is written and
ready to be tested. Okay, so we've
written the functionality for our get
recommended movies for our
recommendation service. Our get denoted
by our get recommended movies function
here. So we're now in a position where
we want to test the functionality
through Postman.
So firstly we of course need to map this
to an appropriate route. Map this get
recommended movies handler function
Jarnic handler function to an
appropriate endpoint path. So to do that
let's go to the protected_roots.go
file. So, we may as well define the end
point within the
setup protected route section here
because we're going to need the user to
log on. We're going to need to get a
token to test this functionality because
it's user specific. We're getting
recommended movies based on the user's
ID. So, in other words, the user in
order for the user to get a valid user
ID, they must be logged into the system.
So this is why we may as well define the
route for this even for testing purposes
within the setup protected roots method
here. Okay. So let's define our end
point. So for slashremented
movies like this,
controller
dot and this one is get recommended
movies. So, we're mapping an endpoint
recommended movies. This is part of the
path to the endpoint recommended movies
and we're mapping it to the function we
just created called get recommended
movies and that's within the protected
section because we need this or
middleware to run so that the user ID is
saved within the genic context here and
we are of course grabbing that user ID
in our code
here because it's a very important part
of our query where getting the user's
favorite genres because the recommended
movies are based on the users's chosen
genres that the user picks when the user
registered with the application. These
favorite genres were chosen by the user
when the user signed up to the magic
stream service. Okay. And this is the
basis for the recommendation service
that we're providing through the magic
stream website. And this function has
been defined for handling the
recommendation service functionality.
Okay. And you can see what we're doing
in this function is we're getting the
user's favorite genres within an array
here. And then we're querying on the
movies collection
for all of the movies that are tagged
with the users's favorite genres here.
Great. So we've now defined our endpoint
here and we've mapped it to the get
recommended movies function. This is the
handler function, the jinggonic handler
function and we've mapped it to a an
endpoint recommended movie. So we can
now test our code. So let's run our
code. So let's go go run dot run our
code.
Excellent. It's listening on port 8080.
Let's go to Postman. Let's launch
Postman here.
So let's firstly type in the log login
login endpoint like that. So this is the
base URL followed by the relevant
endpoint. The login endpoint. We're
sending a post request to this endpoint
because we need to log in with the
relevant credentials and I've already
set up those credentials. So we're going
to log in as Craig Denton here. And this
is just a standard password to make
things simple during the testing and
development phase. Okay, post. And
that's it. That's all we need. And let's
log in as Craig Denton. Great.
Excellent. 200. Okay. The server side
code for the login functionality has
returned to us a valid token. This is
the token which contains the user's
claims, the claims for Craig Denton. So
Craig Denton's user ID can be extracted
from this token when that token is
passed through to the serverside code so
that the recommendation service can be
leveraged so that user ID can be
extracted in order for the relevant
query to go through and bring back the
recommended movies for Craig Denton. So
let's copy this token to our clipboards.
And now we want to change this end point
to recommended movies
like this. And then we want this to be a
get request. And then we must go to the
authorization tab here and include our
bearer token.
So I'm just going to delete whatever is
in here and replace it with the token
that we've just received after logging
on as Craig Denton. Let's paste that in
here into this field. And now we should
be able to leverage our recommended
movies endpoint which should bring back
the recommended movies for Craig Denton.
So let's try that out. Let's press the
send button.
Excellent. And it has brought back Jack
Reacher.
Okay.
Um which is tagged up with the these
genres action and thriller. Okay. Okay.
And you can see this is the admin review
we gave it fairly recently when testing
the AI functionality and it's been
ranked as one excellent. That's why it
is listed first within the list of
movies returned from our query.
Okay. And then we've got Knives Out
which is also which is a drama and a
thriller. It's tagged up with drama,
thriller, and mystery. And it's also got
a ranking value of one. ranking name
excellent.
And then we've got one that has been
also excellent. So, we've got three
movies returned to us that are excellent
for Craig Denton. And this one's called
Blitz, the Jason Staithm movie. And then
we've got one that's just okay and it's
a comedy and it's Airplane. And then
we've got another one that's okay. So
you can see that it is ordered
the movies from one to five
based on the ranking value. So all the
ones, there's three ones, three that
have been ranked as excellent and then
two that have been ranked. Okay, so it's
returned the movies in the appropriate
order and it's returned five a limit of
five movies to us. And we can try again
with a different user this time. So,
let's log in as someone else. Okay,
let's go to Compass and find a different
user here.
Um, so let's log on as Ben Madison. So,
Benmadison,
benmadison@hotmail.com.
I'm just going to copy that there for
the credentials. And the password
doesn't change. Okay. So, I'm going to
the body here,
raw, and I'm going to replace this email
address with Ben Madison's credentials.
The password doesn't change. So, I've
just made this easy for testing
purposes. This must be a post request.
So, let's set that to post. And let's
send that to log us in as Ben Madison.
And we got a token that's been sent to
us
here. Let's copy the token. Now let's
change this to recommended movies
and this is a get request and let's
replace the token
authorization bearer token here.
Let's select all of that. Delete what's
in there and paste in our new token for
Ben Madison. And shouldn't have a quot
the quotations should be omitted here.
Okay, there shouldn't be any quotations
within the token. So, let's test our
recommended movies end point for Ben
Madison. Let's see what movies get
returned for Ben Madison. So, let's send
that through.
Excellent. Okay, it's got Jack Reacher
again at the top
and then it's got Knives Out.
Okay.
Blitz
airplane
and this time it says so there is a
difference here. So they obviously had
very similar genres selected and then
we've got the undiscovered country which
was different from the last set of
results and it is ordering these movies
in the correct order from one to five.
Okay,
for good measure, let's log on as
someone else. So, let's log on as Bob
Jones who's an administrator. Okay, so
let's grab his email address here.
Let's go to login
here. Set this to post.
Okay, let's go to authorization.
Set this to no orth. And we're going to
go to the body here.
Replace the email address with Bob
Jones's email address. The password is
the same. And let's send that through to
login. Bob Jones. Okay. Good. And you
can see Bob Jones's favorite genres
here. He just likes comedy and fantasy.
Okay. And so let's grab the token for
Bob Jones here.
Copy it to our clipboards.
And let's change the end point here to
recommended
movies like this. This must be a get
request. Let's go to authorization.
Select bearer token and let's replace
what's whatever's in here with the token
for Bob Jones. Okay. And now
let's test. Bear in mind Bob Jones's
favorite genres are comedy and fantasy.
and let's see what movies are returned
for Bob Jones.
Okay send.
Okay, so fantasy. You liked fantasy and
we've got Harry Potter and the
Philosophers Stone. And this movie's
been deemed as okay based on the admin
review. This movie wasn't great, but it
wasn't bad either. So, it's okay. Three,
we got Airplane. He liked comedy, so
that's correct. Airplane.
And it's also been ranked as okay. And
then
the next movie is Gone Girl, which is a
drama and a fantasy. Okay, so fantasy
has been tagged for Gone Girl. I'm not
sure if that's accurate, but in our
data, it's been tagged with fantasy. So
that's why Gone Girl has come back and
it is ranked
as okay. So that
is ordered correctly within the results.
The Hangover comedy three. Okay, so
they're all okays basically,
[Music]
right? So our recommendation facility is
working correctly based on the data
that's saved to our database. Obviously,
this is just test data, so don't expect
it to be 100% accurate in terms of the
genres and the rankings, but our code is
working correctly. This verifies that
our code is working correctly.
Our recommendation service is working as
expected. Excellent. So if you'll
recall, we created this end point and we
temporarily
added the code to configure the endpoint
i.e. map it to the relevant handler
function, the jinggonic handler
function. And we initially put this
configuration code within the setup
unprotected routes just for testing
purposes. So it would made it easy for
us to test the functionality within this
function here which provides
functionality so that an administrator
can add movie reviews to the system and
update those movie reviews within the
system. Um so if we go to compass here
you can see those movie reviews.
Go to the movies collection. Each of
these has a movie review. This is a
lovely movie and it's been rated as
excellent by the AI. The sentiment
detection functionality that we wrote
which engages with AI through lang chain
go brought back excellent as the
sentiment behind this movie review. So
it's all this functionality we're
concerned with but only an administrator
should have access to this
functionality. So we don't want ordinary
users to be able to add reviews to the
system. And that's what we're going to
do now. We're going to protect the
relevant update review endpoint. So the
first step to do that is we're going to
grab this code here.
Let's cut the code and let's paste it
into the protected end point
configuration code here within the setup
protected roots function here. So now
this is protected but we haven't yet
written the code to properly protect
this route because this is a special
route if you like because the user must
not only be logged on but that user must
be part of the admin role. So that's the
code we're going to implement in this
section of the course. So let's do that.
Firstly, we want to be able to extract
the role from the claims, which we're
doing here. And we're inserting the role
within a named value pair within the
context, the ginonic context, which
makes it easy for us to extract the role
in later code.
Um, so we can extract
whatever role once the token has been
accessed from within the middleware and
it's been validated. We can extract the
role. This is what we're doing here.
We're extracting the role and placing it
within a named value pair storage
facility provided to us by the Genggonic
context. And then it goes see next and
it'll move on to the targeted endpoint
which in our particular case will be the
update review endpoint and we'll be able
to access that role through the
jinggonic context. Okay. So if we go to
movie if we go to yeah movie_controller
and we go to our add review
functionality. So we've got our admin
review update function here. The first
thing we want to do is access what role
the logged on user is part of. So to do
that we can let's first
go to the token_util.go
file and let's just create a copy of
this.
And then let's just change the bits
appropriate to what we're trying to do
here, which is instead of access the
user ID, we want to access
the user's role. So get RO from context
is the name of this function. And of
course, we need to change this to RO.
And let's make this variable
RO instead of user ID.
So roll
does not exist in this context. If the
RO is not if you're not able to extract
the RO from the Genonic context, we need
to handle that error there. And then
let's use our RO variable here. Let's
name this
member roll camel case like that.
And then we can return the member roll
to the calling code. And that is our
reusable function written for extracting
the role from the jinggoni context. And
let's go back to the movie_controller.go
file. Okay. Okay. And now we can use the
the function that we've just created
within
the utils package to
extract or to return the users ro
to this calling code here. So utils dot
get roll from context
and then we must pass in the gingonic
context variable here or the gingonic
context argument here
and then let's handle the error if an
error occurs when trying to return the
role the user's role
let's handle that exception.
Okay. So, C.JSON.
Let's send back a HTTP bad request in
this particular case.
Status
bad request. Oops
request.
And an appropriate error message.
And we can use the gin.h
functionality here to return wellformed
JSON.
Let's include a colon there. And then
roll not found in context.
Okay. And then let's of course
include the return keyword so it halts
the execution of this function. We don't
want this function to continue if the
role is not found
because the user that's trying to add a
review must be part of the admin role.
So it's crucial to this functionality
that the user's role is accessible.
Okay. And then now we can just do a
check here. So if roll and I'm just
going to leave the code like this for
now just for simplicity.
We could put this in a configuration
file but I'm just going to leave it like
that as a literal string admin.
And just for simplicity. and let's
handle the exception. So if the user is
not part of the admin role, we need to
pretty much kick the user out so that
they do not have access to this
functionality.
And it's a status bad request again.
Okay. And let's use jin.h H
to send an appropriate error
back to the client code.
And we just say user
user must be part of the admin
role. And it's that simple. And then we
use the return keyword to stop the
execution of the code within this
function.
Okay, brilliant. And that is pretty much
it. So now the user
has to be part of the admin role in
order to be able to leverage the
functionality within this handler
function. The function that handles the
endpoint configured here, the endpoint
update review.
Okay. So we can test that code now. So
now let's launch Postman
and let's go to
login. We need to log in the user.
Okay. And we want to log in this user as
Bob Jones. Okay. Let's go body raw.
Okay. Great. We've already got Bob
Jones's credentials
here. And this is a post request.
Okay. Brilliant.
Okay, so let's run our code.
Okay, brilliant. It's listening on port
8080
and let's go back to Postman and we can
test our login functionality. Let's
press the send button here.
Excellent. Bob Jones is logged in. Bob
Jones is an administrator. You can see
here his role is set to admin. So, he's
an administrator. And we just want to
copy
Bob Jones's token here.
We want to copy Bob Jones's token to our
clipboard. Let's do that. Copy.
And then we want to test the update
review endpoint. So we go update review
like this. And we want to
update the review
for a particular movie. So, let's go to
the compass and let's find a movie that
we can update.
Okay. And as I've said, I hate this
movie for um Unforgiven. And that
couldn't be further from the truth. I
love this movie. So, actually, let's
test a review for Unforgiven. Adding a
review for Unforgiven. Great movie. Gene
Hackman, Clint Eastwood. Excellent
movie. So, it's currently set to five.
Terrible. I hate this movie. And let's
create a review that is a little bit
more glowing. Well, a lot more glowing
than that. Okay. So, so I'm going to
copy the IMDb for unforgiven here.
Okay. And I'm going to paste it here for
the parameter
so that we can target a particular movie
for the review. And then let's set this
to patch because we're only partially
updating the movie document. We're not
replacing an entire movie document.
We're just replacing this part, the
admin review. And then our AI
functionality will automatically extract
the sentiment and save it to this movie
document, this part of the movie
document here. Okay.
So great, we're set up to test this. So
it's a patch request. We've copied the
token to our clipboards. So that's the
next thing. Let's go to authorization
here. Bearer token. Let's replace
whatever is in here
that's left over from the last time we
tested an endpoint through Postman. And
let's paste the new
and let's paste the new
So, let's paste the Let's paste the
token from that Bob Jones received once
he logged on. and
paste it here.
Okay, brilliant. And now we need to
update the body. So we need to
include a field within the JSON here,
admin
review. And let's
create a glowing review for unforgiven
Clint Eastwood
as always was magnificent.
What an amazing
amazing
cast and
story.
Okay, Clint Eastwood were Clint Clint
Eastwood, as always
was magnificent. What a what an amazing
cast and story. Okay, so that's our very
simple movie review for Unforgiven. And
let's just make sure that this is
appropriate JSON data that we're passing
through. So, I'm just going to go back
to the code here.
And if we go to
the handler function here, let's just
check.
So yeah, it's expecting admin review.
And the JSON must look like this. Admin
review. You can see that by the JSON tag
here. And then whatever we pass in here
gets mapped to this wreck strct here.
Okay. Within our code. So that looks
good. And then we can just test that. So
we've got the endpoint URL which
includes the movie parameter, the IMDb
value. Update review. Let's just make
sure that the endpoint is 100% accurate.
So if we go to protected roots, update
review, and then followed by the
parameter. So let's go back to Postman.
And that looks good. We've included our
review within the body of the HTTP
message we're about to send through to
our endpoint.
We've configured the token because we
just logged in as Bob Jones who's
definitely part of the admin group. So,
this code should actually work. And
let's see if it does. We'll press the
send button.
200. Okay. Excellent. Yep. I would have
expected excellent to be returned as the
sentiment behind the admin review that
we've just created for Unforgiven.
Brilliant. So, that has worked. we've
been granted access to the endpoint
because Bob Jones is a part of the um
admin role. So that has worked. So if we
go to compass, we can see now if we
refresh the data, reload data and we go
and find unforgiven
after we've refreshed the data. There it
is. And let's look at the rankings. So
it's updated our review. Clint Eastwood
as always was magnificent. What an
amazing cast and story. And then we've
got the ranking section here. And the
ranking name is set to excellent. So the
AI deemed this movie review as having an
excellent sentiment. And of course the
value for excellent is one. So that has
worked perfectly. Brilliant. So I guess
the thing we can do now is just
deliberately perform a test that fails.
So to do that we can type in login here
and we can login as just an ordinary
user. So we've got Craig Denton.
So let's change this back to email and
this toradent
hotmail.com.
And the password
for all of our users
is just
a very simple password. Not very secure
password, but this is just for testing.
Um,
and it is, I believe,
at one exclamation point like that. So,
we're logging in as Craig Denton. We
need to set this to post. It's a post
request for the login functionality. And
then we can just send that over.
Brilliant. 200. Okay, we've logged in as
Craig Denton. So, let's copy Craig's
token to our clipboards here. Oops.
Here it is.
Okay. Copying Craig Denton's token here.
Let's go to authorization and replace
the token left over from the last test.
Let's remove that and add in our new
token for Craig Denton's login. Okay.
And then let's update
the endpoint path. So it's update re
view and unforgiven. Let's just copy
that token
again. We'll stick with the same movie
unforgiven.
Let's include it as part of the URL. So,
it's a parameter within the URL that
targets a particular document within the
movies collection. So, we're targeting
unforgiven
with this URL here. So, we're updating
the review for unforgiven.
At least we're going to try. So, we're
actually hoping that this will fail
because we want users who aren't part of
the admin group. If we look here at
Craig Denton, at Craig Denton's details,
he's just a user. He's not part of the
admin role. So, this should we should
get a 401 unauthorized error. So, let's
see if that works. Let's see if Craig
Denton is prevented from adding a review
to the database by our code. Okay. So,
let's hit the send button. 404 not
found. Okay. So, that's not what we
wanted. Review. Update.
Review. What?
Okay. So, that's not the error. This
should be a patch. That's why we've
received this 404 not found. So, let's
try again. It should be a 401.
Okay. So, not a 401. We've actually sent
back a 400. bad request and that's just
because that's the way that I wrote the
code. So if we go back to the code that
is correct. We have been kicked out. We
weren't able to use the update review
functionality
but I think this would be more
appropriate
as a 401. So this should be status
unauthorized. Yeah, you don't have the
authority to access this endpoint. Let's
save that.
So let's save that and let's test that
again. So go run dot enter.
Okay, it's listening on port 8080.
Let's launch Postman and let's try that
again. Okay, we've still got our
settings there. Last time it sent us
back a 400 bad request. That's because
of the way we wrote the code. The logic
is correct, but we should be passing
back a 401
unauthorized exception rather than a 400
bad request because this user is not
part of the admin role. It's a valid
user, Craig Denton, that can be
authenticated, but not as part of the
admin role and only administrators can
add reviews to the system. So, we want
Craig Denton kicked out when he tries to
access uh the update review endpoint.
So, let's press the send button. 401
unauthorized user must be part of the
admin role. Great. And that's pretty
much the server side code all written.
And we can now move on to writing the
client code, the React code. So, we've
coded the server side part of our code
and have successfully tested each
aspect, i.e. each of our endpoints
successfully. Hopefully you've noticed
that at times during the code creation
process I have flagged in the way of
including arrows and appropriate text in
post-production that even though the
code works there is a better way of
writing the relevant code. So this is
what we are going to cover in this
section of the course. We are going to
look at some practical examples of a few
go jinggonic best practices by
appropriately amending just a few pieces
of code that we have already created and
successfully tested. So we are going to
address a few minor coding issues in
this part of the course. We are not
fixing any code per se but we are
amending the code in order to adhere to
best practices. Please note that any
keys for example secret keys like these
ones here
or API keys like for example open AI API
key here for interacting with endpoints
on open AI i.e used for accessing
third-party endpoints
for security reasons can come from a key
management system from a cloud provider
and therefore in production would not be
embedded within the ENV file as we are
doing here.
We are reading in the values from thev
file here. Let's look at our code. For
example, you can see here we're checking
to see if that
env file exists because we're running it
on our local machines here. But
regardless of where we're actually
reading the relevant environment
variables from, this line of code is
actually reading in the value. And this
could come from a key management system
or it could come from the cloud
provider's own
environment variable facility.
Environment variable management
facility. Um, so that code doesn't
actually change. But here we are just
checking whether the env file exists and
we're not throwing a fatal error. We're
not logging a fatal error if the env
file doesn't exist. So when the code is
actually running in the cloud, this will
actually result in an error occurring
because thev file will not exist on the
cloud, but all it'll do is log a
warning. It won't log a fatal error. And
this line of code here gets called
regardless of whether the code is
running in the cloud or locally.
So it doesn't actually matter where the
environment variable is coming from. But
we are performing a check here just to
make sure that it is on our local
machines so that it can tell us that our
code can tell us as it were if the
environment variable exists on our local
machines
um or not. And ob obviously when we're
running it locally the environment
variables thev file must
exist on our local machines but it won't
exist in the cloud. So this will be
logged in the cloud which is fine
because it's just a warning message.
It's not a fatal we're not logging a
fatal error. So it is best to store the
relevant key values. So we got these
key key values here. For example, the
well, we've got this key value here,
OpenAI API key, and these uh secret key
values here that sign the relevant
tokens. It's best to store those in the
relevant cloud hosts key management
system,
as a security measure, as an extra
security measure. Right? So
if we go to let's perform our first
best practice code amendment if you
like. So let's go to
database_connection.go
here. So here we are handling our
connection to our MongoDB database. We
also have a reusable
function called open collection here
which is reused throughout our
application where appropriate code is
executed to open a specified collection
within our MongoDB database. So we are
using the MongoDB for go driver here
within our code to interact with our
MongoDB database and we are centralizing
this reusable functionality within this
file. Now here's the actual issue. when
we import this package, what we're doing
here, which is not really good, is we
are actually automatically dialing the
MongoDB
uh instance. So, we we're dialing
MongoDB here when we import the package.
We actually don't want our code to
automatically dial MongoDB.
We want it to only establish a
connection. So, by dialing, I mean
establishing a connection with MongoDB.
We only want to do that at when when the
MongoDB connection is actually uh
necessary when it when it's going to be
used. So we don't want to do this uh
like like we've got it here currently.
So we're going to copy this to the main
method. We're going to call this code
from the main method and pass in we're
going to inject the client into the
where we're setting up the roots here
and then we'll inject it into the
relevant handler function methods. So
that's what we're going to do. But
before we do that, this DB instance
isn't a really good name for our method.
So because we're using the DB prefix
here, which has special meaning for
MongoDB, so instead of calling it DB
instance, I'm going to change that to
connect. So we'll keep the method name
very simple and just call this connect
like that. And then we can change this
code over here to
connect. But we're going to have to
change this code further because we're
going to import this database package
from within the main package. And then
this code will be changed to
database.connect. But let's cut this
code. Ctrl X. And I'm going to go to the
main method now here. And I'm going to
paste it in before this line of code
here. uh sorry after this line of code
here just before where we're setting up
our roots I'm going to paste in this
code where we will connect to
establish a connection with MongoDB. So
we need to import the database. So let's
go back to database connection there.
And we're also going to want to import
these. We're going to want to import
this package here. So let's go back to
main.go and make sure that we're
importing that package. So let's ensure
that we're importing the package
there.
Okay.
So that underline has gone away now. But
the problem here is we haven't imported
the database package. So let's go here
where we're importing the database
package and just copy that importation
code. So this code here and let's paste
that here.
like this. And then we can call.
Is there a problem there? Uh,
yep. Let's paste that in. It's because I
saved it and we're not using it. It
removed this line here. So, I won't save
it. But we want this um importation code
in place because we're going to use the
database.
We're going to use database.connect
here.
Dot connect like that. And now we're
going to pass in the client
to where we're setting up the roots
here.
And in fact, this should be a lowerase C
for client because it doesn't need to be
available to any calling code. So we'll
keep this a local variable client and
the underlying CR because we haven't
actually included a parameter definition
for these client values here. So that's
the next thing we need to do. So if we
go to setup
setup protected roots
protected_roots file.go,
we now want to include the client here.
So client which is of type
mongo.client
like that.
Okay. And we don't have the
package. So we need to paste in the
package where we we're pasting it
in here.
So we need to paste that in within
the roots package also. So let's include
that there within the sorry
protected_roots.go
file so that we have access to the
relevant type there. And then we're
going to also inject this client into
all of these
roots here. These handler functions,
these genonic
root handler functions if you like
endpoint handler functions, they need
the client in order to connect connect
to MongoDB. Okay. And we need to do the
same in the unprotected roots. So let's
do the same here. And we need we can
just paste in this
package importation code here
like that. And then we need to include
this parameter definition within the
relevant method like this. And then we
of course have imported that. So that's
working correctly. And then we want to
just pass in the client like this.
And of course you've noticed that
there's red squiggly lines there because
we don't we haven't included a parameter
definition within these handler function
these jonic handler function
methods. So
um we'll do that in just a bit. Let's
just copy let's just paste the client
in like this so that it is being passed
in to these handler functions here these
gingonic handler functions. So these
functions handle
the logic for when these end points are
called via the relevant HTTP methods get
post get and post in this particular
case. So the next step as you can see is
to include the client
this definition the client definition
for parameters within each of these
handler functions. Let's handle this one
first. So movie imdb. So it's get movie
here.
So get movie. We can actually just
navigate to it by selecting the method,
right clicking and go to definition. And
let's include
that definition like that there. Great.
Let's go back there and now add movie.
We need to do the same here. So go to
definition and let's paste that in
there.
And we just repeat it. You can see the
red squiggly lines are disappearing as
we make the relevant coding amendments.
So go there and paste this definition in
there. Let's go back to protected roots
there. And we want admin review update.
We need to amend that code. And let's do
that there. So we're passing in the
client there. Okay. And let's do the
same now for
for the the roots for the protected
roots the the sorry the unprotected root
handler methods if you like the function
the handler function methods here. So
let's go to get movies
and let's include that definition there.
And let's just go down
and repeat this process for each of
these handler functions
like this.
Okay.
get genres.
Okay.
And then refresh token handler.
Great. And then let's just go through
each of these functions and make sure
that our code is amended
appropriately.
Okay. So, next thing I need to point out
is these variables. We actually don't
want to do that here. Um, it's quite
cheap to create these connections to or
open these collections. It's quite
cheaply done behind the scenes. So, we
can actually just open these collections
as we go. So, I'm going to
cut this code here and I'm going to
include it here
just above this line of code here
like that.
And now we need to
pass in the client
like that. We're going to inject in the
client like this
for this open collection method. And now
we need to amend this open collection
method. So let's go to do definition
here. And we need to include
this definition here.
like that because we're going to be
passing in the client
like this here.
And let's make this a small C where
we're connecting to the relevant
collection like that.
So we're opening the collections in a
slightly different way now and we've
amended our code. There is this reusable
code which is centralized within the
database_connection.go
file here and then we can just go back
to moviecontroller
here and that should be pretty good.
That's okay. And I think that's all we
need to do in regards to opening the
relevant collection.
Okay. And then let's go to the next
method. So let's go to protected roots.
We got add movie here. So let's go to
the definition and we want to do the
same thing.
Okay. So we actually amended get movies
there. Um we want to do the same for get
movie. Okay. So it's the same kind of
code. We can just paste that in there
like that. We're passing in the client
and we're handling the movie. We're
opening the movie collection in a
slightly different in a slightly
different way now. like that. Okay. So,
let's go back to protected roots there.
So, we've done ad movie.
Let's make sure that we well we have
amended ad movie.
Okay. We haven't. So, we need to do the
same thing here.
So, we can open the collection here like
this
for the add movie method.
Okay.
Let's go back to protected roots. So
we've done get movie, add movie, and we
want to do re get recommended movie. Get
recommended movie, sorry. So let's go to
definition there.
And it's just the same repetitive
task of making sure that we are
now opening the collection
appropriately.
Let's go back to protected roots here.
And let's deal with admin review update.
So let's go to definition there.
And where we are making our colle our
connection where we are opening our
movie collection. We need to include
this line of code here just before
our code engages with the movie
collection here.
Great.
Okay. So, we're now handling
our movie collection, connecting to our
movie collection appropriately for the
protected routes. So, no red squiggly
lines here. We're getting there slowly.
It's a bit of a tedious process, but it
is worth it. Okay. And then we've got
get movies. And we actually I
inadvertently did that thinking it was
get movie.
So, we've actually amended this code
here
already. And all we're doing is opening
the movie collection before we're using
it. We're opening it locally here, not
up here. We don't need to do it up here
anymore. And we're going to do the same
for all of these collections in just a
bit. So, we're now opening these
collections in a different way locally
when we need them. And we're doing that
because it's fairly cheap to open these
um collections
behind the scenes. So, it's not a
problem to do it this way.
Okay, so let's go to So we've handled
that unprotected roots. We've handled
get movies register user. So let's go to
definition.
And of course now we're changing
the code regarding
this user collection. So let's cut that
and we want to paste it in here
just before we're using the user
collection. like that there. And that's
all we need to do. And that's not all we
need to do because we haven't passed in
the client. We need to pass in the
client like this. And then let's make a
copy of that
code. Let's go back to the unprotected
roots code here. And login user. So
we're going back to the
same controller and amending login user
with the way we are now engaging or now
with it. the way we are now opening the
relevant collections
and we're just pasting that code in
there so that we're opening the user
collection appropriately before we use
it within the login user method and then
let's go to unprotected roots again log
out will be the same thing. So let's go
to definition
log out here.
Okay. And with log out
we're updating all tokens. So we'll have
to pass in the client here to this
update all tokens method within utils.
So let's include the client there
because we're now passing in the client
to each of these handler functions. So
we include client there and then we need
to
um we need to copy this definition here
so that we can update the update all
tokens method within our utils package
and we need to include this definition
here client MongoDB
and we're using the now the user
collection at this point here
so we need to amend we need to include a
line above this code that actually makes
that connects to the colle that opens
the collection.
Okay so
here it is here. So we can just cut this
code here
and
paste it in just above
the line of code where we're actually
using the collection. Of course, we need
to pass in the client
like this.
Okay, let's go back to
unprotected roots code, the setup
unprotected roots function.
And now we're dealing with the genres
here. So let's go to that method.
Go to definition. So you can see here
we're using the genre collection. So we
want to eliminate this code here
that we've defined outside of any
particular function. We don't want to do
it like that anymore. We want to do it
the way we're handling the get movies
collect the opening of the movies
collection.
So we need to go back to
sorry this unprotected route get
genres handler function and paste
that
open collection code just above where
we're using the genre collection and we
want to pass in the client like this.
Okay. And that is looking pretty good.
So we're getting there. Let's go back to
unprotected roots. And then we got
refresh token handler. I don't know if
we're actually interacting with the
database in this code here.
Validate refresh token.
Yeah, we are. We we've got the we're
engaging with the user collection here.
So, we need to update
this method with a with code to
open the user collection here.
Okay. So we can just copy the relevant
code from
uh
yeah
let's go back to unprotected roots
refresh token handler
go to there and then just above here
include
as I said earlier if you get stuck at
any point and you're not able to follow
along with this code or you find that
you get lost, um you can check out the
final code
at this URL. The URL to the relevant
GitHub repository has been included
below in the description. Okay, so we're
updating it there. Find one, right? And
then we've got update all tokens. We
need to pass in the client here.
Excellent. That looks good.
And then I saw a red squiggly line up
here.
There it is. Refresh token. Update all
tokens. We just need to pass in the
client. When we're logging in the user,
we're updating the tokens within the
user collection for the relevant user.
So, we need to pass in the client object
here that connects to the to MongoDB.
Okay. So, that's the connection to
MongoDB
there. And we're now
handling opening the relevant
collections
as we need them within local function
handlers or handler functions.
Okay. So one thing we haven't done is we
haven't eliminated if we go here we are
still using
this rankings. We're still opening the
ranking collection in the old way. And
this of course is now not going to work.
So what we want to do is cut this code
here and we need to find
the function called get ranking. Now
it's this is a method that's called from
within one of the handler functions. So
it's not being called directly by an end
an end point. So we need to actually
include the client definition here. So
let's do that. client
uh star dot
client like this.
And then let's include the code that we
just
copied to our clipboards that we cut and
let's paste it here. And then let's pass
in the client object like that there. So
now we're opening the collection in the
in the in a consistent way. And we're
doing it now for the rankings
collection.
So we're only opening the collections
when needed within the local within the
various local functions.
Okay, great. And then we need to find
out where this has been called. So we
can go find all references.
Go here and we need to Okay, we're going
to need to pass in the client down to
here also. So I'm going to include
client
as star mongo.client
like that. And we need to pass in client
there.
And then we need to find where we're
calling this. So we can find all
references there.
There we go. And here we're defining
client within the ad. This is the actual
handler function, the jinggonic handler
function. So we're handling an actual
endpoint in this function here. And
we're calling these these functions from
within the parent handler function. So
we're calling get review ranking which
calls get ranking. And we need to pass
in the client here. So let's client
there. And the red squiggly goes away.
And now we are handling opening the
relevant rankings collection
in an appropriate way. if you like.
Okay, we've got a problem there. Let's
go and see what the problem is.
Undefined user collection here. So,
we've so this is also a function that's
being called by one of the handler
functions.
Um, and we don't have the client object
available to us. We're not opening the
user collection in an appropriate way
yet here. So, we need to handle all of
those things. So
we need to find
where we are
opening the user collection or users
collection
and let's go back there and let's go to
where the problem is and let's paste in
that code. Now we're opening
we're opening the collection
appropriately but we don't have the
client object. So we need to include the
relevant definition for the client here
the MongoDB client. So client and that's
defined as mongo.client
like this. Okay. And we're passing it in
as an argument there like that. Okay. We
still have a problem.
Okay. Yep. Let's go to the problem and
of course we this is the get recommended
movies
handler function which handles an actual
endpoint call. This is the logic for an
actual call to an endpoint
using a HTTP method from the client. So
we need to pass in the client that is
passed into this handler function
to this method. Like that
we have no problems now.
Excellent. So we're also opening the
rankings collection appropriately now.
So what we're doing here now is we we're
only opening the collections when we
need them from within the relevant
functions.
Whereas before we were opening the
collections outside of any one
particular function call which is not
what we want to do. So this is a better
way of doing it.
Excellent. Okay. So the next thing we
want to do and I would say this is
actually quite an important fix is we
want to pass in this ginonic context
object to the width timeout method here.
And let me explain why we want to do
this. Okay. So this is quite an
important fix. We are currently not
handling this resource clearing related
code correctly within our Jonic
application. Here we are passing in the
context.background object to the with
timeout function. We should be passing
in the jinic context here. Jin
implements context in a way that it
embeds the requests context.ext.
So you can safely use it wherever a
context.ontext
is required. Why not context background
like we're doing here? If you use
context.background background instead
you lose request cancellation
propagation if the client cancels the
HTTP request closes the connection
navigates away etc. Jyn cancels the jin
context so using C as the parent context
means your timeout context is
automatically cancelled when the request
is canled using context.background
background ignores client cancellation.
So your handler may continue doing work
even after the client has gone away.
That can waste resources. You lose
deadlines and other request scoped
values. Jyn may attach request scoped
metadata or timeouts in its context.
Using C ensures you inherit them. Use C.
Your context respects client
cancellations and request scoped values.
Use context.background.
Your context is independent, ignores
client cancellations and could lead to
resource leaks. So we must pass in C the
gingonic context here rather than
context.
Right? So let's handle this. Let's make
sure we are doing this. So let's pass in
C here firstly.
Okay. And so that's basically what the
fix is. It's very basic. So we need to
do it wherever we see this. We need to
pass in the gingonic context instead. So
let's select that and let's go to find
all references and let's go through them
one by one. We've handled that there and
we need to handle it here. So we
uh pass and see there
go there. Pass and see here.
Okay, select this one. So, we need to
pass in C here for admin review update
there. So, we're passing in the ginonic
context object
that is passed into this anonymous
function here. This is where our root
handling functionality exists. This is
the handler function for an endpoint.
So, we're passing in the jin context
object instead of context.background.
background
um for the resource handling code
here.
Okay, excellent. Let's go to the next
one.
Okay, passing C here.
Next one. Pass in
C here.
Okay, go to the next one. We need to
pass in C here. Now, this one's a little
bit different because this is called
from an actual handler function. So,
we're not implementing a gingonic
handler function for this method. So, we
need to pass in C to this method. So, C.
So, we can do that at the end here.
Let's go comma C
and let's define it appropriately as
sorry Jin dot
context like that there. And we of
course need to ensure that we're passing
the context to this
um
to this function here. So let's go find
all references and we need to pass in C
here
to fix that there.
Okay, brilliant. Find all references and
you can see we are here. This one here
we're not yet.
This is uh this is calling get rankings
from another function that's not a
gingonic handler function. So we need to
also pass in the C here the Gonic
context. So let's do that. C
uh as star jin context like that and
then we can pass C down there
to get rankings function and then we can
just find all references here and make
sure that we're passing C in here
like that.
And that looks pretty good. We're now
handling this clearup code, this
resource clearup code effectively by
passing in the gingonic context to the
width timeout method here, which clears
up the relevant MongoDB resources. So
the next code I want to amend is within
the main.go file, the main package here.
So it's actually good practice to
ping the database
before we set up the roots here because
if we don't have any connection to the
database then our code is going to fail.
So this is just a better way of handling
the case where the where we are unable
to establish a connection to MongoDB. So
we can do that with this code. If
equals to client. We've got our client
object. Now we're connecting to we're
estab trying to establish our connection
here. And then we're going to try and
ping. We're going to try and see if we
can if our mong if MongoDB is receptive
to us connecting to it. So uh this is
not connect context do background like
this and then null. We don't have jin
gonic context at this point. So we can
use context.background. background as
the context here and then null and then
on the same line we can check the error
to see if this object has a value. So if
is not equal to null.
Okay, we want to handle an error if we
are unable to ping. Of course, null has
one L. If we are unable to ping
MongoDB, we actually want to log a fatal
error here. So we use fatal
LF like that. And we can include an
appropriate message here. Failed. We're
not reaching the server. failed to reach
server. So we're flagging a fundamental
issue here. So we don't want the code to
continue. There's a fundamental issue
and by logging it we we should by
examining the logs
understand what is actually happening.
So there's a problem with us connecting
or reaching
um the actual server. So our client
can't ping MongoDB. So we need to
actually stop execution
here and log an appropriate error and
it's a fatal error. So we don't want the
code to continue and set up the roots
because we know that it's going to fail
downstream anyway. So we can handle that
issue at this point here in the code
within the main function there. And the
other thing I want to do is
um make sure that we're disconnecting
from the database and we can ensure that
we disconnect at an appropriate time
from the database and this is to do with
clearing up resources.
So defer funk and then we want to
equals to client disconnect
like this
disconnect.
Okay, we need to pass in the context
dot background method there like that.
And then we can check our error object
and see if it's null. If it's not null,
we want to log a fatal
error here.
Okay.
And we can now let's amend this message
so that it is appropriate to what is
occurring here. So we're trying to
disconnect at an appropriate time. We're
trying to disconnect from MongoDB. Say
for example, an error occurs, we want to
disconnect from MongoDB. And if that
fails, we need to log that fact so that
we're aware of what has happened.
So failed to disconnect from MongoDB
like that.
Okay. And we're logging the appropriate
error. Open and close brackets there.
Space there.
And that's pretty much it. So, we're
just making sure that these cases are
being handled appropriately, making sure
that we can connect to the database to
MongoDB before we set up the relevant
routes so that it fails here at this
point
um before it's going to fail anyway if
the if we're unable to connect if we're
unable to reach the server at this point
here. So, that's what this ping
functionality is all about. And then
this defer functionality is just to make
sure that we're disconnected from
MongoDB. We don't want to have those
resources hanging about in memory
unnecessarily. We need to make sure that
we're disconnected from MongoDB.
Okay. Excellent. And the other thing is
I'm not currently checking whether the
environment thev file exists before
calling the code to read in the
appropriate environment variable value
here. and on our local machines that's
quite important. So let's include the
relevant code.
Um,
so it's just
colon equals
go do
go.env.load
and then within quotations env like
this. And then we just check our error
object.
If it's not equal to null, an error has
occurred. And we just want to log a
warning at this point because
this error is going to occur when it's
deployed to the cloud.
But locally this um warning message
can be helpful for us because then we
know we haven't created thev file and we
need to do that and this is the reason
why certain code is failing. So at this
point we're checking to see if that.env
env file exists before trying to read
one of the environment variables here.
And this code doesn't change whether
it's running locally or in the cloud.
It's going to read the relevant
environment variable in the same way
regardless of how it is stored. And it
will be stored differently in the cloud
to how it is stored locally in thev file
here. But we need to just check whether
that file exists. And it won't exist in
the cloud. So this it will just log a
warning message
in that particular case. It won't be a
fatal it won't log a fatal error. It's
logging a warning message. Unable to
find
[Music]
file like that.
Okay. Excellent. Okay. So now it's time
to create a front end for our
application and we are going to use
React for this purpose. You can see here
we've uh created a client folder. So our
serverside code is within the server
folder within the root folder, the magic
stream movies root folder. We've got a
server folder and we've got a client
folder. So within the client folder,
we're going to create the react code.
Within the server folder, we've got the
Go Jonic MongoDB related code. Here we
are going to create a React project. So
we are going to use a technology called
Vit for creating our React project. Vit
has an advantage over traditional build
tools like create react app and bundlers
like Webpack. For example, it offers
faster dev startup and rebuild time,
simpler configuration, and out ofthe-box
simpler defaults. So, we're now going to
use VIT to create our React project. To
use Vit, you must have NodeJS version
14.8
plus, 16 plus or newer. We also need
npm, which comes with Node.js JS and is
used for installing dependencies.
So if you don't have NodeJS installed,
navigate to NodeJS.org
and you can download and install the
latest version of Node from here.
Excellent. And you can check what
version of of Node you've got installed
simply by typing node dash V like this.
And you can see version 2401.
That's my version of node. And if you
want to check what version of npm you've
got, type npm slash sorry -v like that.
Excellent.
So once you've got that set up, i.e. uh
an appropriate version of node and an
appropriate version of npm, we're ready
to go. So firstly, we need to be in the
client folder. So let's type cd client
because we're going to be creating our
client code within the client folder and
we're about to create a react uh project
using vit. So to create the react
project using vit we then type n pm
create like this
vit and then at
latest like that.
Okay. And then to proceed, we press Y.
So it's telling me that I need V 7.1.0.
Need to install the following packages.
So I'm going to
say Y here.
So we need to give the project a name.
I'm going to call my project magic
stream
client like this. Press the enter key.
Excellent.
And then you can use your down arrows
and you want to select React here. Press
the enter key and then select JavaScript
from this menu here.
And now it's instructing us to type the
command CD to navigate within our Magic
Stream project, the root of our React
project. And then we want to type npm
install which will install all the
necessary dependencies to run a react
application. And then we can test our
project by typing npm rundev. So let's
follow those instructions.
So let's go into magic stream client
stream client like that. Okay. Okay. And
then we want to install all the relevant
packages within the magic-stream-client
folder. So to do that we can use npm. So
type npm
install. Press the enter key
and it will install all the necessary
dependencies that we need
in order to run our React project.
As always, we just need to be patient
while it installs those dependencies
on our local machines.
Excellent. That wasn't too painful. And
that's all done. Okay. Found zero
vulnerabilities. That's good. Let's
clear the screen. And now we can we
should be able to run that.
So you can see here it's created our
folder, Magic Stream Client. It's
created the node modules folder which
has all our React dependencies.
Great. So we've created a basic
infrastructure
for our React application using Vit.
And there's a lot of advantages to using
Vit over the traditional create react
app facility. Right. Okay. So we've now
got the infrastructure for our React
project set up. So vit has set up a
basic project infrastructure. So there
should be some very basic functionality
included in that and we can test this by
typing npm rundev like this.
Okay, brilliant. So it's listening at
port 5173 on localhost. So let's copy
that
URL. Let's go into our favorite browser.
In my case that's Chrome. And let's test
that the infrastructure has indeed been
created for our React project.
Excellent. Look at that. And there we
go. We can test the interactive
functionality by pressing this count
button. And it counts within our
browser. As we click the button, it
creates a it increments the value within
this button. We can see that our user
interactive functionality is in place
and we can start developing our client
application now. That's brilliant. So
make sure you get to this point before
you start developing the client side for
our magic stream application. Excellent.
So let's go out of that and let's go
back
here and we can press Ctrl C to cancel
out of this. So it won't be listening on
this port on our local host. So, let's
press Ctrl + C and let's clear the
screen. We're going to get started with
an easy win and we're going to start
with a functionality that just displays
kind of a gallery of movie posters on
the front end. So to do that, we're
firstly going to create a folder within
here which will house all our
components. So, new folder and I'm going
to call this components. So, we're
within this src directory which was
created by VIT by default. We're
creating a components folder here. So we
got our components folder and we're
going to create our first component
within a folder named movie. So our
first component will be a reusable
component that will represent one movie.
So it'll be represent basically a front
end a card which will contain the poster
of the relevant movies. So we're going
to create one movie, one movie component
that can be reused for all of the movies
throughout the application. So that's
what we're going to start with. Okay. So
let's create our movie component. And
we're going to create that in the form
of a JSX file. So let's select the movie
folder here. Press this icon for new
file and create a file called movie.jsx.
So this is a JSX file. This should have
a capital letter. So let's
stick to conventions here. And let's
make sure that the movie file name has a
capital M here. Okay, great. So we're
going to be using Bootstrap as our CSS
framework for styling and layout
purposes. And an easy way to utilize or
leverage Bootstrap is through a package
called React Bootstrap. So we want to
install React Bootstrap. So, we must
make sure that we're in the
magic-stream-client
folder. So, to install React Bootstrap,
we type npm
and then I or install. Let's type
install and then react
Bootstrap
like this.
Excellent. And we've got React Bootstrap
installed. So, let's start building our
movie.jsx jsx component, our movie
component within the movie.jsx file.
Okay, great. So, I'm going to start by
creating the infrastructure for the
movie component. So, just const and then
movie with a capital letter like this
and then equals to and then this is
where we'll pass in our props. And we're
going to
pass in a movie prop to this particular
component. And then let's implement
the structure for our component here. So
this is the the basic structure for our
movie component done here. And then we
can return
the relevant JSX code to the calling
code like this. And then below here we
want to export
export the component as default
so that the calling client can easily
import this component and we'll look at
how to do that in just a bit.
Great. And now we simply write the
code
the JSX part of the code for the movie
component.
Firstly, we need to import a few things.
So in fact at at present we just need to
import one package and that's the button
package
from React Bootstrap. And we can do that
with this code here. So, React Bootstrap
slash button like this.
But there's a few other little steps
that we need to carry out in order to be
able to leverage Bootstrap the way we
want to within this component and other
components. So, so we need to go to the
main.jsx file here.
There it is. Main.jsx file. and include
this line of code import
within quotation marks bootstrap
slashdist/css
slash bootstrap
domins
like that and this will enable us to
leverage Bootstrap some of the Bootstrap
classes from within the components of
our application. So let's go back to our
movie.jsx file and implement the code
for this. So we first want to create a
div.
And I don't want to focus too much on
the styling code because it just take
too long to explain every class that I'm
including here for
styling purposes or layout purposes.
So, I would urge you to look up these
classes on the Bootstrap 5 website or
the React Bootstrap website if you want
to know more about the details of these
classes.
Okay. And it's kicking up a fuss because
I've included a dash here. So, we need
to include an equals. Oops. An equals
there. Great. That looks a lot better.
Okay. And let's proceed.
So this movie, by the way, this is what
this movie prop will be the object that
contains all the data that we'll
retrieve from the server. And we'll do
that a bit later. So just take it for
granted that this movie data has been
returned from the server and we're now
able to leverage its various fields
within our movie component. Okay. So
let's create another let's create a
nested div here.
Class name equals 2. And then let's
include our class names here. Card. And
these are all our Bootstrap
class names that are used for
styling
and also for layout purposes within our
movie
within our movie component here. Okay.
Okay. Let's include another nested div
here.
Okay.
So let's go class name like this
equals to
and then card body. These are bootstrap.
These are standard Bootstrap classes
that we're using to style flex deflex
and we're using flex grid. So we're
leveraging the flex grid technology
through Bootstrap here for our card.
And within our card, we're displaying
all the relevant information about the
movie.
So H5, let's include the H5 element
here.
Class name
equals to card title. So this is where
we're going to include the card title.
And then this is how we can display
variables within and mesh those
variables within the HTML code. And this
is just JSX. So it's not actually HTML
code, but it represents HTML code if you
like. And then we can wrap any variable
data that we want to display on the
front end within curly braces like this.
So movie dot title like this. So we're
displaying the movie title within the
card within this H5 element here.
Okay. Okay. And below that, let's
include a P tag like this. Type class
name
equals to and then let's include an
appropriate
Bootstrap class. So this is just for
styling purposes. You'll see the
significance of this styling when we run
the code once we've finished some basic
code here to display the movies on the
front end. Okay. So we got card text and
MB2. That's just margin bottom two.
That's what that represents. And within
curly braces,
let's include moo v dot. And we're going
to include the IMDb
ID within this element here. So, we got
the title and the IMDb ID displayed so
far. And this is within the body of the
card that will contain all the movie
data.
Okay.
Below this, let's include the ranking
value. This is the sentiment that we
discussed earlier when we were creating
the server side code. So if ranking
ranking name So if ranking name is
truthy,
let's include this code here
to display the ranking. So let's include
a span element like this. Okay. And a
class name
class name equals to and this is just
more
Bootstrap classes. So we're using the
badge class and this is just for styling
purposes. Makes us makes it easy for us
to style our code without having to go
into the details of the actual CSS. And
let's include this with a font size.
This is just our own custom CSS
so that we can override whatever font
size is there at the moment. And we'll
include
and this needs to be within further
curly braces. So we need to wrap this in
two curly braces because we're including
an object.
So font size and then let's include the
font size which is just one
rim here. Okay. Okay. And then within
curly braces, let's include the ranking
name, which is just the sentiment of the
movie. You know, excellent, good, okay,
bad, or terrible
ranking
name like that. Okay, brilliant. So, we
slowly building up our
movie component. And then up here,
we want to actually include the poster
image. Now, this is obviously the most
important part of the display for the
movie, the movie data, the poster image,
because without the poster image, you're
just going to have a boring title and
the IMDb value, which isn't particularly
visually appealing. So, let's include
the poster image here. So, to do that,
I'm going to create a div here. Okay,
div.
And I'm actually going to include my own
custom inline style here.
So style equals and then open curly
braces. And then let's include an object
position
uh relative here. So this is just inline
styling here. And this is how you do
that. You create an object to represent
your inline style within the JSX element
like this. So position relative. And
you'll see the outcome when we test the
the code in just a bit. But for now,
let's just create the component. So we
want to include the poster image here.
And then for the source, we want to
include a variable here.
So we don't want those in quotes. So
it's src equals to movie dot poster
path. And that's the poster path that we
want displayed for the movie on the
front end.
Okay, brilliant. And then for the alt,
we'll just include the title like this.
Movie dot title
like that.
And let's include a
class name
like this. So
card
image
top like that
and then our own custom styling here. So
let's include our CSS code here. So
object
fit contain like this. And I'm assuming
you've got some knowledge of CSS. It
would take too long to explain
all of these properties, these CSS
properties 50. But please, by all means,
look them up. Very important if you're a
web developer to understand at least
basic CSS
percent, right? Okay. So,
I think that's okay for now. for our
component. It probably isn't perfect,
but hopefully it'll display all the
relevant data on the front end. Okay, so
within the components folder here, let's
create a new folder called
movies. So this the folders should be
just to stick to convention, the folders
should have a small letter in front of
the folder name like this at the
beginning of the folder name. So the
first character should not be
capitalized.
So we got movies and then within movies
let's create a file called movies.jsx
like that. So let's create the code for
our movies component. So firstly we want
to import
the movie component and we can do that
like this from
and we go dot dot because we're going
back a directory and then you can see in
sense has suggested the folder. So movie
and then the component is movie like
that. Okay.
Okay. Movie is declared but it's not
used. Okay. So let's create the
infrastructure for our component. So
const movies
equals to and then we pass our props
through like this and we want movies. So
that will be a collection of movie data
that we've that we will in a bit
retrieve from the server. So we'll
contact the movies endpoint and bring
back the data. And then this data is
passed into our component by the calling
client which we'll create in just a bit.
So this one we want to include a movies
prop and a message prop here. And then
let's
create this as an arrow function like
this. And let's return
the appropriate JSX for our movies
component. And once again, I'm not going
to go into detail about all the
the various Bootstrap classes that I'm
using here. So class name
equals to container. So this is just a
bootstrap class and margin top four.
This is just wraps or abstracts away the
CSS code for the margin top setting.
Let's create another div here.
And I'm just going to set the class name
to the class named row. And this is for
layout purposes. So we're using the
Bootstrap grid system
for layout purposes here. And let's open
curly braces like this. movies. We want
to check that movies contains a value
or is truthy and
movies.length. So there is indeed data
within the movies collection being
passed as a prop to this component. We
can do that with this code.
Movies.length
is greater than zero like this. So if
this condition is true go question mark
and we can handle the true condition
like this. If the condition returns true
we want to use movies the collection the
array dom map. So we're using the
JavaScript map function to loop through
the movie data within our movies
collection. And this is how we can do
that. So we go movie like this and we
use an arrow function
to
establish our logic.
So,
so if movies is truly and the movies
collection contains movie data, we're
looping through the movies collection
and we are going to
display our movie which we've just
created within the movie component and
we need to include the key attribute
here and set it to a unique value and we
can do that through this code. We can
set the unique value to underscore the
underscore ID property within our movie
data. Bear in mind, of course, this data
is coming from the MongoDB database. And
we've got this ID underscore ID value
established within the movies collection
to uniquely identify each movie. And
we're using this as our key
for our movie element here. So this
helps react identify each unique
element. Okay. And then we go movie. We
want to pass down the prop movie to the
movie component. So we go movie equals
to within curly braces. Then the movie
data gets passed down
as a prop to the movie
element like this.
Okay. We can just
Close
this element like that. Get rid of that.
Excellent.
Okay. And we have a little issue here.
Let's just check.
We'll check that out in a bit. Let's
just finish off our if else condition
here. So, if that's true, we want to
display a movie. We're looping through
all of the movies. So if the movies
collection contains data, we're looping
through all of the movies within the
collection and displaying the movie on
the front end appropriately there. Else
here, so the colon signifies the else
condition. And then we can just include
the message that will be passed down to
this component as a prop within this H2
element like this.
Okay, H2. and then within curly braces
message like that. Okay, let's just take
care of this issue here. So, let's look
at what's going on here. So, we've got
our movie component. We're exporting it
as default here.
Movie doesn't look like there's anything
wrong there. We've got it components
movie.jsx
and then within movies.
Okay, I'm just going to write the export
functionality here. So, we want to
export this as default.
Export default
movies like that. Okay, brilliant. And
now this is obviously worrying me here.
Uh, I know what actually is going on
here. It's because it's actually painful
when you do this in React and it
complains. It's because I initially
um created this file with a with a small
M at the beginning of the word movie.
And I think
the this is why it's flagging this as
erroneous because then when I changed
it, it's not picking up the change for
some reason. When I changed it and
capitalized this M, it's not picking up
the change. So, this is a bit painful.
But to get around that,
um, I'm just going to
cut that cut that code, put it in my
clipboard. I'm going to delete this
file. I know this is a bit of a pain.
Move to recycle bin.
And I'm going to create the file again.
New file,
this time with movie.jsx.
Like that. And I'm going to paste that
code that I just cut into this new file.
and save that. And let's see if that's
that has resolved it. Yeah. So, for some
reason, I get an issue when I first when
when I change a file name, it doesn't
seem to be to propagate through. And if
you initially didn't get the file name
right, it can complain when you're
trying to import a particular JSX
component within another component. And
that's what was happening there. So,
that's a bit painful, but that's
resolved now. Okay. So, uh, and then we
want this displayed on the homepage. So,
I'm going to create a component called
home. We want the movies displayed on
the homepage. So, I'm creating a folder
called home. And
let's create a folder called home like
this with the
name of the folder.
The first character of the name of the
folder will be in lowerase here. So,
we're sticking to that convention. And
then, let's not make the same mistake.
The actual JSX file will now have a
capital letter for the first character
of its name. So, home.jsx.
And we're now going to create the code
for the home.jsx
component or file. Right? So, screen
there. Right? So, let's uh do that. So
now within this component, we're
actually going to be calling the
serverside code.
So before we start writing the actual
code for the home.jsx file, I'm going to
create a folder within src
called API.
And this will house
the code for connecting to the server.
Just the basic code for that. I'm going
to create a new file called aios
config. We're going to use the package
the axios package the axios dependency
to handle the infrastructure for calling
the server side code. So we actually
need to install Axios first before we
configure of of course before we
configure Axios. Yeah. To install Axios
we simply type npm here at our terminal
prompt. npm install aios like this.
Brilliant. So we've got Axios installed
and we can now
write the just the infrastructural code,
the configuration code for
using Axios to call
our server side endpoints. So let's go
Axios import Axios from
oops Axios like this.
Okay. And we want to read in a base
address. So it's going to be hosted on
local host initially, but we want to be
able to configure the base address for
where our serverside code is running so
that we can easily change this value
when we eventually deploy our code. So
let's create an environment
file aenv file
on the same level as this src directory.
So we want to actually create that
within magic stream client here. So
let's create our env file like this env.
And let's configure an environment
variable.
And you need to adhere to a naming
convention
when you have created your react
application using vit. So to do that we
have to include vit like this and then
our
the name of our environment variable. So
I'm going to call this API base
URL. Okay. And we're going to initially
be running
our server side code at local
host colon port 8080
like that. So we've configured our
environment variable. Let's go back to
Axio's config here and let's read in
that environment variable. So API URL
URL like this equals to import and this
is how we read an environment variable
using VIT because we're using VIT. We've
used VIT to create our project. So we
have to adhere to certain VIT
conventions.
API
underscore base
URL like that.
Okay, we can include colons here. Okay,
so we're reading in the
base address for our server side
endpoint
here. And then let's configure Axios. So
export
default Axios
dotcreate.
Let's open the brackets there and then
let's configure Axio. So the base URL
property must be set like this base URL
API URL and that's of course the
the value that we've read in from thev
file. So it's the environment variable,
and then within the header, we want to
include
this configuration here, headers.
And this is an object
JSON object content
dash type
colon and then
application
for/json like that.
We didn't close this quotation here and
that's why it's complaining. Okay,
brilliant. So we've got Axios configured
now.
Great. So now let's go back to the home
component. So firstly I want to import
these commonly used hooks for React. So
use state,
use effect
from React. Whoops.
Okay.
Then I'm going to import the Axios
client.
So we're importing the component that we
created it just earlier from the
appropriate directory
API. So it's in the API directory and
then it's picked up that it's Axios
config.
So we're importing Axios config
component here.
like that.
Great. So, we also want to import
the movies component
from this directory.
Movies whoops
dot movies with a capital letter. And
let's create the infrastructure for our
components. A const
home. And we do that in the form of an
arrow function.
Okay. And then we include the arrow.
Open curly braces.
Okay. And we want to
create state for a movies variable. So
state variable. And then along with the
state variable, we've got the set movies
function which sets or changes the state
of the movies state variable.
And we go equal to use state and we pass
in an empty array by default. So no
movies yet. But we want to populate this
movies state variable
with an array of movies, a collection of
movies which we will retrieve from the
appropriate endpoint on the server.
Okay. Okay. And then we want to create a
loading
state
variable set loading like this equals to
use state and then by default we want
this to be false. So it's a boolean
state variable and then a message if in
case an error occurs we need to display
a message to the screen. set message
equals to use state. And now we're going
to use the use effect hook.
Use effect hook. So that when this
component loads,
we call
the movies endpoint
on the server
and populate.
populate the movies collection with the
appropriate movies data that we
retrieved from the server.
Okay, let's go const
[Music]
like this
equals to we want this to be called
asynchronously. So we use the async
keyword here and we include another
arrow function like this open curly
braces and within this we create our
logic for calling the movies endpoint.
So and we're going to use axios to call
the movies endpoint. So we set loading
equals to true because we're about to
call the serverside code and we want to
display a loading indicator on the front
end while the serverside code is
retrieving the relevant data and
returning it to us. Uh let's set this
message to nothing here. So we're
initializing the message to an empty
string here. Let's include a try catch
block here so that we can handle
errors if they occur.
Okay,
there's that. And a finally block here
which gets called
whether an error occurs or not. And here
we can use this finally block to set the
loading
boolean value to false because we want
the loading indicator
to no longer be displayed
whether an error occurs or not. So we
can include this code within the
finally block and set the loading state
variable to false here. So we've set it
to true up here and we're about now. And
now within the try block, we're going to
call
the relevant endpoint to get all the
movies data. So we're using Axios client
and we go get
and this is how we call our endpoint on
the server just for/mov which is our
endpoint that will return the relevant
movies collection to our client React
code. And then let's set the movies
state variable using the set movies
function to response. data which should
contain the relevant array of movies.
Okay. And then if
response do data. So if an empty array
of data is so no movies are found on the
server
equals to zero
then let's set the message to set the
message of the message state variable to
an appropriate message. So there are
currently no movies
available. So if the say the database is
set up but the movies haven't been
imported or no movies have been added to
the relevant collection in the MongoDB
database, this is the an appropriate
message to display on the client. Okay.
And let's handle the error. We're just
going to console log the error here like
this.
Okay.
Error fetching movies will be good
enough I think. So error oops error
fetching movies
go like this and then
comma
error like that.
and then set the message to set the
message
state variable to an appropriate error
message. And this is the same
basic error message, just error fetching
movies. We've included a bit more detail
in the console window. if an if this if
an error occurs at this point if an
error occurs when we're trying to use
Axios to fetch the movies from the
server. Okay. And that is our use effect
hook written. So this hooks into a React
life cycle. So when the component loads
we're sending off a call, an
asynchronous call using Axios and
hopefully the endpoint returns us valid
movie data here. And we're setting that
state variable
m movies here
appropriately. We need to pass an empty
array like this to the use effect hook.
So that means that when this when this
when the component loads this code fires
and populates hopefully populates the
movies collection with the movies data
so that we can now return
JSX to
to the calling client. Okay. So let's
include
empty
tags here. And then if the loading state
variable is true,
we want to just display a loading
message. And we can just do that by
returning say H2.
We'll include a proper spinner later on
as a loading indicator, but
for now, let's just include the text
loading dot dot dot and then else
condition fires. If the else condition
fires
here,
we actually want to display
a gallery of movie posters if you like
the movie data. movies equals to and
then the retrieved movies within the
state variable. The movie state variable
should be retrieved
and then if there's a message also
passed down the message state variable
as a prop
to the movies
component. We have a few problems here.
Okay, what's going on? Okay, we need
opening bracket here. We need an opening
bracket here
and a closing round bracket here to wrap
the else part of the if statement.
Okay, so I'm not currently closing this
movies element and that's why we're
getting all these red squiggies. So use
a closing tag there. And now our code
looks good. So
within this fragment here which just
means that uh this is a substitute for
an element but we don't want a
particular element here like a div or p
element for example. So we can this is a
way that we can include a parent element
without including a particular HTML
element for the parent element. So we're
including a react fragment here. So just
we can do that by implementing empty
tags like this to house JSX elements
here. So if so while the data is being
loaded while the data is being retrieved
from the server this loading indicator
should display or else we want the
movies displayed
to the user. Okay. So now we've got all
these components set up. It's still
complaining. I don't know why it's
giving us the same problem as we had
before. But I I think I seem to remember
that I got it right with this one. Okay.
So, this has got a small letter. This
should have a capital letter here. So,
I'm probably going to have to do the
same thing. I'm going to copy all of
this code and cut it and delete this
file here.
Sorry about this. And then create the
movies JSX file
with a capital letter at the beginning.
JSX
and then just paste that in.
Shouldn't really have to do that, but
So,
do the same thing here just to fix this
ridiculous problem. Delete
that there. Paste that in there. Okay.
And that should fix everything.
So I don't know why that seems to cause
a lot of problems if you initially get
the
file naming convention incorrect and you
want to change subsequently change it.
It seems to cause havoc. It seems to
flag a lot of associated errors. So it's
best to get this correct from the
beginning I guess. But um to get around
it, you can just copy the code to your
clipboard, delete the relevant file and
create a new file and make sure that the
first letter is capitalized. So okay,
great. So
that's done. So I think the next step I
think we can now that our code looks
good, we can go to the app.jsx
code here. And one thing I actually want
to do is just get rid of all of this
here.
Okay, I'm going to just get rid of the
the app.css code so it doesn't interfere
with our CSS code. So app.css and the
other one is index.css. I'm just going
to delete all of that. And then we can
go to the app.jsx file here. And we want
to replace all of this code here. now
with just a call to our home component.
Okay, we don't need all this here. And
I'm going to import the home component.
So, I must make sure I'm exporting it
first. So, oops.
So type export
default
default home like that. Okay. So export
default home. So firstly we want to
import
the home component
within the app
component. So import home
like this from
comp
from then within quotations
dot slash components
slashhome
slashhome
like that. And we're importing the home
component. And then we can just
include
the home element here
within our code for now
just to test whether we're able to
call the movies endpoint retrieve the
data and then display the relevant movie
gallery if you like to on the front end.
Okay. So we want the server side code
now running because we're going to
contact the endpoints running
on our local machines. So it's going to
be localhost colon 8080. But in order to
run our code now you can see here we're
dealing with running our client
application. These are two disparate
entities on the on the web now. So
we need to be able to run both of these
projects,
both our client and serverside code
simultaneously. So I'm going to create
another terminal window to run the
server side code. So let's go into
server
and then
magic
stream movie server like this. Oops, got
that wrong. CD server.
It's magic stream movies. That's why
that went wrong.
Movies
server like that. Okay, let's clear the
screen and we can run our code. Let's
go. Go run dot like that.
So let's see what happens here when we
go to our client code and run it. So
let's run. So, npm
rundev like this to run our client code.
Okay, so we're missing something here.
Okay, so when we try and run our React
code,
we're getting this issue here. Failed to
resolve import B bootstrap disc CSS
bootstrap and we we installed React
Bootstrap, but we didn't install
Bootstrap. We have to install Bootstrap
in order for this to work. So this will
mean that we can include the you know
the react bootstrap allows us to include
various elements within our code various
React Bootstrap elements like button and
things like that. But in order to use
the actual Bootstrap classes and for
this importation code to work we
actually need to install Bootstrap
separately from React Bootstrap and
that's why we're getting issues here.
So, I'm just going to press Ctrl C.
And to install Bootstrap, we just type
npm
install bootstrap.
And that should resolve our issue.
Excellent. Let's clear the screen. Let's
try run our code again. npm run
dev.
Okay, looks good. And let's launch our
client code.
Okay, so that hasn't given us the same
error, but I think there is another
problem and I'll explain what that
problem is now. We should, if I'm not
mistaken, get a cause error.
Oh, okay. So, let's run our code. mpm
rundev, our client code here.
We've still got our server side code
running here at this point, 8080.
And let's copy this URL for our client
React code. And let's paste that
here. Press enter. And nothing's
happening. And let's see if there's any
issues.
And there's no issues apparently. So
what could be going on?
Okay. So I strongly suspect that I'm not
calling this fetch movies function. we
still have to call this function. We've
created it, but we're not calling it
anywhere. So that would be the problem
there. So all we do to fix the problem
is below the function. So within the use
effect hook, we call fetch movies like
this. And so now when the home component
loads,
this fetch movies function should be
called. So let's error fetching movies.
Great. So that's better.
Okay.
Okay. So,
let's look and see what errors have been
printed to the console. And it's an Axio
error. And here's what I wanted to see.
So, this is actually an expected error.
What's happening here is we're getting
blocked by cause a cause policy which is
by default active within your browser.
And so our client
on port 5173
is being blocked on the server here so
that we we're not able to access this
movies endpoint by default. First of
all, what is cause? It stands for cross
origin resource sharing. This is a
default security precaution built into
browsers that serves to prevent
malicious websites from accessing
sensitive data from another domain. And
you see we're running the React client
at a completely different disparit
location to where we're running the
server. So we're running the client on
port 5173 on our local machines and
we're running the server side code on
port 8080. So this is considered two
disparit domains. And then cause kicks
in and stops our client from accessing
the endpoints on our server. So cause
stands for cross origin resource
sharing. This is a default precaution
built into browsers that serves to
prevent malicious websites from
accessing sensitive data from another
domain. So basically the client and
server code are running on different
domains. So our browser is stepping in
and not letting our client communicate
with the servers, not letting us call
the endpoints on the server from the
client. So to resolve that, we need to
include appropriate code
on the server. Uh, so I'm just going to
stop the client from running by pressing
Ctrl C. Cancel out of that. And we can
stop the server from running too. So
let's go to this terminal and do the
same. Press Ctrl + C. Clear the screen
here. And then let's go to our main
method in the go code. So we're
returning to our go code, our server
side code. And we need to write
code to prevent cause. And we can do
that by explicitly including code to
prevent cause within our serverside code
within the main function the entry point
function of our serverside code. So
firstly we need to include a package
to deal with this cause related issue.
So let's use the goget command to
install the appropriate cause package so
that we can leverage the cause
functionality to prevent cause from
interfering with our client
communicating with our server. Okay. So
to install the relevant cause package to
help us prevent cause from interfering
with our client communicating with the
server, we need to install this package.
So we can use the goget command for
that. So go get and then github.com
[Music]
sljin
dash
contrib like this and then slashcores
press the enter key.
Okay, it's downloading the cause package
for us. Excellent. So that seems to have
been successful and then we can include
the relevant
importation code at the top of our main
file here. So within our main package
here we include this package importation
code to import the relevant cause
package so that we can handle cause
appropriately.
Okay.
Okay. So now we can create a variable
called config and we can configure our
cause related code appropriately cause
config like this
and then let's configure cause
appropriately. So configow
I'm going to say allow all origins and
I'm going to set that to true. Now note
that we will have to change this code
before we deploy it because allow all
origins
will not work if we're using HTTPS. So
to secure our full stack application, we
are going to use HTTPS when we deploy
this code to the production environment.
So we won't be able to use allow all
origins equals to true like this boolean
field like that. But for now, let's
include that for testing purposes. We
can we can do that for now.
Okay. Allow all origins and then config
dot allow methods. So we're allowing the
following HTTP methods with this line of
code here
string. And let's include
get
post.
And we're actually just using get post
and
hatch within our code. So I'm just going
to include these methods here. Um this
should be within curly brackets and not
round brackets
like that.
Okay. And then config dot allow
headers like this equals to and it's
another string array
and let's include
origin
content dash type
and
authorization
like that.
allow these headers.
Okay. And then config dot
expose headers
equals to string array.
So these have to be curly braces.
on
content hyphen length
like that.
Okay.
Okay. And then let's configure router
use
and let's configure our cause settings
dot new
and then
config like this.
Okay, this is a bit of a pain to have to
set all this up to bypass cause, but
okay.
So, one more uh configuration settings.
So, config dot whoops dot
max age equals to 12 times time do hour
like that. Okay. And you must make sure
that you've got time package, the time
package imported here so that it can be
used down here appropriately. And the
other thing that I haven't done quite
correctly is these this code here
must be above where we're setting our
roots
here.
So this code must be called before the
root configuration code that we created
earlier.
So that cause will be handled
effectively. Okay, great. So now let's
go to our server side. Let's go to the
terminal prompt for to run our server
code. So go run dot.
Excellent. So it's listening on port
8080.
So let's run our client react code. And
to do that, we go npm rundev like that.
Okay. And we can now copy this URL to
our browsers
to test our code.
Excellent. So, as you can see, we've got
round we've got around the cause issue
and our movies are being displayed in
kind of a gallery, a grid
within from within our React code.
Excellent. So, that is a great start for
the creation of our React client code.
Okay. So, we've now created the
functionality for displaying all the
movies. available on our Magic Stream
website to users which are and these
movies are publicly accessible to
everyone. But in order to stream them,
you must log on and
authenticate before streaming the
relevant movies. So the movies are being
displayed on the home component. We're
drilling down to the movies component
here. And then each movie is displayed.
Each movie is being displayed here
within a loop through the movie
component here.
And this is the movie component here,
which is basically just a card which
establishes the layout for each of the
relevant movies. And we've got Bootstrap
here for styling the relevant cards
displayed in like a card deck if you
like or like a gallery with the poster
images displayed in a gallery for each
movie. Before we create the front end
user registration and login
functionality, we need to add links to
the front end so the user can navigate
between the main pages. We're going to
establish the relevant links within our
header component. So the header will
reside at the top of the website and
will contain all the navigation to the
main pages, login, registration, home
etc. We are going to leverage the react
routter DOM package to manage our client
roots because the linking to the
relevant pages is all um part of the
rooting system. So each page has to be
configured within our rooting system and
what's governing that rooting system if
you like is react rout. So this is a
package we need to install react routed
DOM. So to do that let's go into the
client
forward slash
client magic stream
client like this.
And then within this
directory we can install
react rout. And to do that, we type npm
install react
router DOM like this and press the enter
key.
Excellent. So we've now installed React
router DOM and we can make use of that
within our header component which we are
about to create. So let's do that. Let's
create the header component. So within
the components folder, we simply create
a folder called header. Let's make sure
we stick to protocol and the folder has
a lowercase H at the beginning of the
word header like this. And then let's
not make the same mistake as we made
earlier with the movie and movies
component. And let's make sure that the
JSX file first character in the name is
in uppercase. So we call this header
with H and uppercase JSX like this. JSX.
and we're going to create our component
within this JSX file. So let's start at
the top and let's import
the button from
React
router. Sorry, React Bootstrap.
So React Bootstrap slash button. So we
can make use of the button element
imported from React Bootstrap. If you
want to learn more about React
Bootstrap, please navigate to their
website within your browser. the React
Bootstrap
website where you can get more details
about the relevant uh components
available to you through React
Bootstrap. Okay, let's import
container. So, we're importing the
container
element from React Bootstrap. React
Bootstrap and then forward slash
container
like this. Okay. Whoa, I haven't yet
mastered the art of typing and talking
at the same time. I am improving, but uh
just bear with me. Okay, so then import
Whoops. See, look at me. Import nav like
this from
And we're going to import the nav
element like this. React
dash bootstrap
nav. And you'll see these elements will
be placed in their relevant context as
we develop the code for this component.
So
okay, so navbar and we want navbar
import
nav bar.
So all of our navigation is going to
reside like for example buttons that
link to the register page or the login
page are going to reside within the
navbar situated at the top of our front
end.
Okay. So react
bootstrap slashnavbar like this.
Okay.
And let's now import the relevant
components
from
react router DOM. And this is these
components are extremely important
because this is how we can establish a
centralized routting system
for our gosh for our front end.
Navigate. So use navigate hook. We want
to use navigate hook. nav link like this
and link and you'll see how we use these
in our code in just a bit from and react
router
[Music]
dash dom like that. Brilliant.
Okay, let's create the
infrastructure for our component. So
const
header equals to we won't pass any props
in at the moment but a prop for logging
out of the application will be passed in
at a later stage when we get to that
functionality a little bit later. So
const navigate
like this equals to and then I'll use
navigate hook like that.
Okay. And then let's return
I think. Whoops. That doesn't look quite
right. Something's not quite right. It's
because I haven't made that an arrow
function. And that's what all the fuss
was about there. And let's go return.
Okay. And then within round brackets, we
can include our JSX code. So let's start
with a navbar which we've imported from
React Bootstrap here. So let's include a
navbar element like this. And just bear
with me while I
include
certain attributes here. And I'm not
going to explain all the attributes, but
you can, as I said, go to the Bootstrap
5 or Boot or React Bootstrap websites
and you can read about and you can glean
whatever details you wish from the
content published on on those websites.
Okay LG.
Okay, like this. Uh this should let's
just close that nav bar.
What's the enter key? So expand equals
large. Okay. Then class name
equals to
shadow SM
like that. Okay. And then within the
navbar let's include the container
element. I'm just going to create the
tags and go navbarbrand.
But ordinarily,
Visual Studio helps us out and creates
the actual tags for us when we press the
tab key after entering the name of the
element.
Okay. And for now, for the brand, I
actually am going to include a brand.
I've created a an icon that we can use
for Magic Stream brand. But for now, I'm
just going to include Magic Stream here
like this. This is the name of our
fictitious company. So, for now, we'll
just include that for our branding.
We'll get back to the the actual
branding code in a bit. Okay, let's
include our tags first. This is one with
a dot in it also. So,
bar
dot
toggle
and the significance of the navbar.
Okay. And this is a we can close the
navbar. We can close the navbar toggle
element like this.
Okay.
Get area controls
equals to
main
navbar
nav like this. And these are just
Bootstrap 5 classes that we're using
here.
And the significance of the nav toggle
is that on smaller screens the menu you
can close and open the
the menu bar using a particular toggle
which is useful for smaller screens. So
on a desktop computer for example the
browser being in full screen uh you
won't need this toggle but it will
appear automatically on smaller screens
so that the menu bar can be opened and
closed um as required.
So it's for responsiveness and that's
basically one of the advantages to using
Bootstrap in general is for screen
responsiveness. So that's why we're
using this nav toggle this navbar toggle
element here. And let's create the
actual collapse functionality. So this
is what we want to include in the
collapse of the menu. the items, the
navigation bar items that we want to
include.
When the menu bar is expanded and
collapsed. So when the menu bar is
expanded, we want the following
items included within this. So if you
want to
create the tags and the element at the
same time within VS Code can make your
coding a little bit more efficient. Just
type nav that's the type of the element
and press tab like that and it'll create
the opening and closing tags for you.
Okay. And then let's include class name
like this
equals to me
auto.
Okay. So within this nav element, this
parent element, we want to include our
our link, our home link. So let's create
our tags. Then nav.link like this. And
let's close that off. nav.link.
So we're including a link within the nav
element. nav.link element. We go as
equals. We want this to be part of the
React router DOM functionality. So we
include the nav link element here. And
this is what we've imported here from
React Route DOM. And this is how we're
establishing this as a link. So
Bootstrap knows that this is a link from
React Route to DOM and will manage this
linking functionality for us. Okay. And
then we include the to attribute and
this is where we want
this link to link to. So the page will
be just forward slash which is the
homepage and then within here we're just
going to include
the label for the link which is just
home. Okay. So this is the homepage for
slash the root of our navigation.
So when the user clicks on the home link
it of course takes the user to the
homepage. Okay, excellent. This is when
the user logs on and the user wants to
see the user's recommended movies. And
we'll create the functionality for this
component a little bit later.
So recommended is the name of the page
that we're going to create name of the
root if you like that we'll create in a
little bit later. And let's label this
as recommended
like that. Okay, brilliant.
Um,
and I'm just going to temporarily
create a state variable here. So, we
haven't yet imported use state. So, we
need to do that. So, let's import use
state like this
from React.
Okay. And I'm just going to create a
state variable. So for now I'm just
going to create a boolean state variable
for this purpose just for testing
purposes
equals to use state and I'm going to set
this to false ie the user is not yet
logged on. Okay and this is just for
testing.
Okay so we got those links home
recommended which will be displayed to
the user.
Okay regardless of whether the user is
logged on or not. Okay. And let's now
create the
links
within the navbar for the registration
and login buttons and the logout button.
But as I say, depending on what mode
you're in, but depending on whether the
user has been authenticated, different
buttons will show up for the different
scenarios. And let's look at the details
of this now. So let's click, let's type
a nav like that. So we've created our
nav elements. Within this nav element,
we want class name equals to the
following classes. MS
auto
space align.
Whoops.
Align
items
center like this. Okay, great.
And then let's include empty tag. So a
fragment here.
Let's include
a span. So this is for the scenario
where the user's logged in. So I'm just
going to type span
close span.
And we're not doing the login
functionality just yet. So for testing
purposes, I'm just going to go hello
and then within strong whoops within
strong
HTML elements, I'm going to say name
like this. So hello name, but this will
be replaced with the username of the
actual logged in user later on.
Okay. And then below this, we're going
to include the log out button. So
button. So this is when the user is
actually logged in. Let's go variant
equals to
and this is just for styling purposes.
We're including these bootstrap related
classes out line
like this and then size
equals to SM.
Okay. And then we're going to include an
on click, but we'll include that later
because we're not going to include the
authorization functionality till a bit
later. So we'll leave out the on click
function for the buttons for now.
Okay. And let's go log out there. Great.
And then
so for this one if orth
is truth the if our state variable is
truth the then and then open brack open
round brackets like that and we need to
actually put this
bracket at the end
here like that.
Okay. So if orth and then question mark
we want this true when the author is set
to true or is truly we want these
elements to be displayed.
Okay.
And then here we close the round bracket
and we include a colon and we want to
include now what happens when author's
falsy
meaning the user is not logged on.
Okay, we need to remove this round
bracket here. Oops, put another question
mark. Okay, like that. And that looks
much better. Now we need to include the
code here. And this code will be
for displaying the register and login
buttons.
In fact, I'm just going to copy and
paste these in to save time.
And you can see here now we've got the
register button here
and we're navigating to
to the login and we're navigating to the
register form here. Sorry, the register
page here.
Okay, let's just tab that in a bit.
Okay,
so if that means the user's been
authorized, we're displaying the log out
button. And if the user hasn't been
authorized, we're displaying the login
and registr and registration button
here. And we you can see we've got an
onclick
event included within this button here.
So we need to we're going to in order to
navigate to these pages which we haven't
yet created. We need to use the use
navigate hook. So we've got our navigate
variable here which has been retrieved
from the use navigate hook here and
we're using that method here to navigate
to the relevant pages. Okay, we've got
one little issue here. This collapse
element should be closed down here. We
want all of these as part of a
collapsible
navigation bar.
So we must include collapse element
here. We must close it down here. Okay.
And the other thing is the container
element.
also
close the container element
down here. Everything, all of these
navigation
links or buttons must be included
within the container
like this as well as the collapsible
menu bar.
Okay,
great.
Nav should probably be tabbed in a bit.
Okay.
Okay, let's make our code look
beautiful. Okay, there we go. I think
that's pretty good. So, if we go to
app.jsx, JSX. Let's include the header
component here just for testing purposes
for now so that we can see what's going
on. Header from
header. So we're importing that. Oops.
And I don't think I've exported it yet.
So that's something that we can't
forget.
So
let's export header as default from this
component. header component default
header like that. And now we're
importing it within the main parent
component, the app component here. And
then we can just include it above
include the header element
above the home element. And we can see
kind of what's going on now. So for
testing purposes, this should be good.
Um,
and let's open another
terminal window here. Go CD server. We
want to run the server.
Magic
stream movies server. Hopefully that's
correct. It is. And let's run the code.
Go run dot. We're running our server
side code now. And let's run
our
client side code. So, npm rundev like
that.
Okay, let's copy this URL to our
browsers.
Let's see what we got here.
Oops.
Okay, we got a problem.
Failed to resolve import
components. Oh, home. So, let's leave
that up there. Um, okay. Right. So,
we've
not quite got that correct. Of course,
this needs to be header.
Let's save that and see if the error
goes away. So, far. Okay.
New dependencies. Okay, let's just see
what's going on here.
So, nothing's displayed. We have issues
here. See, go to dev tools.
Error caught use may be used only in
React router DOMJS routter component.
Okay, so we can't actually
use that yet. Okay, let's resolve this,
right? Okay, I understand what's going
on there. Okay, so we need to go to the
main.jsx file here and we actually
in order for those for that routting
functionality that we've included in the
header component, we need to wrap our
app component within a parent component
that's taken from the React rout
package. So let's do that. Okay. So
let's import
the following from react rout. So import
curly braces
browser router. We need to include that
there.
And then roots
like this. And then root. We need to
include this code here. So we need to
take that out
there. Include
browser
routter like this.
Within the browser routter parent
component, we include a component or
element called roots like this. And then
within the roots element, we include a
an element called root like that.
and we go okay and we can close this
like that. So let's remove that
and then path equals to
okay so
for slashstar like that and then element
equals to app
like that.
Okay. And I think that is correct now.
Um, see what happens.
There we go. Look at that. Okay.
So, we've got our login. We got our
register recommended. We've got all of
that now in place.
Excellent.
We go to a small screen. You can see
what I was talking about with the
toggle. You can now toggle those open
and closed if you on a smaller screen
like that. And we can neaten up all this
a little bit later. But yeah, so our
routing is pretty much working. I mean,
our header functionality is pretty much
in place. That looks pretty good. Okay,
good.
Okay, so the next thing we want to do is
complete
our roots. So to do that, we're going to
import the necessary
components
from
React roottodom. So root.
Sorry that should be capitalized.
Brute
roots.
and then use nav gate
from react
rout.
Okay. And now we can configure our
roots. So this pretty much stays where
it is. So let's include the roots
element.
Okay. And then our fish root
and this is for the home
component. So path equals to. So this is
a route to the home page. Home component
element
equals to then within curly braces we
can include our home element like this.
Okay. Okay, we need to include them
within this tag like that. Need to
include the root like that. The home
element within the for the element
attribute set to the element attribute
within the root element like this. And
this is how we configure our roots and
react rout.
Okay,
great.
Let's create one for register 2. So path
equals to
register like this. We're going to
create a
component for register in a bit.
Like that there. And let's do the same
for login.
For the login route, let's just make a
duplicate of this route and change the
relevant bits. So
login like that.
login.
Um this should be capitalized login.
So we need to create these components.
So let's do that.
So within the components, let's create a
folder
for register. So red, whoops, this
should be in lower case register like
that.
And then within the register folder,
let's create a file called register.jsx.
register.jsx
[Music]
like this. Enter.
Okay. I'm just going to create just the
basic
structure for the register
component.
We're not going to include any of the
functionality just yet. We just want to
get it navigating correctly to the
register and login pages first off and
then we'll create the functionality for
these two pages. So for now, let's just
include very basic functionality. So
let's return
just H2
uh register
like that. So it's just very basic
functionality to include an equals here
of course. So we need to export the
register component. Export
default
register
like that.
Okay. And now we need to create the
login component. So let's go to
components there. So let's create the
folder for the login component.
So login
then within the login folder, let's
create the login.jsx file with the L
capitalized login.jsx.
Enter.
And I'm just going to go to register
here. I'm going to select all, copy and
paste. Let's paste that within our
login.jsx file and change the relevant
bits. Login, export,
login,
login like that. Okay,
excellent.
I think that's pretty good. So, we've
got the components in place. So now we
need to import them within our
app.js file and create the relevant and
configure their roots. So header let's
go red just like that
reister
range.
Okay, let's duplicate that and do the
same for the login
element. Login component. We're
importing the login component here
like that.
Excellent.
And now we can create Oh, we've created
the roots of course. So, they're already
in place and we should be able to
navigate to those roots now. We should
have very very basic rooting
functionality in place now and we
certainly don't need that anymore. The
counts that was added by default through
vit. We should be able to test that now.
Let's just see what's going on there. So
our serverside code is still running.
Excellent.
So let's run our client code. MPN npm
rundev like that.
and let's copy
the URL.
Paste it here.
Okay, look at that. Looks pretty good.
And let's see if we can navigate to the
login page. Login. Excellent. So that
routting functionality is basically
working. Let's go to register. Login.
Let's go home. So we got home. We got
login. We got register. And now we just
need to create the functionality for the
login and register components. We've
created our header in its most basic
form and now we want to create the login
and register functionality.
Excellent.
Okay. So in order for users to make use
of the magic stream service, a user must
sign up with the application. To do
this, the user will access the page that
we are about to create and provide the
user's relevant registration details.
first name, last name, email address,
password, as well as the user must also
choose the user's favorite movie genres,
thriller drama comedy action crime
that sort of thing. Our system needs
this information, the genre information,
in order to recommend movies that the
user may find most appealing. That is
one of the core services that the Magic
Stream website offers, a recommendation
service. So once the user registers with
the user's relevant details, the user
will be able to log in to the
application by entering the user's email
address and chosen password. The user
will then be able to make use of the
applications services. Okay. So before
we create the registration form, we're
actually going to go back to the server
side code
and we're going to create an end point
where we can get all the genres. And if
you look at Compass quickly here, I'm
just going to launch Compass.
Let's connect.
So if you look at the Magic Stream
Movies database here, and we go to this
collection called genres, you can see
that I've created a collection that we
imported earlier on in the course. This
collection contains all the relevant
genres that will display within a multi
select box within a multi selection box
that the user so the user can select
multiple genres before the user
registers with the system. And as I say,
this is all part of the registration
functionality. The user will select one
or more genres that the user most
appreciates. So the user basically wants
movies in the relevant chosen genres
recommended from our recommendation
system. That's what it basically means.
So firstly, we're going to create
a endpoint on the server before we
actually create the client side
registration functionality. We're going
to create this this server side endpoint
within the movies controller here.
Movie_controller.go.
So the the um
the controllers package within the
controllers package within the
movie_Controller.go
file. We're going to scroll right to the
end here and we're going to create a
handler function for an endp point that
can be used to retrieve all of the
genres that are stored within this
genres collection here. Okay, great. So,
let's do that. Let's create a function.
And this is just a basic function.
Nothing too hectic. Let's go funk and
let's get genres. This is the name of
the
handler function and we want this to
hook into genonic. So jin dot handler
funk like this. We've done this all
before. Let's open curly braces and
let's return an anonymous function that
accepts a jinggonic context as an
argument. So a C
parameter of type jin.context context is
part of the
function definition of the anonymous
function that we are returning from the
get genres
handler function and we're going to
include the logic within this anonymous
function here. So this allows us to hook
into JgonX web framework and map this
function to an endp point that the user
can access from the client via HTTP.
Okay, so firstly we do the usual. Let's
just in fact I'm just going to copy and
paste this code and I'm not going to
offer any explanation but we know that
this is part this is a way for the
resources to be managed. So once the
relevant queries for the MongoDB for go
driver have been executed there may be
resources hanging about in memory and
this ensures that whether the query
fails or works that the relevant
resources are cleared out of memory
appropriately. So this is just
housekeeping code that we need to
include here. And let's create a
variable called jean
and it's of type model. So it's an array
of model dot
genre types. And it's in fact models. So
that should be models do.re. And this is
where we're going to store the
movie genres that we retrieve from the
genres collection that resides within
our magic stream movies database. Okay.
Excellent.
And of course, if we go to the models
package, so we go to movie.mmodel,
we've got the genres, the definition for
the genre type or strct here.
Excellent. So, let's go back to
movie_controller.go
and let's complete the logic for our get
genres handler function. Okay. So, we
need to create a cursor as we've done
before and we want to create an error
object here. And then let's use the
operator and genre
collection. And I'm not sure we've
created a genre collection yet. Find
Okay, let's just check if we've got a
genre collection here.
I don't think we do. We haven't got a
genre collection defined. So, let's do
that. Let's duplicate the rankings
collection here or ranking collection.
And let's make this
genre genre collection. And let's call
this genres because that is the name of
the collection within the database here
genres. So we're creating the collection
variable here. And we can go back down
to the bottom now and continue with our
code. Genre collection
dotfind. And now we've got IntelliSense.
Great. And then let's pass the context
here. And then bson.m so we want an
empty bson.mm
here because
this is to do with the bson collection
that we are retrieving from the
database. This is the type. It's an
bison format that we're collecting that
we're retrieving from the genres
collection within our MongoDB database.
So that's what that is there. And then
we do the usual go thing. We check if
error is not equal to null. Meaning we
need to handle an exception caused by
executing this query.
We can handle it like this. C.json
and open braces htt http.status
internal
server error comma and then jin.h. We're
using
uh we're using generic jin functionality
here to return an error in JSON format
to the client. And let's give it an
appropriate
let's uh include an appropriate error
message. Error
fetch fetching
movie
movie genres like that. Okay, brilliant.
And then let's include the return
keyword here so that no further code
executes from within this get genres
method. And then this is all part of the
resource clearing code. Defer
cursor. So all the resources related to
this cursor
must be cleared. And this is what we're
doing here.
Let's pass in the context ctx. We're
making sure that those resources are
cleared. No matter what happens within
this function, whether an error occurs
or it's successful, we need at an
appropriate time, we need the resources
associated with this cursor cleared from
memory. And that's just good programming
practice.
And let's carry on. So if
equals to and then cursor. Now what
we're doing here cursor, we're calling
the all method. And we're going to put
we're going to put what's in the cursor
into the genres array. That's basically
what we're doing here. And remember the
amperand refers points to an actual
memory address where that data will
reside. So the genres
the genres variable will point to a
memory location where the genre's data
will reside. Okay. And then we continue
on the same line here. So we're checking
we're we're returning an error object
here. It'll be null if this is
successful or it will contain a value if
it's not successful and we can actually
using this semicolon we can check the
error on the same line like this. So if
it's not equal to nil
here open curly braces and we can handle
that exception
http
dot and it's just a status internal
server error and let's use jin to return
wellformed JSON to the database uh to
the client. Okay. And we'll just return
what'sever in the error
whatever gets returned in this case from
the error method here which is called on
the error object. Okay, this should be
cursor
and let's press the enter key and
include the return keyword there. And
then we're in good shape if the code
gets to this point and we can return
JSON with a HTTP status. Okay,
http sorry HTTP
dot status. Okay. And then the genres
collection. And we've now written our
end point. And let's now go to the
unprotected roots where the unprotected
roots have been configured.
Um, so we go to the roots folder here,
unprotected roots, and let's configure
it. And it's just a get request.
So it's get,
and this will be just genres. So this
will be the path part of the path to the
end point. So it's obviously the
endpoint consists of a base path which
will be localhost colon 8080 when we're
testing it on our local machines forward
slash genres to get those genres and
then we map it to
get genres the function the handler
function that we've just created. Okay,
that hooks into Jenonic. Excellent. So
now we can test that. So I'm going to
run the server code. Let's go to this
terminal window here and type go run dot
to run our code.
Okay, so it's listing on port 8080 and
let's go to Postman and just test that
endpoint is valid is working correctly
before we create the registration
functionality. So here let's replace
this with genres like that. Let's change
this into a get request and let's run
it. Okay. And has it returned the
genres? It has. Look at that. We're in
good shape. Excellent. So, let's move on
and create our registration client side
registration functionality. So, we can
press Ctrl C to cancel the
Genonic web server listening on port
8080.
And let's go back to the client code.
And now we can develop the registration
functionality.
So let's do that. Let's go to the client
code here. Let's go into src components.
And remember in the last section of the
course, we created this registration
folder and the register.jsx
file. And within this file, we're going
to now create the registration form.
And you'll see a little bit later how we
need to call the genres endpoint to
retrieve those genres and then we're
going to display those genres in a
multis select box that the user can
exploit to select the various genres
before registering with our system. But
let's take care of the basics first. I'm
actually going to do a lot of copying
and pasting. I've prepared this code
offline. We don't necessarily need to go
through all of this code line by line.
It's just going to take too long to do
that. So I'm going to just paste in all
of these importation
lines of code here. So we're importing
state use effect container button form.
We're going to be uh creating a
registration form. So we're going to
make use of this react bootstrap form
element here. And then of course use
navigate and link. We'll make use of
the use navigate hook and the link
element. And then let's go through this
fairly quickly. So
I'm going to just paste in this code
here.
So you can see here we are
creating
a state variable for each of the
fields that the user must fill in
when completing the registration form
here. So, first name, last name, email,
password, confirm password. The genres
in the set genres function here will be
to do with the endpoint that we've just
created. The favorite genres and set
favorite genres are to do with the
genres that the user selects
from the multi select box that we'll
include on the registration form. Okay,
great. So, I think we've got the gist of
what we're doing here. Let's move on. So
let's just remove this JSX code here.
Create a container here. And within the
container, we'll include the form. So
let's close container off here. Okay. So
let's let's include the form and I'll
just explain what's going on here. I'm
going to just include all the fields
within the form within our code. Okay.
So I'm going to copy and paste that in
there.
So this is the form.
that denotes our registration
front end form here. So, we've got to
handle submit. We have to handle submit
once all the fields have been filled in
by the user. We've got the first name
field here and we've got a placeholder
within the text box. Enter first name.
We've got value equals first name here.
So, this is the state variable that
we've created up there. And then we've
got set first name as the as to to set
that first name state variable here when
that text box changes. And the pattern
is exactly the same for the last name.
And then same for the email. And we've
got a form control. And you see this
code is all available to you in the
relevant GitHub repository. And that
GitHub repository URL is included in the
description below. And you can just
literally copy and paste this code. Um,
so I don't want to go through and
explain every all this code line by line
because I think it's fairly
self-explanatory and you can always look
up the various elements from the React
Bootstrap website or the other Bootstrap
websites that exist where you can learn
about Bootstrap and styling and layout
and that sort of thing and CSS. Of
course, that's not what this course is
really about. This course is more about
developing a full stack application
using Jin Gonic and React. So it's not
necessarily about the styling and um all
of the front-end code that goes with it.
That's more ancillary to
the end goal of this course. So please
by all means look up all of this code
here if you're not sure of what it's
doing. Okay,
so here we go. We got favorite genres.m
map. So this one needs a bit of
explanation. Form select here. This is
where we're creating that genres, that
collection of genres within a select box
that the user can use to select the
genres
that the user prefers over all the other
genres. And that's to do with the
recommendation service that is offered
by Magic Stream. So, genres.m we still
need to write the functionality to call
the genres endpoint. So, we can populate
this genres collection here on the
client. And then it's adding the various
genres to this select box here. And you
can see each option denotes a genre.
Excellent. And then down here, we've got
a button to actually register while the
users information is being processed on
the server. spinner is displayed within
the actual register button here and
that's what that code is there and uh
the the button will be disabled also
while the the various fields are being
processed but as I say please go to the
go to where the code is hosted on GitHub
and you can just copy and paste the code
I'm not going to go through and explain
this line by line because that will just
take too long and then we've got
onsubmit equals to handle sububmit and
we need to write that method but Before
we do that, let's include a header.
And I'm just going to paste it just
above the form here. Okay, brilliant. We
actually need to
close the div.
That div is associated with that div
there. And we need to close the div
here, just above where it says container
here.
And that's just a header.
Okay brilliant.
Excellent. So, create your Magic Stream
Movie account is the header there. Um,
we've got a logo image which we will
include later. So, I'm actually going to
just delete that for now
and we'll create that a little bit later
so that we can brand our website
appropriately.
Okay. But let's get in the basic
functionality. Let's get the basic
functionality working for now. Okay. And
then above return here, now we want to
include our call first of all to the get
genres
um function and we're going to use a use
effect for that. So I'm just going to
paste in this code here. You can see
here we're including the use effect
hook. We're calling the genre's end
point and we're populating the genre
state variable here. And of course, if
an error happens, we're just handling
that error by
writing the relevant error to the
console window. And then of course we
have to call the fetch genres method
that we created here from within the use
effect hook. And what this means here,
this is an empty array. So when this
component, the registration component or
the register component first loads, we
want this to immediately get called and
populate the genre state variable there.
Okay. So we created that use effect
there. So we need to create a function
here which is being called
um handle genre change. So every time a
user selects makes a different selection
within the genres selection box, we need
to update the favorite genres variable.
And this is what that is doing here in
this code. Handle genre change.
So, we're updating the favorite genres
state variable which stores an array of
genres or a collection of genres and
it's set whenever that selection changes
within the select box on the
registration form.
The set favorite genres is appropriately
being called here from within this
function and it is changing the various
options within the or the various
collection items within the favorite
genres collection. That's what this code
is doing here. And we're calling that
you can see here form select. It's a
multiple select box. And we're calling
that every time the value changes. So on
change it sets it to handle genre change
and that's the function we've created
here and it updates the favorite genres
array and that function is handling that
and then we need to include one more
function. It's very important function
and this function handles the event when
the submit button is clicked by the
user.
Okay here. So
you can see
we've got our various settings here.
It's creating a payload of the users's
details. So it's creating a variable
called payload. First name, last name,
email, password, and then the ro default
role. If a user is using the
registration form, the default role is
user. An admin account can only be
created on the server. So if it's
created through the client interface
through the website then the default
role is always user. So that's what that
code is doing there. Oops, just gone a
bit far there. So user we're setting the
default role there and the payload is
being set appropriately. The favorite
genres are being set to the favorite
genres here. We're creating the payload
and we're posting that to the register
endpoint.
All of these all of this data here,
we're posting that to the register
endpoint and it's being processed and
then we're navigating to the login page
here.
Okay,
excellent. So, we've got our
registration code pretty much set up.
Might be a few little things we need to
adjust here within the handle submit.
um
form here like navigate to login. I
don't think we need to do that right
now. Oh, we can do. Yeah, we can just
navigate to login. Okay, once the user
is registered and I think
we can potentially just test that now.
Let's see what that looks like. Anyway,
I think we've got everything.
Okay.
Okay. And let's let's run the server
code first. Let's go to the server
terminal window. Oops.
And let's run let's firstly clear the
screen and let's go go run. Oops. Go run
dot to run our code. Okay. Let's go to
the client code and let's go
npm rundev like this.
And V is now running our client side
React code
on our local machines. And we can now we
should be able to now test our code.
We've got the server side code running.
And we can go through our browsers to
the client side
website.
Should load up our movies.
Great. So, we've got our movies loaded
here now.
Okay. And let's see if we can register a
user. Let's go to register. Look at
that. So, that's looking pretty good.
And you can see here we've got our
genres here. And we've got instructions
on how to use this genres select box
here. Okay. That doesn't look quite
right there. Genres. Oh, it is. Okay. So
hold control in brackets Windows or
command in brackets Mac to select
multiple genres. So if we hold down the
control key if we're on a Windows
machine, you can see how we can select
our genre. So let's select comedy,
western, fantasy, and thriller. Why not?
Okay,
so this user likes all these ones here.
And I'm going to register.
I don't know. Have I registered Gavin
Alon yet? I don't know if I have. Let's
go to users.
Bob Jones, Sarah Smith, Ben Madison,
Craig Denton. Oh, I have. Okay, so Gavin
LS. Let's select someone else. Let's
select Goth. You can always tell when a
name is made up, can't you? Goth
Jenkins. Goth Jenkins. Just sounds like
a madeup name. Okay. Anyway, so Goth
Oops. Goth. I bet you there is a GT
Jenkins out there, though. Jenkins
at Hotmail. Whoops. Let us know in the
comments if your name is Genkins.
Genkins atotmail.com.
Okay.
Genkins. Password. Okay. I'm just going
to select password
one exclamation point.
Confirm. Password
one exclamation point. So, we just stick
to one password. Just makes it easier to
test our code. And then we've selected
our genres here. And let's see if we can
register this. Okay. So, register.
Okay. So, hopefully that's worked. It's
registered the relevant uh user Goth
Jenkins. We can we'll check that out in
Compass in just a bit. And then it's
navigated us to the login screen, which
we haven't yet developed. and let's
see if Goth Jenkins exists now within
our users collection. Okay, so let's go
view reload data.
Goth Jenkins. Look at that. And you can
see his password has been
encoded appropriately. He's part of the
user group. No tokens yet because he
hasn't logged in. And we've got his
favorite genres here. comedy, western,
fantasy, and thriller.
Awesome. And now we want to create the
login screen so that GTH can log into
our system. Okay. So, let's do that.
That's the next step. I'm going to
cancel this here.
Ctrl C to cancel when you've
when uh it's listening
on your local machine. Just C through
Visual Studio Code cancels that
operation. And now we can go back and
now we can develop our login screen. So
let's go to login here. And I'm going to
do the same thing. I'm I'm not going to
explain this line by line because it
just takes too long and this course is
already huge. So okay. So anyway, let's
paste in these this importation code.
Oops. This importation code here. So we
got use state container. We got all
these react
bootstrap react elements that we're
importing here. We got we're importing
axios client and the various react
routed DOM elements and this use
navigate hook here. Okay. And let's
let's develop our login code. Let's do a
similar thing as we've done before. So
we're going to include our state
variables here. Okay, we've got a state
variable for email, password.
Okay. Right. And we got a state variable
for email and password. Okay. There's
not so much to fill out in this form.
We're obviously just logging on using a
unique email address that we chose when
registering and the password, which at
the moment is just password, one
exclamation point with the P
capitalized. We're just sticking to a
standard password. Okay. And then we've
got all these various things here. We
haven't written this use thing. That's
part of the next part of the course. So,
I'm removing that. Okay, we'll keep all
the rest here.
And the form looks
something like this. I'm going to copy
and paste the form which I've already
obviously developed. And you can just
paste this from you can copy and paste
this from the relevant
GitHub repository.
The URL to that GitHub repository has
been included within the description
below. Pasting in that code there.
Okay. And all it is really, okay, we
don't want this yet because we haven't
yet branded our
website. So, I'm just going to delete
that for now. Welcome back. Please log
to your account. That's the header right
there. And then we've got these groups,
these
React Bootstrap groups for each of the
fields. Email address here. So, value
equals email state variable. when it
changes, when the email address form
control changes, we have to set that
email state variable accordingly. And
this happens before the user hits the
submit button to log in to the magic
stream website. We've got password. It's
the same pattern here. Excellent. And
then we've got this button here for
submitting to the login endpoint.
Submitting the payload information,
which should be the email and the
password to the login endpoint here. And
we've got a link that links the user to
the register form if the user has not
yet created an account using the
register form. Okay, so that's all that
is happening there.
Okay, we go along. We're not yet using
these.
We're not yet using this use location
hook or this use navigate hook. So let's
create the actual
code that interfaces with the login
endpoint. So, I'm just going to copy
that from the code I've got off screen
and paste it in here. And
and this is the handle submit function
that interfaces using Axio's client with
the login endpoint. We're passing in an
object, a JSON object with the user's
unique email address and password so
that the user can be authenticated with
the system. And then it sets this or
object here.
Um,
okay. And I'm going to create a
temporary orth object here.
Um, up here.
This is going to be replaced in a in an
another section of this course, in a
later section of this course. But for
now, just for testing purposes, let's
set the orth object with the
with some user information that gets
returned from the server once the user's
logged in. And we'll do that now. So
we'll set that or object accordingly. So
set response.data. That's what's
returned from this post request that we
are calling here. We're calling the po
the HTTP post method here and
interfacing with the login endpoint via
HTTP and passing this object which
contains the email address and password
here and then we're handling an
exception if one occurs here.
Um, and here we're if all has gone well,
we're actually populating local storage.
But before that, we're setting a state
variable here. And this will be covered
in in a subsequent section of this
course. But for now, I think this is
fairly good. And I don't think we need
to use this navigate
variable. Or we can basically just once
the user logs in
um actually what I want to do is just
write that to the screen. I'll console
log that to the screen. Oh, we're
already doing it here. So we'll be able
to see what's returned from the Axios
client. So I think we're pretty much
ready to test the login functionality
now. Okay. Excellent.
So, I know I'm going through this really
quickly, but a lot of this is pretty
self-explanatory. You can see this is of
type submit. So, in the form here, in
the form
elements here, we've got an onsubmit
event, which is wired up to the handler
function here, handle submit. And here's
handle submit. And we've been through
what this code does here. It interfaces
with the login endpoint and passes in
the payload which includes the email
address of the user and the password to
authenticate the user with the system.
Let's actually test that. Let's go.
Okay, we need to go back to the server.
We're at the server here. Let's type go
run dot to run our code. So, it's
listening on the server. And let's go to
let's Whoops. Let's actually clear the
screen. npm rundev like that. Okay,
let's copy this URL
to the browser.
Okay,
great. And our movies are loaded up.
We've registered Genkins. Let's log in
as GT Jenkins. Look at that. That's
looking good.
Okay. So, go
jenkins
at hotmail.com
and the password I selected was pass
word one exclamation point. And let's
see if we can log in. Okay. So, at the
moment it's not doing much, but we can
test whether we've logged in by looking
at the console window and seeing what's
been returned. Okay, let's go to our
developer tools.
And it's returned an object.
And it's returned a token and a refresh
token.
That is working perfectly.
Excellent. So, we're able to log the
user in, but we will handle,
you know, we can we'll handle the the
once the user authenticates bit. So,
once the users authenticate, we'll
handle that in a better way. Um, so
it'll go, for example, in the header.
That's the next thing to do is in the
header, it'll go welcome goth for
example or hello goth or whatever. This
login and registration button will no
longer appear here. One log out button
will appear. So the user wants to log
out once he's logged in at some point.
So it'll have a log out button instead
of the login and register button there.
And of course, if the user hasn't been
authenticated, the login and register
button are displayed there. This
functionality, this mechanism is
working. It's returned uh the
appropriate information back to the
client once GT Jenkins has authenticated
and it's returned a token and a refresh
token which can be used for subsequent
requests to the server
to protected endpoints. So now that the
user has been authenticated, the user
will have access for example to the
recommendation the movie recommendation
service and will be able to stream
movies also because the user has been
authenticated. So when the server side
code is when the protected endpoints are
subsequently called this token will be
part of that request and the user will
be able to be authenticated
on the server which means that this user
now that the user has been authenticated
the client will be able to call
protected endpoint. So the
recommendation service for example can
be consumed by the user that's just been
authenticated. Okay. Excellent. Right.
So we've created the login
functionality.
So now it's time to adjust the front end
appropriately once the user has been
appropriately authenticated. The user's
logged on, it's authenticated and we
want, for example, the header to change.
So we want the two buttons that would be
displayed when the user hasn't logged
on, the register registration button and
the login button. We want those to be
displayed if a user who's accessing the
magic stream website has not yet
authenticated. And when the user
authenticates, i.e. logs in, we want a
logout button to be displayed and not
the login and registration button to be
displayed. So we're going to handle
things like that now. So what's the
effect on the front end when the user
logs into the to our magic stream
website? So if you'll recall, let's go
to the login page here. We're creating
this or
We're setting the orth object here once
the user has authenticated.
We just did this as a temporary fix. We
want this orth object to be available to
all of the components because we want
the component as it were to know whether
the user has authenticated or not at any
given time. So this isn't ideal. This is
just localized to the login function. We
want this orth to be available to all of
the components. And one way we can do
that is by creating a React context.
Okay, context is a way to share data
between components without having to
pass props down manually at every level
of the component tree. A problem often
called prop drilling.
So here's an example of where we're
actually using prop drilling. So you can
see here we've got the home component.
We're fetching all the movies and then
we're drilling down these movies to the
child component. So firstly the movies
component we're sending the movies
object down to the uh we're passing it
down as a prop to the movies component
and within the movies element within the
movies component we're looping through
all of the movies that have been
retrieved from the server and then we
are calling the movie element here and
we're passing down the movie prop. So
we're drilling down again to the movie
element or movie component here and then
we're displaying the movie. So that's
kind of like an example of prop
drilling. Whereas with context, you'd
just be able to retrieve
the relevant objects from a centralized
location. So it's kind of like it's a
data store essentially in memory that
you can just retrieve those relevant
objects at any given time within a
component. you can retrieve it from a
context and that's a much more efficient
way of doing it especially as your
application grows you want to use
context rather than prop drilling to
pass the relevant objects between
components. Okay, so we're going to
start by creating an orth provider and
this is how we are going to create our
context whereby we can pass the orth
object between our components without
needing to use prop drilling for this
purpose. So let's create
a folder.
Let's start by creating a folder within
the src. So it's going to be at the same
directory level at the same level in the
uh folder tree
as components. And so within src we're
going to create a folder called context.
And let's name this with a small C
context text like that. Press the enter
key.
Then within the context folder, we're
going to create a file called orth
provider. And this is how we are going
to create our context functionality. Or
provider.jsx
Okay. And let's
import
the relevant
components from the React package. So we
want the create
context
function imported from the React
component and the use state hook
from React like this. Whoops.
Okay. And then let's
go const. Let's create a variable called
orth context. This is our context. And
then we create our context by calling
this create context function here. So
create context. And then we open round
braces and pass in an empty object which
is signified by opened and closed curly
braces like that.
Okay. And then let's export. So we're
creating the actual context component.
Export const
proider.
This is the name of the component equals
to. And then within
round braces, we pass in a prop called
children.
And this represents all the components
in our application that will be wrapped.
And you'll see how we do this a little
bit later within the orth provider
element.
So think of this children prop as all of
our components. So this will enable all
of our components to retrieve the
relevant orth object from the children.
That's basically what this it's very
abstract at the moment but this is how
you need to think of it for now to
understand it. And let's open curly
braces like that. const. And let's
include within square braces or
comm, set orth.
And this is the the orth is a state
variable of course that's going to be
passed between our components. Use
state. Let's use use state the use state
react hook like this. And we've got the
set or function here of course which can
change the orth state variable. That's
what we can use the set all function
for. Then let's include the return
keyword like this. Open round braces.
And then within
tags here, let's create the tags for the
orth provider
dot provider element like that.
And let's close it properly here.
So we're closing the orthro
provider.provider. provider element
there and then within
within we need to include a value
attribute here and this is how we are
essentially passing down
the relevant or making available the
orth object and the set function to all
of the children within our application.
So to finish the job, we include
children here. This children prop here
within curly brackets within the parent
orth provider.provider
element. Let's save that. And then we
export default
like this or context.
So we can utilize the or context
context. Great. Okay. And we save that.
And then we want to create a hook so
that we can easily access this context
this O object and set or function from
within our component. So to make it easy
we're going to create a hook a react
hook a custom react hook and hook will
be let's create a folder called hook at
the same level as context and
components. So within src let's create a
folder called hook like this with the
small h like that. And within hook,
let's create
a file called
use orth.
And you'll see how we can use this use
hook in just a bit. jsx.
Okay, let's create the hook. It's pretty
simple. Also, let's import the use
context hook
from React.
And let's import the or
context from the provider that we've
just created.
So, this has the path to it. Context
forward slashorth oops or provider like
that.
and then const let's create the hook
const use or
equals to open and close round brackets
and then arrow function
and then we return the use context hook
and we pass in the orth context like
this. This is how we can make available
the orth object and the set or function
to
to the components of our application.
Let's export that. Export default
use or like that. Semicolon. This is all
to do with making the orth and set
the orth object and set orth function
available to the components within our
application. So we need to wrap all of
this code with the orth provider. So
these are going to become child elements
within the orth provider. And that's how
our components
can be placed into their context where
they'll be able to
use the use or hook to retrieve the orth
object and the set or function. Okay. So
within the strict mode element here,
let's include or provider. But we need
to import or provider first. So import
open curly braces
or provider and then it's automatically
detected the path from where I want to
import or provider. Excellent.
Okay. And let's
move this space. Neaten up our code a
bit. Save that. Crl S. And then we can
wrap these elements, the roots and
everything within
the O provider element. So the or
provider element becomes the parent
element here. And this is how we can
pass down
the relevant context that contains the
orth object and set function to the
child elements the child components of
our application. Okay. The next thing
let's go to the login component here
within components. Let's go to within
login here. Open login there.
And we need to
retrieve the orth and set the orth
object and set method from our use hook.
So first thing we want to do is import
the use or hook here. So to do that
let's type import
use or like this
from let's include the appropriate path
which is this
hooks
for slash use like that. Brilliant. And
then we can use the use off hook down
here. We need to comment this out. We no
longer want this here.
Or in fact, we could instead of doing it
like this,
we could
simply
you just use the set or because we
actually only want the set or function.
We don't need the orth object here
because we want to just set the orth
object.
So, we're going to go set off
within curly braces like this and
retrieve that
from our use or hook. Use like this. And
then we don't need to pass in these this
empty string. So, remove that. And we've
got the set or function available to us
within our login component. And we're
using it here set to set the orth
object. But now you see we're setting it
globally, not just locally. So by using
the hook here, we're hooking in to the
context. We've abstracted a lot of the
complexities of how the context works.
And we're hooking into the functionality
using the use or hook here. And then
we're using the set or function here.
Okay, brilliant. So we got that sorted
out.
Okay. And for now, I'm just going to use
the navigate to navigate to the
homepage.
just for now. We'll change it to
something more appropriate in a bit. But
so we just navigate once the user is
authenticated, we'll navigate the user
to the homepage where the movies will be
displayed just for now. Um but later on
what we actually want to do is if they
go to if the user isn't authenticated
and for example wants to utilize the
recommendation service so clicks the
recommendation
um menu option on the header. If the
user is not authenticated immediately
what we want is for our front end to
display the login form so that the user
can authenticate and then once the user
authenticates we want the user to be
navigated to the recommendation page the
recommended movies page rather than back
say to the homepage.
So that's just for a better user
experience but we'll put in that
functionality a little bit later. For
now let's just once the user logs in
let's navigate the user to the homepage.
Okay, that looks good to me. And let's
go to the header now and sort out the
header functionality.
Okay, so the first thing we want to do
here is import the use or hook.
import
use or Okay, and it's detected the
correct path to the use or hook here.
So, we've got the correct importation
code in place now.
And then we want to include again we
need to transform this here. We're going
to use the orth global object here by
using the use hook use or hook. So we
need to change this. So within curly
braces let's remove that or oops
or from and then use
or and then we don't want to pass false
in there anymore. This is not a boolean
variable. This is an object containing
the user's information which is
retrieved from the server once the user
authenticates. So it'll include for
example the user's token that will be
passed in with subsequent calls to
protected roots. So the user will have
access to protected roots because the
user has been authenticated. So we've
got the orth object here retrieved from
use or here.
Excellent.
And now this actually should just
propagate through appropriately here.
Hello name. But we want
within
curly braces here we want to include the
variable.
We want to include the value or dot
first name. So this is one of the fields
that will be retrieved from the server
be first name on the orth object when
the user logs in. And now we have access
from the header component to the orth
object which is now globally available
through the context functionality that
we've built.
Okay, excellent. And then the rest of
the code in fact should be good
and we'll include the actual logout
functionality a little bit later. In
terms of the layout we need to include a
little bit more functionality. Okay. So
within the components, we're not going
to include this within its own folder.
We're just going to include a file
called layouts
layout.jsx
like this. So it's got a capital L there
to stick with convention naming
conventions.
Layout JSX. So let's import the layout
element.
import
sorry let's import the outlet element
from within react routed DOM so we can
hook our layout into our centralized
routting system which is made possible
by the react routed DOM package here so
const layout let's create the layout
component and you'll see how we'll use
the layout component in just a bit but
let's just create the component for now
go return and then open round braces and
then main element. Type main whoops main
tab. And then within the main element,
this is just a the representation of the
main semantic HTML element. And then
within the main element, we include this
outlet element like this.
Okay. Excellent.
Oh, I was wondering what all these red
squiggies were about. It's because I
haven't included this as an arrow and
the red squiggies go away. That was just
an equals. Whoops. That was just an
equals causing all these red squiggly
lines. And I've turned the equals into a
arrow because this is of course a
component denoted by this arrow
function.
And this component is called layout. And
let's export the layout component. So x
Whoops. export
default and then layout like whoops
layout like that. Okay. And we need
another layout related component and
this one is going to be called uh
require or and you'll see the
significance of this as I write the
code. So required
or dot j whoop jsx
enter and let's write the code for the
required or component. Let's take care
of the importation code first. import
then within curly braces use location
navigate
and outlet like this from and then
within quotations react router DOM.
Okay.
And let's import our use or hook
from and let's include the appropriate
directory
hook
for slash use or like that. And let's
create the component const
like this equals to open and close round
brackets an arrow and then let's open
and close curly braces where we can
include our logic for our required orth
component or let's retrieve or the or
object from the global store if you like
using the use or hook
like this
and then con location for navigation
purposes from the use location
hook which has been
retrieved from the react router DOM
package here. So we can hook into that
functionality for navigation purposes
and then let's return. So if or if all
is truly then open round braces
and we just want to include the outlet
element here
that we've retrieved from react rout
that there
okay let's close we've closed the let's
close the round braces here for this
true
part here and then else and let's open
round braces here.
And then we want to navigate. So we're
using the navigate element here.
We want to navigate the user
to
the login element. So, so if the user is
not authenticated and tries to access
a protected route, we want to navigate
that user to the login screen so that
the user can authenticate. Let's include
the state attribute here and the from
location. So from
location
and then replace here. So it will
replace whatever is in the whatever page
is displaying within the layout. It will
replace that with the login. Okay,
remove that there.
Okay, we've got all these squiggly lines
here. Oops.
Navigate. Let's just figure this out. So
all Okay, we need to close off this
navigate element here.
Okay, and that all looks good now. So
basically what this is saying is if the
user is not authenticated the user for
example tries to access a protected
endpoint.
If the user is not authenticated
we navigate that user to the login
screen so that the user can enter the
user's credentials and authenticate with
the system. Okay. Okay. And let's export
default
and then the required
required orth component like that. Okay.
Now we want to sort out our roots.
That's the next step.
And hopefully this will start to make
sense to you what we're doing here. So
let's go to the app.jsx
file here. We're importing all of these
components at the moment here, but we
need to sort out this code here. Path
equals to forward slash homepage
and element now
equals to component that we just created
called layout. So we pass in that layout
element.
Okay, so those are our nonprotected
roots. Now we want to include our
protected roots
within
the required orth element that we just
created. So this is how we're protecting
those roots. So you can see now the
significance of required all. So any of
the roots included within the required
or element if the user is not
authenticated will be navigated to the
login page where the user must
authenticate and that's it significance
here. So let's go back to the app.jsx
JSX file here and let's create a parent
route for
the protected roots element equals to
and then within tags here
required or
like that and let's close off that route
here
and let's include the route here for
recommended within the protected routes.
Okay, let's include path equals to
recommended like this.
And then element
equals to and then this is a
component that we're going to create. We
haven't yet created it. Rec re receded
like that. And this is just to give you
an idea of how we are going to protect
these roots. So if the user is not
authenticated, the user automatically
gets presented with the login screen and
then the user must authenticate in order
to access the protected route here.
That's how that works. I'm going to
remove that for now
because we haven't yet created that
component
and I want to just test what happens
with the header when we now
authenticate. So, I'm just going to
remove that there. Okay. Excellent.
Um, and let's actually run our code.
Let's see what happens when we run our
code.
So, let's run our code. Let's navigate
to the server
like that.
CD
magic stream
movies server like this. Okay. And let's
go run. Sorry, let's go go go run run
dot to run our server side code.
Let's run the client side code. npm
rundev.
Okay, I'm just going to copy that URL
to the browser.
Okay,
great. So, we've written quite a lot of
code and some of it's quite complex. So,
this is what I would have expected.
We've got a few issues to take care of.
So, let's look at the first one. Failed
to resolve import. It's not finding that
path. Okay. So, let's I'm just going to
stop this. I'm going to exit the browser
there and stop running the client. As
you can see, we've also got our error
defined here. So, it's telling us where
it is. It's within the login component
here.
Okay. And I can already see what the
what the issue actually is here. Um,
anyway, I'm just going to cancel out of
that. Sorl
C clear the screen.
Okay. And we need to just fix this. And
it's because I've actually named this.
This should have been called hooks
because you want to, you know,
potentially include multiple hooks
within the hooks folder, but I called it
hook. So, I'm not going to rename it
here because we've had issues in the
past when we've tried to rename folders
and files. I'm actually going to keep
this as is, even though it's not ideal.
It should perhaps be hooks rather than
hook. So, I'm going to just rename this
here, this path to get around this
issue. Right, let's run the client
again.
dev.
Let's copy that. Let's copy that URL to
the browser window.
Press enter.
We've still got problems. Let's go to
our dev tools to see if anything's been
logged that can be helpful to us. Okay.
Render orth provider.
There's a problem with orth provider.
Let's go to the relevant. I'm just going
to close that down.
Um, and let's go. I'm going to cancel
out of this actually.
And I'm going to go to orth provider.
And what could be wrong here?
Ah, so this is incorrect.
Orthrovider.provider.
This should be or context. So I'm using
the
wrong element here. So it's orth
context here.
Okay, let's save the change. Let's run
our code again. mpm rundev.
Our serverside code is already running,
so we don't have to run that. Let's copy
that URL to the browser window.
Okay, let's go to our dev tools.
Layout is not defined. Okay, I think I
know what that is. It's just because
we're not importing the layout element
from within the app.jsx file within the
app component. So let's
close that off and let's stop our code
from running again. C clear the screen
and let's do that. So let's go to
app.jsx
and let's import the layout element with
this importation code. import
layout like this from then within single
quotes let's include the relevant path
so component
forward slash
layout like that
okay
let's see if we've got any more issues
mpm rundev
Okay let's copy this URL URL
our browser so that we can
run the client and the server side code
is already running so we don't have to
run the serverside code and we still
have issues.
Let's go to our developer tools and see
what the next issue is. Oh required or
so we're not importing that within the
app.jsx
file. So let's go out of that
and let's control C this clear the
screen and let's go to app.jsx JSX and
then we just need to import required
orth the required orth
element here
which uh navigates the user to the login
page if the user is not authenticated
and tries to access a protected route.
Okay. So
let's go to app.jsx here. We've
duplicated the layout importation code
here. And let's change the relevant
parts.
So that required orth is also being
imported correctly here.
OR. Okay. And that looks good. Let's try
run our code again.
dev mpm rundev.
And let's copy the URL to our
clipboards. Let's go to the browser.
and run that. Ah, okay. So, that's all
working now. And I want to quickly test
the effect of logging in on our front
end. I'm hoping that these login buttons
will disappear once the user's
authenticated and the logout button will
be replaced in the header here or I mean
will be displayed within the header
here. The logout button rather than
login and register. So, let's log in and
I'm going to log in as Genkins.
atotmail.com.
This is the user that we recently
registered. Genkins
password
one exclamation point. Let's log in.
Excellent. That looks good. That's
exactly what I wanted to see.
Okay. So, it's clear that we're now
logged in and we can log out using this
button, but we haven't written the
functionality for that. We will write
that functionality in just a bit. Okay,
brilliant. The next thing I want to do
is create the functionality for the
recommended component because this is
actually a protected route. And then we
can see how our application behaves when
accessing a protected route when the
user is not authenticated
and then when the user is authenticated.
Okay, so let's go out of this here and
let's write the code for that. So let's
Ctrl C this clear the screen and within
the components folder let's create
a recommended
folder like this
and within the recommended folder let's
create a file
called
recommended.jsx
JSX to house our recommended
component. Okay, great. So, let's create
the relevant code. And actually before
we write the logic for this recommended
component, we need to create
another Axios file,
another Axios configuration, but this
time where we're configuring
the header with the correct
authorization part to it, which will
contain the the the token, the user's
token. Once the user has been
authenticated, the user is given a token
that the user can then the token can be
included in subsequent HTTP requests so
that the user can be authenticated
against protected endpoints. So the user
can access those protected endpoints if
the user has the correct privileges. So
we're going to create to accomplish
this, we're going to create a new
file, a new Axio configuration.
So let's do that. We're going to create
an actual hook so that it's easy to
retrieve this Axios, this private Axios
configuration where we're passing in the
token. So within the hook folder, let's
create a new file and let's call this
file use
cate.
Whoops, sx. Like this. And let's write
the code for this Axios object so that
we can configure this Axio object so
that it includes the relevant token the
authorization token within the header of
subsequent HTTP requests once the user
has been authenticated with the system.
So firstly let's import Axios
from Axios. We've installed Axios. So we
can write the code like this. So let's
write a hook called use Axios private.
Use aios
private
equals to
an arrow function. And then let's
include the relevant code. Okay. So
let's create const axios or
equals to axios
dotcreate
like this. And we're going to create our
config code here.
Okay. And then of course we want the
base URL.
So we're going to read in the base URL
from our environment variable. So in the
environment variable we've got the base
URL configured there.
Um
so let's first read that in. So we go to
do that we go const API url
equals to import
meta. NV. This is how you read in
environment variable values
um when your react project has been
created using vit api_base
url like that. Whoops.
URL like that.
Okay. And that should read in the
appropriate
environment variable value for the base
URL from here.
Let's go back to our hook that we're
creating our hook code. So now we can
configure that base URL by typing base
URL
colon then API
URL like that. It's the base of the API
URL. And now what we're going to do is
create something called an interceptor.
Whenever this Axios object attempts to
call an endpoint, it's going to add
certain information to the header. And
in this particular case, the information
we want is the token, the O token, the
access token, so that the user can
authenticate against protected
endpoints. Okay. So to do that firstly
let's
retrieve
the orth object
and set function
from our use or hook and we aren't
importing it just yet but we'll do that
in a bit.
Use
like this.
Okay, let's import that use orth hook
with this code here.
Import use orth. Okay. And it's found
use in this path. That's excellent.
Automatically it's done that. Okay. And
then what we need to do is create an
interceptor as I said. So with this
interceptor
with this interceptor
it's going to intercept a call to a HTTP
endpoint and add and we want to add
relevant header information so that the
user can authenticate against that
protected endpoint before accessing that
endpoint. So let's go axios
or
dot in
to
seps
response.
This is how we add an interceptor to an
axios object dot use and then open round
brackets. Then we want to pass in a
config variable like this config
round braces and then arrow function.
Okay, we want to configure the header.
We don't need that round bracket there.
We want to configure the header. So,
config.headers
headers
do authorization
like this
equals to within quotations bearer space
curly braces. We can include the
variable. So we need to precede these
curly braces with a dollar. So we can
include a variable within the single
quotes.
Okay.
So now we can use the or object
and dot token like that. H and sorry
these need to be back to characters
because we're including the variable
within the curly braces. So let's remove
those single quotes and include back to
characters instead of those single
quotes here like that. Okay,
great. Okay, so we only want this
configured if of course the author is
true these. So we need to include an if
statement there. So if
or like that and then open curly braces
like that. We want this code to reside
within there. If the orth is true, the
if the orth object is truly the like
that and then we return
config because we've configured it
appropriately for axios like that.
Excellent. That looks good now. And then
outside of this here for the axios for
the actual hook we want to return axios
or so we include the return keyword at
cos like that semicolon.
And that looks good.
Okay. And then let's export this as
default.
Export this hook as default. So default
use axios private
like that. So it means the user must be
authenticated to use this access
component and then the or token can be
included within sub within HTTP requests
once the user has been authenticated. So
instead of using this, we want to use
the
this Axios object for protected roots
which will include the token within the
header of the calls to the relevant
protected endpoints. Okay, great. That's
all that means there. And then let's
create our recommended component, which
is of course a protected route. So we're
going to want to use this hook
to get the relevant Axio object to
retrieve the relevant Axio objects.
Let's go back to recommended and let's
write the code. So firstly let's import
use axios private
from
dot dot slash
dot dot slashhook.
So it's just hook not hooks forward
slash use private. Oops
for slash use private
like that. So we've got the correct
importation code there. Let's import
use effect
and use state
from react like that.
Let's import
the movies
component from this location
from
movies slash
oops forward slash movies like that.
Great. And let's create our component
const using our arrow function
recommended. It's the name of our
component then arrow function and within
our arrow function we include the logic
for our
for our component. So first thing let's
create a
movies state variable
and set movies function to change the
state of that variable equals to use
state
that pass in an empty array.
So an empty
array for the movie state variable we're
defaulting to that firstly and then
let's create a loading variable
loading and set loading
function equals to use state and we want
to initialize
the loading state variable to false
firstly
and then const
message then set message
equals to use state like this.
Okay. And then we want to retrieve
our AIOS object from the hook that we
just created. So Axios private
equals to use
Axios
private. So it's use Axios private hook
like that. And let's create a use
effect.
Let's use the use effect hook like this.
So when the component first loads, we
want to call the recommended movies
endpoint
so that we can retrieve the recommended
movies for the relevant user and it's
from a protected endpoint.
Okay, let's create the function that
fetches the movies. We're going to call
this fetch
reccommened
movies. Okay, fetch recommended movies
equals to a
sync
open and close brackets arrow function
like that. Oops.
Error function like that.
Oops.
Set loading to true
like that.
Set
message to empty an empty string. And
then let's include a try catch block
here.
Okay. And then const response. All
right. Let's just include the catch
section here. Catch
error. And then let's handle the error
by just console dot
error method.
And we'll just write that to the console
screen. So
error
fetching
recall mended movies like that.
And then more detail about the error and
the error object. We can include that
in what's written to the console screen
when an error occurs. Okay. And then
finally, so this code gets called
whether an error occurs or not.
And we'll set loading to false. So we
don't want any loading indicators
displayed once this process has resolved
whether an error has occurred or not. We
want to set the loading. We want the
loading indicator to disappear at that
point. Okay, great. So then response
equal oops equals to await
axios private.get
and then
for slashremened
movies. That's the name of our end point
there. And then we need to set movies.
set the movies state variable
to response dot data whatever is
returned. Bear in mind we're using the
Axios private Axios object here and
we've created an interceptor. So when we
call a protected endpoint the token the
orth token is being sent along with that
message that HTTP request to that
protected endpoint. So we're so the user
is being authenticated against that
protected endpoint the recommended
movies endpoint. I just want to double
check that that is the name that we
called our endpoint. So we can go to
roots protected roots and recommended
movies is the appropriate end point. And
we've tested all of these endpoints
through Postman. So we should be able to
test that fairly efficiently.
test our client fairly efficiently at
this point because we know the server
side code works. Okay, there's that our
use effect hook written. We need to call
the use effect, sorry, we need to call
the fetch recommended movies function
within the use effect hook. So we do
that like this.
Okay. And we need to pass an empty
array. And this is to do with how the
life cycle hooks work in React. So we
want as this when this recommended
component loads, we want it to fetch the
users's recommended movies.
Okay great.
Okay, and the return jsx is pretty
simple. That's the most complex. We've
just written the most complex part of
this particular component.
And so we're checking the loading state
variable loading. So if loading is
truthy then
while our component is loading
let's just include a very basic loading
indicator. We'll include a proper
spinner loading indicator a little bit
later, but for now, let's just include
text like this. Loading dot dot dot.
Okay.
Else then open round braces. We include
the movies
element.
We pass in the movies state variable.
We're prop drilling here, I guess you
could say. We're passing the movies.
And if there's an error message, we're
also passing the message down to the
movies
component here. And we've got loads of
red lines here. We want to include this.
We did we don't have a parent element.
So, let's make this as a fragment.
which allows us to
the ability to not have to include an
apparent HTML element like a div for
example. So we can just uh get round
that by including a fragment here which
are just which are denoted by empty
tags. And that looks pretty good. And we
mustn't forget to export our component.
It's easily done. Export default
recommended.
So now we're exporting the recommended
component as default
which means we can import it here within
our
app.jsx file. So let's do that.
So I'm just going to duplicate that
there. Let's import recommended
from components
recommended
and then with the R capitalized
recommended like that.
Okay. And now we can include within the
protected roots which is wrapped by this
required or um element that we created
as the parent element for our protected
roots. And we can create a dedicated
route for that recommended
element. So
recommended
is the client path
to the recommended
component. So the client route
is defined by for slashrecall mended and
then that's a path to the recommended
component there and it's wrapped within
the required or element because it's a
protected route. So in other words, if
the user is not authenticated and they
try to access this route, the login
screen must be presented to the user
indicating that the user needs to
authenticate with the system before
accessing the recommended the relevant
recommended movies.
Okay, and that looks pretty good. I just
want to double check that within the
header it is indeed
that link is indeed linking to this path
here. So, if we go to header here, let's
have a look at what's going on.
Recommended nav link. Yep, recommended.
And that's what I want to see. So, we
can now test that. I think it should
work fairly well. Although, actually,
I'm going to go to the login screen, the
login component here, and include this
code for the navigation. So it doesn't
navigate
the user. When the user authenticates,
say the user's on the homepage, the
movies are displayed. The user wants to
check the user's recommended movies. The
user's not yet authenticated. The user
presses the recommended header menu
item. The user is then navigated to the
login screen. When the user logs in, we
don't want to navigate the user to the
homepage again to see the movies. We
want the user to go directly to the
recommended screen. So let's comment
that out. And this code here will do
that. It should direct the user
red. Yeah, it should log the user on and
then direct the user to the screen that
the user was trying to access
before the user was authenticated. So
that just makes for a much better user
experience
by doing it that way. And I think
there's a piece of there's more code
that we need to write to make that work.
Not sure I've got that in there. Yeah.
Okay. So, we need to
also this from
variable here. We need to retrieve that
appropriately from use by using this
location variable. And I'll show you how
to do that now.
So,
and that's retrieving
the relevant
uh path that the user was trying to
access before the user authenticated.
So, let's do that. const from
equals to location dot state
dot from
dotpath
name
like that. And if it's null, whoops. And
if that is null, so there's no
we can use we can navigate the user to
the homepage.
So if this is actually null, this path
name which is taken from the browser's
history, if this is actually null,
it will by default navigate the user
once the user is authenticated to the
homepage.
Okay,
excellent. So I think we're in good
shape there and I think we're ready to
test it. We've still got our server
running. Okay, great. So we don't need
to rerun the server. Let's run the
client. mpm rundev.
Okay, great. It's running.
Hopefully, there won't be too many
issues with this. Could be some issues.
Okay let's
go to the client. Okay, that's looking
good so far. So, now we're not
authenticated and let's access the
recommended
menu option here. Okay, so the first
part of it the functionality is working.
We're not authenticated. So our code has
navigated us to the login screen. And
let's let's now authenticate as go
jenkins@hotmail.
[Music]
This is hotmail address. His email
address.com.
So go jenkins@hotmail.com.
Password
one. So we are including Genkins's
credentials within the signin form. and
let's try and log in and let's see what
happens. Okay, so we got a problem and
that's okay. And let's just investigate
this by going to the developer tools. So
more tools, developer tools.
Okay, so it's responding with a 401
exception. And I suspect that's
something to do with the interceptor not
adding the token into the header before
making the call to the protected
endpoint, the recommended movies
endpoint. So it's a problem with Axios.
So we kind of got an idea of where to
look. So let's close that down. Let's go
to our code here. I'm going to controll
C that cancel and clear the screen. And
let's take a look at use Axios private
here. And I can already see what the
problem is here. I included response
here. And this should in fact be
request. We're making a request.
So every time we make a request to an
endpoint, a protected endpoint, we want
this our orth token to be added
appropriately to the header. So this
should fix that problem. And let's see
what happens now if we run our code. So
our server side code is still running.
So npm rundev.
Let's copy this URL
to the browser. Let's launch the our
browser window.
run our code.
Brilliant. So, our movies are being
displayed to us, but this is a
nonprotected endpoint for the homepage.
It's just the movies endpoint that is
being called to retrieve these movies
and then displaying it on the homepage,
which is correct. And then we want to
see our recommended movies, but of
course, we haven't authenticated with
the system. So, the system doesn't know
who to recommend those movies to. And we
haven't of course authenticated whether
we have the privileges to use the
recommended
movies facility. So we need to
authenticate. So go
genkins oops at hotmail.com
password
one exclamation point and let's log in
and let's see what happens. Excellent.
So that means our token has been passed
to the server
after we've authenticated
and the system has recommended the
movies based they're all excellent
movies apparently the sentiment attached
to all of these movies so it's
recommended the best movies in the
genres that we chose which were western
comedy thriller drama I believe th those
sort of genres were
selected when and we registered as Goth
Jenkins. So, it's recommended the
appropriate movies. But this is big
because we've now from the client, we're
now able to access not just protected
endpoints,
not just sorry, not just unprotected
endpoints like the movies endpoint,
which displays all the movies on the
client for us in the on the homepage,
but we've also
now passing in a token
with requests to protected endpoints
like the recommended movies protected
endpoint. So when we press recommended
the recommended when we select the
recommended
header menu option here it automatically
authenticates
us on the server because the token is
being passed in with the request to the
relevant protected endpoint. After we've
authenticated with the system, the token
is passed down to the client. The client
then stores that in memory and then when
we subsequently call protected
endpoints, the token is used to
authenticate us. So we can now just
because we're authenticated, we can now
go swap between protected endpoints. We
can call protected endpoints and
unprotected endpoints. So we can now
call unprotected endpoints and protected
endpoints no problem once we have
authenticated because that token can now
be passed in with subsequent HTTP
requests to protected endpoints and we
can authenticate through the use of that
token that access token.
Excellent.
Okay. So in this part of the course, we
are now going to create the UI part
associated with the AI functionality. So
this is when the administrator logs into
the system and will have a facility
available to that administrator and the
administrator can then create a movie
review for a particular movie and then
when the user submits when the admin
user submits that movie review. That
movie review is sent along with a base
prompt to open AI. It is anal an
analyzed and through the prompt the
instructions are to extract one word
describing the sentiment behind the
movie review. So this is a way for us to
extract quantitative information from
qualitative information. The qualitative
information is the movie review written
in natural language and that is becomes
part of a prompt to the AI. the AI
analyzes that and returns a just one
word and then we're associating a a
value with that word. So, uh the options
the word options to describe the
sentiment that we have included in the
prompt are excellent, good, okay, bad
and terrible. And we you can see that
it's very easy for us to associate a
value with each of those words. So,
excellent
um has an associated value of one and
terrible has an associated value of
five. Okay. Is value associated value of
three. Bad has an associated value of
four and two is the value that
represents good. So, it's a way for us
to rank the movies in a sense. So,
through this ranking, you can order the
movies by those values. And that's
exactly what we're doing for the
recommendation system. So, we're
recommending five of the top movies
within a users's genre, using those
rankings to order the movies, those five
movies. And if, for example, there's
more than five movies available for the
users's chosen genre, it will only
recommend the top movies based on those
sentiments, those rankings extracted
from the admin's movie review. So, let's
get started. And in fact, I'm going to
keep this very simple. We're going to
get this done really fast because I'm
not going to write out all of the code
because most of it on the front end is
just Bootstrap, uh, classes and CSS
basically just to style a UI facility.
Um, uh, the code is available on GitHub.
So, please go to the GitHub repository
if you want to copy and paste the code
and then you can do your own analysis on
the various Bootstrap options. By all
means, please look them up on the
various Bootstrap
websites so that you can uh get more
detail about those classes and what
they're doing. But it would just take
too long for me to explain every single
Bootstrap class and uh I think it's
unnecessary for this particular course.
So, as I say, I'm just going to copy the
and paste the code for this particular
UI facility and then I'll give you a
brief explanation of what everything is
doing. So, let's um create within the
components folder. Let's create a new
folder and let's call this review with
the R in
lowerase. So, review like this. And then
within the review folder, let's create a
file called review.jsx.
And I've already written this code. It's
on GitHub. I'm actually going to copy
and paste the code into this file.
And here is the code. Just pasted it in
here. And I'll explain
the code to you now.
Okay. So this is what the front end is
doing. We're using the Bootstrap the
relevant Bootstrap classes to basically
split the screen in half. On the left
hand side, we've got the actual movie
which will it'll display the poster and
we're reusing the movie element for this
purpose. And this is just the various
Bootstrap classes that are used to we're
using the Bootstrap grid system to place
the movie on one half of the panel, the
movie poster, and relevant details
including the sentiment. And you'll see
how that works when we run our code. uh
we can change the sentiment
on the fly in real time by updating a
text area box which appears on the right
hand side. So that takes up the other
half of the screen. The right hand side
of the screen is taken up by this text
area box. So it's just a big text box,
multi-line text box that where the
administrator can add the
administrator's review like this. So
it's a text area type for this form
control. We're using React Bootstrap for
the form control element here. And then
we've got the submit review button here,
which is a submit button. Type equals to
submit. And we can submit the form
value, the form control value for the
text area, which contains the
administrators movie review to the
server to our review endpoint
or what have we called the endpoint? The
end point is update review. and we're
sending that along as with a patch
request to the server and the relevant
IMDb number of the movie so that the
server knows which movie to update with
the relevant admin review. So of course
we're using Axio's private because it's
a protected endpoint. Remember in the
last part of the course we're behind the
scenes we're adding in the users token.
In this case, it would be the
administrator's token that the that is
received from the server side when the
relevant user logs in. So the token is
returned to the client and then we are
including within this functionality here
use Axios private this hook. We're
including an interceptor and we're
adding that token to the header
so that requests from that particular
user can be authenticated on the server
through the use of this token which
contains the user's claims which is just
information about the user
and it includes the user's ID for
example the user's role and that sort of
thing. So the user can be authenticated
on the server and therefore access
protected endpoints. And we've taken
care of that behind the scenes here
within the configuration of this
particular Axios object that we're using
here
within the review.jsx JSX file to send a
patch request to the server to the
update review endpoint where the
administrator is updating the
administrator's movie review and then
what's returned is the ranking name here
which is the sentiment behind the movie
that AI is extracting. So please by all
means refresh your memory and go back to
the serverside AI functionality that we
created which sends this movie review
along with a prompt to open AAI um and
the open AI functionality
extracts a sentiment from the movie
review basically. So it analyzes the
movie review written in natural language
and extracts one particular word and we
can attach a numeric value to that word.
So the words are excellent, good, okay,
bad and terrible for but they can be
extended. You can create an entire
spectrum of rankings. So this uh
software is designed to be extensible in
that regard. So you could add as many uh
ranking values as you like or sentiments
sentiment options as you like
um for this functionality.
So that's basically it really. And
so if we just to describe what's
happening here, we are analyzing the
orth
state variable here. This is the or uh
variable that gets populated when the
user authenticates. But we also have to
check the orth ro that it's admin in
order for that particular the particular
admin
the particular administrator to have
access to this text area control so the
user can update the administrator's
review.
Um or else what happens we've got an
else part here. We're just putting that
review in readon mode. So any user can
it doesn't have to be an administrator
to actually see the review but the user
has to be an administrator in order to
update the review. So here we're just so
here in this particular scenario
the code is deemed that the user is not
an administrator and is displaying the
relevant review within a an alert
control and this is just a bootstrap
class which turns this div into an
alert. So it's basically read only. You
can see the review in in readonly mode.
But if you're an administrator, you can
actually update that review and send it
through to the server to be processed.
Great. Okay. And we haven't yet created
the spinner code here. Um I think it
might be a good time to do that
actually. And I'm going to do the same.
This code's not that important. It's
just for um a better you user
experience. I'm actually just going to
create a new folder called spinner here.
And I'm going to create spinner.js.
Okay.
And I'm going to copy in the relevant
spinner code.
And it's just basically a span and a div
and utilizing various CSS properties to
create the spinner effect.
Okay. And we are within the review
we are importing spinner up here. And
then while the
relevant UI screen this component is
loading that spinner will display. So
while this loading state variable is
truthy, the spinner displays and once
the data is ready,
the loading indicator is
is disappeared and the actual UI is
displayed
to the user where the user if the user
is ad if the user is an administrator
can update the relevant movie review.
Okay. Okay. And now we need to update
the app.jsx file here. So let's go to
app.tjsx.
And it's in a protected endpoint.
So we need to firstly import the
endpoint.
So I'm just going to copy this here.
Duplicate this code and then change the
bits the relevant bits. So So this one's
called review. Okay.
review
review. So we're importing the review
here.
Okay, review is declared but its value
is never read. So we're going to do that
now. We're going to utilize this review
component within
we're going to add a root for that
particular component. So I'm going to
duplicate this here.
path equals 2 and then it's going to be
review like this
and we need to actually include
a parameter
within this path and it's the IMDb
parameter imdb
ID parameter because when this route is
accessed we need to pass an a unique
movie ID
so that the movie can be extracted. The
relevant movie data is extracted uh from
the MongoDB database and the relevant
data is then displayed for the movie on
the review screen.
Okay, that's how that works. And of
course, we need to replace this
recommended with re view. Okay, we've
mapped that route now. That looks good.
Okay, excellent. And then we also need
to include a review button
for our movie cards. But we don't want
that review button. We're actually
displaying a movie card. We're reusing
our movie element on the administrators
panel where the administrator updates
the relevant movie review,
but we don't want the review button
displayed in that regard. And this is
just uh we're going to use prop drilling
to to accomplish that.
So, let's do that. Um,
update review. Okay, this is worrying
me. There's Okay, already included file
name Golang magic stream view.
Okay, I've done it again. I've done
something stupid. Sorry. Okay, I don't
know why I do that. That's got a small
letter in it. I'm going to just cut this
here.
Okay. And I'm going to delete this file
here.
[Music]
And I'm going to recreate that file.
Sorry about this. It's got to have a
capital letter here. View.jsx.
Let's stick to protocol and make sure
that that letter is capitalized. Can't
believe I've made that mistake again.
Okay. Sorry about that. And let's paste
that code back in there. And that should
sort out our problem on the app.jsx JSX
page. Great. That has Sorry about that.
We were importing, sorry, here it is.
We're importing the review here. And
there's no red squiggly lines there now.
Okay, great. So, and we've got our root
setup. And the next thing is to we've
pasted in the update movie review method
here which simply navigates to the
review page and passes in the relevant
IMDb ID value to this route here and
then that parameter can be utilized
within the review
page. So if we go back here you'll see
how that parameter
Here we go. So we're using the use
params hook to get the IMDb ID value
here and we're importing it from react
routin
this Axios private get query
we're
getting the relevant data for a
particular movie which will could
potentially include the review for that
movie and the ranking the sentiment and
all the movie information
for that movie the title the movie
poster etc. And we're grabbing that from
this movie endpoint
which must include as a parameter the
IMDb ID value of the relevant movie.
So this is how we're passing that to the
server the relevant IMDb movie. Then it
queries our MongoDB movies collection
and returns the movie data. And then
we're setting the movie data here. the
state variable called movie. We're
setting with the set movie function
here.
Great. And then we're reusing
our movie element here. We're reusing
that element here and displaying the
relevant movies information in this
particular UI. Okay, let's go back to
the prop drilling code. Um, I got a
little bit distracted by that error um
issue we had on app.jsx. So what we want
to do is drill down this value here.
This method we want to
drill that into
the uh home element first. Okay. And
this is because we only want the movie
button, the review button displayed in
certain context and I'll I'll describe
those contexts in more detail in just a
bit. But let's just drill this down
using prop drilling. Let's drill it down
to the
home component and we're
sending our method down there. So it can
be used well it can be used within the
movie component ultimately
but uh through a button that the button
when it gets clicked will call this
navigation functionality through this
method call. So we're passing that down
to the home component.
So, we need to include this prop
within the home component. Let's go to
the home component here.
Okay.
Uh, we include it here. We include the
prop here. Okay. We don't need those
curly braces.
Okay. There. And then we actually want
to drill further down
to the movies component here. So, we're
going to do the same thing here. We're
going to include the prop, the relevant
prop here. Paste that in there. So,
we're passing down that method
reference, the reference to the update
movie review method that resides
actually within the code for it for
which resides within the app.jsx file or
the app component here, but we're
drilling it down. We're passing a
reference to that method down firstly to
the home component, then to the movies
component. So, this is some serious prop
drilling. So we need to include it here.
Okay.
And then we can drill down further into
the movie component. So we do that like
this equals to like that. And then we
must include this prop within the movie
component here.
Okay.
And we can do that like this. And now
we're passing in that update movie
review method. And now what we do within
the movie component is create an if
statement
where if update movie review is truthy
include the button.
And in this respect we can control
when the button is actually displayed
for the relevant movies. We can control
that by checking whether it's truthy or
not. So if it's passed down as a prop to
the movie component, then we include the
button. If this isn't included as a
prop, the button won't display. So we
can control when that review button is
displayed. And then we're calling the
update movie review method here and
passing in the relevant unique
identifier. When that button is clicked,
this code is executed here on the
app.jsx
file here and we're using this navigate
utility here to navigate to the review
element. And we're passing in the
relevant IMDb ID like that. So the
relevant movie review is displayed, the
relevant movie information and the movie
review is displayed on the review page.
And I think we're in good shape. I think
that's all the code we need for now.
Let's let's run the code and see where
we are with this.
Okay. So, I'm going to run the server
side code. And we do that by typing go
run dot like this. Press the enter key.
Excellent.
Okay. Okay, so the server side code is
running. Let's run the client. npm
rundev. Press the enter key
to run the client code. Hopefully we
don't have any issues here,
but I guess there's always a chance that
we do. Anyway, let's copy that to our
browser window
so that we can test out our code.
Okay, we have a problem.
Okay. Failed to resolve import hooks.
Ah, that should be hook. Okay, no
problem. It's because we copied and
pasted the code in from the prototype
type code that I wrote in preparation
for this course. That's what's causing
that. So, uh, where do I go here? I go
to review. So, let's go to review and
let's change this to hook.
There's another reference to it down
here. Okay. Save that. MPM rundev. There
any more references I don't know about.
Let's run that. Okay, let's copy that to
the
browser window and let's see what we
got. Okay, excellent. You can see now
our review
our review button is being displayed
under each of these movies.
So, that has worked perfectly. And now
we should be able to click the review
button to review a movie. Okay, actual
review code is not working for some
reason. Just check what's going on here.
Navigate is not defined.
I'm just going to clear that.
Um,
let's go to app.jsx. Here we're not uh
retrieving a variable. We're not
retrieving a value from the use navigate
hook yet. So to do that, we can type
const
navigate
equals to use navigate
like that. I don't know what I was
thinking. I' i've included this outside
of the function to include that within
the app function, of course. Okay. MPM
rundev.
Okay, let's copy that to our browser
window.
Let's try again. Review. Great. So, it's
prompting us to authenticate. Let's just
log in as goth jenkins
at hotmail.com
password
one exclamation point. Let's log in. And
there we go. I love the acting in this
movie. It was absolutely sublime. And
why isn't the text box showing in this
particular case and the submit button?
Well, that's because this user is not an
administrator. It's not part of the
admin role, but part of the user role.
So, all the relevant movie information
is being displayed and the sentiment.
Excellent. But we can't change that
sentiment through updating this movie
review because we're not an
administrator. So, let's log out.
Oh, we don't have the log out
functionality yet written, do we? Okay.
So, we can't log out. Um, let's go home.
Let's just cancel out of the whole
thing. And we'll write we'll have to
write the log out functionality soon.
Let's clear the screen and let's run the
client again. But this time we are going
to log in as an administrator. And let's
check who's an administrator. I believe
it's Bob Jones. He's always an
administrator. So let's go into the
compass utility here and check our data
to see who we have designated as an
administrator. I believe Bob Jones is an
administrator. Let's check and then we
can log in as Bob Jones. He's an
administrator. Bob Jones. Okay, great.
And you can really make any one of these
an administrator because you have access
to the back end. Just change the role.
Just double click in there and you could
change, for example, Sarah Smith to an
administrator. I won't do that now, but
you can easily do that if you want to
test other users as administrators.
But let's log in as Bob Jones because
that's an easy thing to remember. All
Bob's credentials are pretty easy. Um,
we're still running the code. So, let's
go to our browsers and copy that in
there. And let's see if we can log in as
Bob Jones now. And let's go to Once Upon
a Time in Hollywood. And let's review
Once Upon a Time in Hollywood. Let's
click review. Let's log in as Bob Jones.
Bob Jones at hotmail.com.
And then password one exclamation point.
login and look at that. This movie is
awful. And it's not awful at all. It's a
Tarantino movie and it's really quite a
good movie. I really enjoyed it. But
we've decided through this admin review
that the movie is awful. So now
let's change that. Let's uh say um
another
masterpiece
by
I don't know how to spell terino.
I think that's right. Another
masterpiece from Tarantino.
Tarantino.
Another masterpiece by Tarantino.
The acting was also
sub
blime
sub sub
blime.
Let's see what it extracts from here as
the sentiment. So, let's submit the
review. There we go. We got our spinner
working. It's come back. Excellent. So,
we've changed the sentiment to excellent
from terrible to sentiment through our
admin review. And isn't that great? You
see, let's let's try another review. And
this time, let's do a rather middle of
the road review. So, let's say um it
wasn't
great,
but it wasn't
awful either. Let's see what sentiment
we get from that.
Okay, middle of the road. So, that's
working pretty well.
This movie
wasn't
too bad. Actually, I'm hoping this will
bring back the sentiment of good.
Good.
So, our AI is behaving itself, our AI
functionality, and we're able to add
reviews because we're an administrator.
We logged on as Bob Jones and we can add
our reviews to our movies now and it's
extracting the appropriate sentiment
which has a bearing on our
recommendation functionality here which
brings back
the
recommended movies and the users genres
the chosen users genres and it brings
them back in order from one to five. So
all of these are excellent. So it's
ordered Highlander 2 is apparently
excellent. Okay, we've got to do
something about that review cuz this was
a horrible movie. Um,
so it's bringing back Highlander 2 as
excellent, Pet Detective, excellent. And
then Once Upon a Time in Hollywood,
we've just changed that review as good.
And then Hangover is good, and Harry
Potter as okay. So, it's extracting the
five top movies in that particular
user's chosen favorite genres. And that
is working perfectly. So it's ordered
them by ranking which is the sentiment
and we've associated a value from one
excellent to terrible five. And that
means we can order by them quite easily
here.
So that's our recommendation system is
working really well there. But let's do
something about Highlander 2. I'm not
accepting that. That was excellent. So
let's go back. Let's go down to
Highlander to excellent. No, it was an
abomination. And I'm going to I'm going
to reflect that in my review. So, this
movie is an absolute
[Music]
tempted to use a some colorful language
here, but I won't. I'll just say this
movie is an absolute abomination
with an exclamation point. So, that
should be terrible. Now, let's submit
that through and see if our AI deems
this movie is terrible. And it does.
good. This is a terrible movie. And you
can see now the effect it'll have on the
recommendation system.
See, now Highlander is not even
mentioned here in our recommended movies
for Bob Jones because we've decided Bob
Jones is an administrator and he's
decided it's terrible. So, it's been
unceremoniously kicked off
this page completely.
Great. So, it's no longer being
recommended as a as a excellent movie as
a movie.
And you can see that these movies have
been ordered by the ranking value.
Excellent. Good. Good. Okay. Okay.
Brilliant.
Excellent. So, we can't log out yet.
That's the next thing we need to do is
build the login the logout
functionality. But I'm really happy with
what we've done so far. And I think that
looks pretty good. And it's pretty easy
to use. And it's responsive, too. You
can see here if we make the screen
smaller, it just pops that under there
like that, which is pretty good. Okay.
Excellent.
And we can just use our menu like this.
So if we go to recommended, it just
plunks those. This is the beauty of
using Bootstrap. Our screen is
responsive. Our screens are responsive
because we used Bootstrap. and it
handles that for us automatically.
So I'm really happy with that.
Brilliant.
So at this point we have in place all
the functionality or most of the
functionality for our application but at
the moment we are passing our access
token to access protected endpoints from
the client through the header of the
HTTP message. So if we look here for
example, if we look at use Axios
private, you can see here with this
interceptor when we try to access
protected endpoints, we first are
passing we are first adding the O token
to the header of the relevant HTTP
message every time we access protected
endpoints. So this is actually a
vulnerability to sophisticated cyber
attackers. The reason for this is at
this point we are currently storing the
token in memory and in many cases when
including the access token within the
header of the relevant HTTP messages to
access protected endpoints, you may be
tempted to store for example the access
token in local storage on the client.
This is a security vulnerability because
sophisticated attackers can potentially
read the access token from local storage
or even from memory using malicious
JavaScript code. So to overcome this
security vulnerability, we are going to
store the access tokens using HTTPON
cookies. So why is it safer to handle
the passing of the access tokens from
client to server using HTTPON cookies?
Well, this is because HTTPon cookies
cannot be read through JavaScript code.
And therefore, this is much safer. This
is a much safer way to secure the access
tokens. So, what are HTTPON cookies? A
HTTPON cookie is a cookie that cannot be
read via JavaScript in the browser. For
example, through document.cookie.
Document.cookie cannot be used to read
the cookie. It's set automatically by
the browser with every request to the
server from the domain that set it. The
HTTP only flag makes it inaccessible to
scripts and the secure flag ensures it's
only sent over HTTPS.
Why are access tokens sensitive? Access
tokens are essentially keys to the
user's account. If someone steals them,
they can impersonate the user. How can
they be stolen? What are the major
threats? XSS, cross-sight scripting.
This is where an attacker injects
JavaScript and reads tokens from memory,
local storage, or session storage. CSRF,
cross-sight request forgery. This is
where an attacker tricks the browser
into making authenticated requests. So,
how do HTTPON cookies help? Protection
against XSS.
If you store an access token in local
storage or session storage, any XSS
vulnerability can let an attacker read
it directly. With HTTP only cookies, the
token is not exposed to JavaScript. So
even if malicious scripts run, they
can't read it. Automatic sending with
requests. The browser automatically
includes cookies with each request to
the back end if same site rules allow.
So your front end doesn't have to
manually attach the token to each API
call. Let's create the code that will
now store the access token as HTTP
cookies. So firstly we need to adjust uh
what is occurring on the server side. So
let's go to user not user model user
controller. Let's go to the login code
here.
Okay, we want login user and there it
is. And you can see currently
we are passing the tokens down here
and we don't want to do that. So the
first thing I'm going to do is remove
this this code. So firstly I'm just
going to copy uh comment these out. We
still want to generate the tokens in the
same way. We still want to update the
database with the tokens. So we're
generating all the tokens here. We're
updating the tokens within the database
and then we're passing the tokens back
to the client. This is the part of the
code that we definitely want to change.
And how do we do that? So we do that
with this code here. Okay.
So just above where we are
sending the HTTP response back to the
relevant client, let's set the tokens
here. So we are no longer passing that
token back to the client like this.
We're passing all the other information
to the client and we'll see why this is
useful in a bit when we address the
client side code in this respect. So
here we go. We're setting the cookie
access token here. We're setting a
timeout here. Okay. And
we've set the secure flag to true here.
HTTP only true. So it's a HTTP only
cookie that we that we are now sending
back to the client and this happens
automatically. So we don't have to
manually send it back like this with
this code here through the response.
It's going to automatically handle that
for us. And then we also want to do the
same for the refresh token. So we're
setting the refresh token here to an
appropriate HTTP cookie, HTTP only
cookie and the access token. And we'll
address what the significance of the
refresh token is in just a bit. We've
done so we've now changed the server so
that the access token will be passed
from server to client via a HTTP only
cookie. There's a few things we need to
do on the client to make this work. So
let's go to the client code. We need to
adjust the code here for the the first
Axios object. So it's actually a very
basic update. We need to include this
with credentials
flag
and set it to true like this. And that
will ensure that the HTTP cookies are
passed between client and server. And we
actually need to do it even though this
particular Axios object is not used for
protected endpoints. We still need to
ensure that the client and server that
the tokens are being passed between
client and server every time we send uh
a message to uh the server from the
client we need to ensure that those
tokens are sent via HTTP only cookies.
Um so we all we do is we set this with
credentials property to true and we need
to actually do the same now
in our
hook here where we've got the
interceptor here. We no longer need to
intercept
and pass in the bearer token like this.
But we are going to use an interceptor a
little bit later. For now, I'm actually
just going to copy this. I'm going to
comment this out. I'm just going to
ensure that with credentials is set to
true here also like that. Okay. So that
is now correct. So we'll leave this as
it is for now. And you might be asking
yourself, what's the point of even using
two separate Axios objects? But we'll
address this in just a bit. Why I still
want to maintain this separate Axios
object for accessing protected
endpoints. And it's to do with the
protect. It's to do with the refresh
tokens. And we'll look at that logic in
just a bit. But for now, let's just keep
our logic as is here. Um, in fact, we
don't need this line of code here
either. The with credentials property is
set to true. And that will ensure that
the HTTP only cookies the relevant tok
that store the relevant tokens are
passed between client and server. Okay,
automatically. So we don't have to add
anything to the header and therefore
potentially risk a security
vulnerability to XSS attacks. So we're
no longer adding the bearer token here.
Okay,
let's go to our login client now. Now
that we've sorted that out, let's go to
our login client here. And I want to
make sure that we're storing it in local
storage. So that's what we want to do.
We want to store the orth data. So this
contains the orth data. Here you can see
that we are
setting the orth state variable which is
now global. We made that global. We made
that global through context
um in the last part of the course. So
we're setting it here with
response.data. So after the login
occurs, we can see here on the server,
if we go back to our login code,
oops, we can see on the server that
we're passing this information back, but
we're no longer including the tokens.
We're just passing the user ID, first
name, last name, email, RO, and favorite
genres back to the client from the
server once the user logs in. But this
these cookies here, these HTTP only
cookies are passed back automatically.
So, we don't have to manually do that
through code because they're by virtue
of the fact that they're HTTP only
cookies and we're setting the
appropriate flags on the client, we're
saying, please do that on our behalf.
Please just send those HTTP cookies
automatically.
And we're setting with credentials
equals to true here. And we're doing the
same for this Axios private hook that we
created where we were using an
interceptor to add the relevant token to
the header when accessing protected
endpoints. We're no longer doing that.
We're just setting with credentials
equals to true because we want those
tokens to be passed between client and
server automatically. And this is how we
do it. But this is still relevant
because we're going to
um adjust the code here later on so that
it handles refreshing the tokens. But
we'll look at that in just a bit. We'll
look at that later on. So, okay. So,
let's go back to the login here. And
we're setting the orth object here.
We're setting the state variable. Well,
let's just first check that our code is
still working. We haven't messed
anything up.
Um, okay. So, I'm going to create a new
terminal here. And let's go cd server
slash magic
stream
movies
server like that. Okay. And let's run
our code. Go run dot. So, we're running
our server side code. Hopefully, we have
no issues. Good. It's listing on port
8080. Let's go to the client terminal
and navigate to the relevant directory.
So client slash magic
stream
oops client like this. Okay. And let's
run our code. So it's npm rundev to run
our code.
Okay. And let's copy this now to our
browser window. And hopefully
I haven't introduced any bugs.
Error
fetching movies. Oops. First bug. Okay,
so interesting. Let's see what's going
on there.
Developer tools. Okay, so we've got to
address cause. We've also got to change
the cause policy
um appropriately.
Okay so
let's do that. Let's handle that code.
Let's go out of there. Let's cancel out
of here. Crl + C clear and we got to
handle cause. Okay, so I've got to also
cancel Ctrl C cancel the server from
running. Okay, and let's sort out cause
we got to go to the main.go file to do
that. So it's this code, this
configuration code here we need to also
change because we're we're um going
cross domains with our client and server
which gives you a lot of flexibility. If
it's on the same domain, you basically
have to always have your code on, you
know, running on the same domain here.
We can run our components on disparate
domains, which is what we're doing here.
But the cost of that is we have to
configure cores appropriately to make
that work. So, let's do that.
And I'm actually just going to
copy and paste the code for that. This
code is available on GitHub. So, please
by all means just do the same thing.
Just copy and paste the code from GitHub
if you don't want to type it out
manually just to save time. So, I'm
going to copy this from my code off
screen here
and I'm going to paste it over here
just above where we're setting up the
roots. Okay, great.
And
okay, we need to import log there. We're
using log and os of course because we're
reading the environment variable but we
need to configure the environment
variable
because what we're actually doing here
is building up URLs
that we want to permit to access the
server basically. So we're going to
include
our client side URL for the react code
within the environment variable. uh and
we can we can store multiple allowable
clients in that environment variable by
using a comma delimited string that
stores a delimited string of the
allowable
client URLs the the the URLs that we are
allowing access to our serverside code.
So to do that we need to go to env and
configure the relevant URLs.
So, here are our allowed allowed origins
here. So, we've got HTTP
localhost 3000, HTTP localhost 5173,
which is the one we're currently running
our client on, and then HTTP localhost
8080. So these ones are irrelevant for
now, but you could run your client from
these addresses
running on port 8080 and port 3000. And
our cause configuration would permit
these clients to access our server.
Um, so let's go back to main.go. So
we're reading all of this this
information in. We are creating this
origins array here
and we're just logging them so that we
can test the code to make sure that it's
working correctly.
And then we've got we're setting allow
origins. We can't use the other way. We
can't just allow all origins like we
were doing before. We have to allow
specific origins because we're now using
h the HTTP only cookies method and we
want to when we publish our code to the
cloud we want to use HTTPS so that our
code is so that our data is encrypted on
the wire between client and server that
it's exchanged between client and
server. So this is another way to
prevent our data from potentially being
stolen.
So we're going to use HTTPS instead of
HTTP so that our data is encrypted on
the wire when exchanged between client
and server. And in order to do that and
use HTTP only cookies, we need to
configure our cause in an appropriate
way. And this is what we're doing here.
As I say, please the code's quite
involved. by all means just copy the
code from uh GitHub and then go through
it in your own time line by line so that
you make sure that you understand
exactly what's going on here.
Okay, brilliant. So we've now configured
course correctly. Let's run our server
code again. Go run dot
Okay, excellent. Let's go to the client
code. Let's clear the screen.
Clear. Whoops. clear npm rundev.
Okay,
let's copy the URL.
Let's just make sure that that URL we
have indeed permitted that URL that
we're running here 573.
And we need to make sure that within our
environment variable yeah 5173. So we
are indeed permitting that origin to
access our server side endpoints. Okay.
So let's copy that to the browser
address and hopefully we won't get an
error now when we try to access our
movies endpoint. And now that is working
correctly. But let's see if we can still
authenticate the user a particular user.
So let's log in. Cray no it was go
Jenkins. Let's use go genkins. That was
the last person that we
registered. go genkins at oops at
hotmail.com
password with the pc capitalized one
exclamation point.
Okay, excellent. So that's now working
correctly. We've authenticated goth. Oh,
okay. Oh, so it is saying hello. I was
wondering what was happening there, but
the font isn't black, so it's being
hidden there. So we'll have to sort out
that problem. Hello go. Okay. And then
we also need to do the log out, write
the logout functionality. Let's go to
recommended.
Okay. So, we got a problem here cuz
we're using the Axios private
um object
to access that endpoint and there's a
problem. So, let's have a look at what's
going on. More tools, developer tools,
401. Okay. So, something is going wrong
there.
It's not allowing us to.
Interesting. Okay. So, it's not allowing
It's not authenticating. It's giving a
401 unauthorized error. It's not
authenticating
Genkins
when he tries to access his recommended
movies. Okay. So, the recommended movies
endpoint.
So, what could be happening there? Let's
check. Let's investigate this. Let's
I'm going to cancel out of this. Clear.
Cancel out of the server code here.
Clear. Okay.
Okay. I mean that looks good. So this
can't be a problem.
Um
must be to do ah okay. H of course.
Okay. Okay. So what I haven't done is I
haven't addressed the middleware. So or
middleware. So it goes through this
middleware code when it before it
accesses any of the protected endpoints.
You can see here the protected roots
routter use middleware. Middleware. So
it's running the code within here. So
let's go to definition
and see we're going get access token.
Let's go to get access token. Let's go
to definition there. And of course,
we're still reading it from the bearer
token, which is not what we want to do.
So, we need to change this code, get
access token.
Okay. And it's pretty simple. The code
for that is pretty simple.
So let's
comment out this code here. I'm just
going to keep that commented out so you
can still have a record of how you would
do it if you were passing the token
through the header of the relevant HTTP
message. And I'm just going to paste
this code in here.
Token string here
uh like this. So we're reading the the
access token from the cookie now instead
of from the header. That's what we
needed to do there. And that is the crux
of our problem. So let's see if that has
solved our problem by updating the get
access token.
utility function here. So we're getting
it from the HTTP only cookie instead of
the header, okay, of the relevant HTTP
message. So let's run our server side
code. Go run dot.
Let's run our client side code. I'm
first going to clear the screen so we
can see what's going on and then I'm
going to run it. So let npm rundev like
this. Okay, I'm going to copy this to
the browser and hopefully
we don't have any issues, but you never
know. Okay, so
getting our movies. Great. So, let's log
in. Go genkins
at hotmail.com
password
one exclamation point.
We're logged in. Let's see now if we can
access the recommended protected
endpoint. Excellent.
Great. So, it was the way we were
getting the the token within the
middleware. The middleware protects
those protected endpoints. So, we were
still trying to get it from the header
and we're not we're not storing the
token in the header anymore. We're
storing it in a HTTP cookie for security
reasons that we've just discussed.
Great. So, those are Goth's recommended
movies. So we're able to access
endpoints and that is really good. So
the next step is to write the logout
functionality. Let's do that now. Okay.
So I'm just going to cancel out of that.
Ctrl C to cancel out of that when you're
running the client. And it's the same
when you're running it through VS Code
on the server. Just control
C. Let's clear the screen. And now let's
write the logout code.
So we're going to actually write this
function within
the app component and we're going to
drill it down using prop drilling into
um the header I believe. Yeah, we want
the log out.
We want obviously the logout button
resides within the header. So when the
button is clicked, we want to run that
logout functionality and we are going to
store that logout functionality.
We're going to store the method that
handles the logout functionality within
the app component. Okay,
let's create the function for handling
the logout. We'll call it handle logout.
And I'm just going to paste it in here.
As I've stated a few times now, you can
just paste in the code from
the code that's stored on GitHub and uh
you can copy it from the relevant page
on GitHub and just paste it into your
app.jsx code here.
Okay, so I've pasted in handle log out
here and we're going to pass that down
through prop drilling to the header.
Okay,
to the header component. So, and we can
do that through this line of code. But
obviously, we need to include the prop
within the header component. So, let's
go to the header
component here.
Okay. We need to include that within
curly braces.
Handle log out.
Okay. And then we need to include
the logic within the button.
So, the logout button here, we need to
include an on click. And we can do that
very simply with this line of code. So
we include an on-click event and a
non-click event handler that we're
passing down from the app.jsx component.
We're drilling that into if you like
drilling that into the um header
component so that when the user clicks
the logout button, it runs the relevant
logout code. So we actually haven't
created this endpoint on our server. So
that's the next step cuz at the moment
that's going to nowhere.
But if that serverside code did exist,
we're posting the users ID, the relevant
users unique ID to the logout endpoint.
And the logout endpoint handles the
logout functionality on the server. But
let's create the server side code, the
relevant server side code for logging
out.
Okay.
So I'm actually just going to
just paste that in from the code that
I've got. So let's go to user_controller
and I'm going to paste in the code from
the prototype code that I've created in
preparation for this course just to save
time and we'll just briefly look at the
code here. So we've got a function
called logout handler that hooks into
jinggonic so that we can expose an end
point that maps to this function. So
when the user goes to that endpoint or
when the client calls that endpoint,
this function is called and the relevant
logic is applied. So we've got a logout
strct here, local strct that includes
uh JSON data, the user ID, it maps to
that. So this user ID will will store
the user ID passed in through the body
of the post request to this logout
handler function. And then we're binding
to the strct with what's passed in
through the body, the user ID, which
will be passed in as JSON data through
to this function when the relevant
endpoint is accessed.
And it's uh accessed via a post request,
a HTTP post request. So we're handling
any exceptions here. Then we're just
logging it to the screen for testing
purposes. we're logging it and then
we're updating the relevant tokens to an
empty string here because we're logging
out. We don't need these stored anywhere
on the server, the tokens. So, we're
logging out. So, that's why we're doing
that and handling any exceptions. And
then we're setting the token
to expired minus one to invalidate the
token
um the HTTP cookie. So it cannot be used
anymore in subsequent requests.
And we're doing the same for the refresh
token. And then we're just passing back
a message to the client with a status
response HTTP status of status. Okay.
And a relevant message logged out
successfully.
So now the next thing of course we need
to do and we can do it in unprotected
routes is we need to map an end point to
the logout
jinggonic function. So it's so it's an
unprotected route and this is just this
unprotected route has this and the path
log out.
So this is the end point log out and
then we of course need to map it to the
appropriate genonic function which is
log out handler
log
out handler. So let's go to the client
and see what's happening there. So we're
posting to the logout. We're posting the
users ID. It's returning us HTTP status
of okay hopefully. And if all has gone
well we're setting or to null. So we're
setting that global orth variable to
null. and we're removing it from local
storage. Okay, so let's test that out.
Let's see if we can log out of the
system now. So let's run the server. Go
run dot
and let's run the client. Let's first
clear the screen here and let's go mpm
rundev to run the client. Let's copy
this URL
to the browser window
like that. Okay, great. Let's
authenticate as goth jenkins
hotmail.com
password one dot. Okay, log in. We still
need to do something about this, but it
has said hello goth, but it's in black,
so we can't see it against the black
header here. But we have logged in
successfully. And we can go to
recommended. That proves that we are
logged in successfully. And then uh we
can log out by pressing this button
which is not working. Okay. So let's
just see what's going on here.
Oko
logged out reference Axios client is not
defined at handle logout. Okay.
Oh, I see. Okay. No, it's just an
importation problem there.
Okay. So, let's fix that. I'm just going
to go to the client and quickly fix
that. We're not importing Axios clearly
here. Okay. Okay. So, we're trying we're
trying to call
the logout endpoint using the Axios
client, but we haven't imported the Axis
client here. So, that's what's happening
there. And it's easy to remedy this
problem with this line of code here.
We're just importing the Axios client.
So we need to import the use or hook
here
like that
and make sure
that we set a variable the state
variables or and set or retrieve the
state variables or and set from the use
hook like that. So, this should actually
be hook, not hooks. Let's change that
there.
MPM rundev.
Okay.
Copy that URL to our browsers.
Login as Goth Genkins.
Okay. Are we logging in here?
Everything's working. And let's see if
we can log out. Brilliant. So, our
logout functionality is now working
brilliantly. That's great.
Okay. So, what I want to do here is for
the use private hook, I'm going to
replace this code. So now you'll see the
significance of using a private hook
here to get the to use this use Axios
private hook where we're creating a
completely different access object to
the one we're using for protected for
unprotected endpoints here. And of
course
we're using this hook code here that
creates a completely different Axio
object. And the reason I'm doing that is
so that we can refresh the token which
will keep the user logged in.
But this code is super involved and I'm
not going to do it line by line because
you'll see it's got there's a lot here
involved. But I will give you a brief
explanation of what's going on. By all
means please
look at the code on GitHub and copy and
paste it into your own applications and
go through it line by line yourself to
get an understanding of what's actually
going on here.
Briefly, I'll explain briefly what's
going on here. So,
for example, let's say the user has left
the user's browser open and the token
has timed out. So what's what's actually
happening here is this code is going to
receive a an error a 401 error here. So
what it does is it puts that request
here within a failed queue. So it puts
it within a queue here and then this
code here traverses that queue and tries
it again once
a new refresh token a new token the
token has been refreshed.
So this is why we have a refresh token.
The client, you know, once the access
token times out, so it gets the
appropriate error sent back to the
client, the token is timed out. This
client makes a further post request to
this refresh endpoint, which we haven't
yet written. We'll write that code in
just a bit. And it gets given a new
token. So the access token and the
refresh token are both refreshed on the
server and passed back to the client.
And then the client then this code here
that traverses the failed queue
will then try again with the new
refreshed token to access that protected
endpoint. So the tokens are refreshed.
So in other words, it doesn't just log
you out. the the client is still remains
lo the client still remains logged in
and our code here just automatically
refreshes the token on the server so
that the client remains logged in. It
doesn't just log you out automatically
or present you with an error that your
token has expired. It refreshes the
token for you. And we've even got code
here that if you've left the token to
expire, in other words, you've just left
your browser open and the token has
expired and the refresh token has
expired, then it logs you out and
presents you with the login screen. So,
you have to reauthenticate. But you can
control the time on the server, the time
when the tokens expire. But this is what
this code is doing here.
So, if your token has expired and you
try to access a protected endpoint, it
won't just log you out. it will make a
post request to the server to get to
refresh the tokens. So you will remain
logged in and on the quiet it's
refreshing the tokens and then a
subsequent then it will process the
queue
here and a new call to that endpoint
that you tried to access where the token
expired will be made with the new
refreshed token. So that will go through
and so you won't be disrupted on the
client and this makes for an excellent
user experience. So you will remain
logged in until you actually manually
log yourself out which will invalidate
the tokens. So this is how this code
here is working. But I urge you to go
through the code the logic line by line
and try to understand this code.
So that's what this code is doing here.
It's refreshing the token if the tok if
the access token expires and keeps the
client logged in so that the client has
control over when the client logs out.
Okay. So now you can see that we're
making a call
here to this refresh endpoint. We
haven't yet created that refresh
endpoint. So, I'm going to go to the
server side code and create that refresh
endpoint within the user controller
file. Well, we're going to create the
handler function within the user
controller file and map it to an
appropriate refresh endpoint. So, again,
I'm just going to copy this code in here
to save time, but please by all means go
through the code yourself
to understand what is going on.
Okay, here. So let's copy this function
handler code in here. And you can see
what's going on here. We're getting the
refresh token from the cookie here.
We're validating the refresh token. So
the refresh token is automatically being
passed in via the HTTP only cookie that
we're setting when the user logs in. So
the token has what's happened here is
the token has expired on the client and
instead of the user being logged out
when the user tries to access a
protected endpoint
the code automatically makes a call to
the refresh endpoint and this logic is
run. This jinonic handler function is
run and the refresh token value is
extracted from the relevant HTTP only
cookie here and we validate the refresh
token here
and we
make sure the user ID that is also sent
through with the refresh token. It will
be within the refresh token encoded
within the refresh token. So we can
access the user ID by extracting that
user ID from the token itself. So it's
being decoded. The claims are the claims
that are encoded within the refresh
token are being decoded and read here.
So we're able to read the user's ID from
the claim here and find the user within
the database.
Um and then we can generate new tokens.
So we're generating a new refresh token
and a new token. And then we are saving
those relevant And then we are saving
those tokens to the relevant HTTPON
cookies here. And the tokens have been
refreshed. So the user remains logged in
when the user accesses a protected
token. And the access token may have
timed out. Our client code automatically
makes a request to this logic here via
an appropriate endpoint and the tokens
are refreshed and and saved to the
relevant HTTP only cookies. So the user
can remain logged in and can access
those endpoints. So it's up to the user
to log out
manually. So that makes for a much
better user experience. But if the
refresh token and so if the token if the
access token and the refresh token have
timed out,
we've got this code here that actually
logs the user out properly. In other
words, it removes the local storage
setting, the or setting. Bear in mind,
this user local storage setting will no
longer store the tokens. So it will
store information about the user but not
the tokens. So there's no security risk
in storing the the sort of the first you
know the first name the last name and
information like that. So the tokens are
no longer stored here but we remove the
user information and we set the orth
global or state variable to null. So
we're logging the user out. If the
access token and the refresh token have
expired then we log the user out with
our code.
Okay. So, let's go back here. User
controller.
Okay.
Have an issue there. We need to close
that off there. Save that. Getting a
problem there. User dot user ID. User
collection.
I think we called it user collection.
Sorry. Let's change that to user
collections cuz I'm copying and pasting
the code. Validate refresh token. Okay.
We don't have validate refresh token. We
haven't written that code yet.
Clearly
undefined. Yeah. So we haven't written
that ref uh validate refresh token
function yet.
User do user ID saying user do user ID
is undefined.
So we're mapping that user strct there.
So, user do user ID doesn't like that
there. Um, just going to look at my what
my model looks like here.
Yeah, user ID. Okay. So, we've included
that as a capital D. Okay. So, that's
the reason. So, I'm just going to make
that a capital D
to solve that problem there.
Um,
validate refresh token. We need to write
the validate refresh token. I've already
written that in the prototype. So I'm
just going to copy that
validate refresh token. It's very
similar to the validate
token method,
but obviously it's meant for to validate
the refresh token. So I'm just going to
copy that in there. Let's go to our
token util
file here and paste it in there. Okay.
Um, we have we're not reading in the key
clearly there. So, we need to
make sure we're reading in Oh, secret
refresh key. Okay, we are reading in the
secret refresh key there, but we
referencing a different name there. So,
secret refresh key. Let's update that
there.
And that now looks good.
And all we're doing here is writing
standard
Go code here to validate the refresh
token in much the same way as we've done
to validate the access token here. It's
pretty much it's this code is very
similar. Okay,
great. Let's go back to user controller.
What's the problem? Let's
go to problems.
Ah, user ID. So that user ID there also
needs to be changed to user capital ID
like that. Okay, fine. No more problems.
Now that we've written our refresh token
handler, the next step is to map
an endpoint
to this refresh token handler function
gingonic function. So this will also be
an unprotected route. And let's include
the relevant post request to the refresh
endpoint there. the relevant code that
maps a post request to this refresh
endpoint to the relevant gingonic
handler function. Okay, I think we're
ready to test that now. Let's just
double check that I haven't broken
anything. We're not going to do a
detailed test on that refresh
functionality, but you can by resetting
the various timeouts. You can trigger a
a uh access token. You can trigger the
access token to expire by appropriately
setting its time out. And then you can
test the refresh functionality yourself.
Okay. And I'm going to run
the client
dev npm rundev like this.
Okay. Let's copy that to the to the
browser.
Okay.
Big screen. That looks okay. Let's log
Gu Jenkins in.
Hotmail.com
password one exclamation point.
Great. Okay. So, we're logging GTH in.
Obviously, we need to do something about
that. No point in having Hello Goth in
black against a black background. So,
we'll do something about that.
Recommended. So, it's all working fine.
And can we log out? Yep, we can.
Perfect. So, we're in good shape. Okay.
Great.
And then to do with the authentication
on the front end and how it affects the
front end and logging out, there's one
more test I'd like to do. Firstly,
actually, I need to run the code again.
Run dot
npm rundev. I just want to show you
something at the moment why the login
functionality isn't ideal just yet.
So firstly I just so I want to show you
why the login functionality isn't ideal
yet. So if I log in
uh okay so
let's log in as G Jenkins again. Why not
stick to the same user
Jenkins
atotmail.com
password one exclamation point. So we're
logged in and we can access all the
relevant endpoints. All is just fine and
dandy and wonderful. Great. But now what
happens if we refresh the browser
window? Look at that. Logs us out.
So we've we can no longer access our
protected endpoints. So that's all. So
that's a huge inconvenience if the
client if the user just refreshes his
browser and just gets automatically
logged out. If for some reason the
client decides to refresh the browser,
look what happens.
And we're still, you know, we don't want
the user to be logged out at this point.
We want the user to remain logged in
even when they refresh the browser. So
we can actually use the
to to resolve this. We can actually use
the orth
information that gets sent down from the
server when the user logs in and we can
we are currently saving that to local
storage
and we can use that to check whether the
user is logged in or not. So when the
user if the user refreshes the browser,
the user can remain on the recommended
page and not be presented with the
sign-in screen. So let's write the code
to resolve this issue. Okay, I'm just
going to cancel out of that. Clear the
screen.
Okay, we can keep the server running.
We're not going to create any server
side code. We're just going to amend our
code on the client to resolve that
issue. So to do that you can see what's
actually happening here. First let's go
to orth provider. We've got the orth
provider component here. We're going to
amend the code within the orth provider
component. But what's actually happening
here is if we go to required or here you
can see that it's checking the orth
state variable. It's retrieving it from
a global store within the context. So
it's retrieving the or state variable.
It's checking it here. If the orth is
truth the it outputs the expected page
that the users navigated to. So in this
case it would be the recommended page.
But if that or is falsy then it
navigates it explicitly navigates the
user to the login route here which is
what we want if the user hasn't yet
authenticated. But if the user has
authenticated, we want the recommended
page displayed to the user. So to
resolve this issue, when the user for
some reason decides to refresh the
browser and then it presents the user
with the login screen, we don't want
that if the user has authenticated. But
we do want it if the user is not
authenticated because then the user can
authenticate through the login screen
and the login functionality. But if the
user is just refreshing the page but is
in fact being authenticated, we want the
recommended page presented to the user,
we don't want the user to be navigated
to the login screen. We're setting this
local storage value here with the
response sent back from the server when
the user logs on which contains various
information about the user. Now what's
important to note here is that we are no
longer sending the tokens back for
security reasons. That's being handled
by HTTP only cookies. So but we are
sending information about the user that
is not so sensitive like the user's
first name, last name, email. That's
okay to store locally because it doesn't
matter if some nefarious code comes
along and reads that information. It's,
you know, it's not going to allow them
to access protected endpoints like the
token would because the token is like a
key that can be used to unlock protected
resources.
But we do want to store information
about the user returned from this login
endpoint once the user has
authenticated. And we are storing that
in local storage within the browser.
We're storing it statically. So whether
the user if the user refreshes the
browser the context information is
actually flushed out of memory but this
user information that's saved to local
storage will be stored statically within
the browser. So we can still read that
information from the browser but we
can't read that state information after
the users refresh the user's browser. We
can't read the state information here in
the orth variable and the orth state
variable that will be cleared from
memory. So we won't be able to read
that. So that is the reason why this
code here is then navigating us to the
login screen once the user refreshes the
browser because that orth state has now
been cleared. So it's set to null. So
this code is running correctly. But
that's not what we want
uh for the user's experience to be. So
to resolve this, let's go to orth
provider here. And the first thing we
want to do is create. So firstly I'm
going to create a loading state variable
and you'll see how we use that in just a
bit. And then we need to include this
code here.
And you can see what we're doing here.
We're reading that user
data from local storage which is stored
statically in the browser.
So this is the way we can get around um
if the browser refreshes the user's
browser. If the user refreshes the
user's browser, that O information is
lost pretty much even if the user's been
authenticated. So that's currently what
happening. But now we're actually
reading that information from local
storage.
And if that is successful, we're
resetting that state variable. So after
the user refreshes the browser, we're
reading in that information from local
storage and resetting that orth state
variable which is accessed now globally.
which can be accessed globally through
the use or hook that we created. So
that's what we're doing here. So when
this loads again, we're reading that
information from local storage which is
stored statically. So the information
when the user logged in is not lost. So
this is how the user can remain logged
into the system even
even if the user refreshes the user's
browser and the orth information is
lost. We can read that information in
through
this setting in local storage.
Okay. And the next thing we need to do
is include this use effect. So when the
orth state variable changes it runs
this. So if author is truthy, we can set
the item in local storage.
And if it's not truthy, if it's falsy,
we're removing that from local storage
there. And now we we're handling this
centrally within the orth provider
component here.
Okay. And we need to import use effect
because now we're using it twice within
this
component here. The or provider
component.
Okay. Okay, we got two use effect hooks
running here. One that executes when the
component loads. So, it's reading in the
users's information from local storage
and the other use effect only runs when
the orth state variable changes. And
that ch will change when the user logs
in and logs out. So it'll be set to null
when the user logs out and it'll be set
to the users's information when the user
logs in the relevant information but it
won't include the tokens so that our
application remains secure. We are
handling the tokens through HTTP only
cookies.
Okay. Excellent. So now let's go back to
the login. So now you can see here where
where we're adding it to local storage.
We're now doing it centrally within the
orth provider.
um component. So we no longer need to do
that here because this use effect will
run when changes
within the login component. So when we
set off
the this use effect will trigger if you
like and this code will run. So if orth
is truthy, which it will be when the
user logs in, we're setting we're adding
that information to local storage. And
when we log out, conversely, when we log
out, so let's go to the app.jsx
file here. When we log out here, we are
actually removing it explicitly. But we
don't need this code because now that
code is centralized
centralized with an orth provider here.
So we're removing it when the orth is
set to null. So it changes this use
effect runs. It goes ify then set it.
But if it's falsy, if it's been set to
null and it's changed from truthy to
falsy, in other words, it's been set to
null. The user's logged out, we remove
it from local storage here. And that
code now is central is centralized
within the orth provider component. And
we need to pass down
the loading state variable.
So, we're setting
loading to true here because when we're
setting it to true by default and then
when the use effect hook runs,
it's going to be loading
and then
if
and then once it runs through this code,
the loading state variable is set to
false. Whether an error occurs or not,
it's still set to false. So, that
loading indicator will disappear.
Okay. And then the next thing we need to
do is go to
required orth. Now we need to see
exactly what required orth does first.
So let's go back to app.jsx. You can see
here we're wrapping these protected
roots within the required orth element
here.
So this required orth protects these
roots on the client. So let's go to
required or and see what's happening. So
you can see if orth so if the user is
authenticated you can go to the
protected route the or the user will see
the protected route that it's trying to
access else if that is null the user's
explicitly navigated to the login
screen. So the user needs to
authenticate.
So we need to include and this logic is
fine by the way that's perfect. So we
actually need to include this code here.
So
we're going to include a loading
indicator this our spinner that we
created earlier on. So we need to import
the spinner
there.
So we're going to show a loading
indicator if that loading
state variable that we are retrieving
from use or the use or hook which
engages with our context that we've
created within or provider here. See,
we've created this loading state
variable here that's being set
appropriately within this functionality
that reads in the users's information
and then sets the orth
state variable appropriately.
Um, so we go back to required or here.
So if it's loading, we want to display
the spinner and we're importing the
spinner there.
Great. Okay, we're now retrieving the
loading state variable which is now set
globally within our context
and this logic is correct. We don't have
to change anything else. So I think that
will now work and we're no longer
setting that um local storage variable
once the user logs in because we're
setting it within the lo login component
because we're setting it now centrally
within the orth provider and remember
the orth provider component or element
wraps all the roots. So that's that's
how we can pass down the context. That's
how the context is available to all of
our components within our application
which includes our protected roots where
it's needed for for example with the
recommended
component here.
Okay. One other thing I want to do is
within orth provider
I just want to put this try to to
this try code here to
encapsulate all of this functionality
here.
Okay. So let's just move that down
outside of these this curly bracket
there.
That makes more sense.
Okay, that looks all right. Okay,
so we also we want to catch any
exceptions like when it's reading the
local storage setting, we want to catch
that if there's a issue there. And of
course, we want to set the loading
indicator to false whether an error
occurs or not when processing this
code here, which reads in the user
information, pauses it because it'll be
in string format when it reaches reads
it from local storage and turns it into
an object here and sets the orth
state variable. PM rundev.
Okay.
Let's go to the relevant URL here
recommended. Okay, let's log in
like that. Let's go to the recommended
page here and let's see if if what
happens when we refresh this page.
Brilliant.
Okay,
there we go. So, we're refreshing the
page by selecting the address
and pressing the enter key. And now
we're remaining logged in.
So, this remains in the log in state.
And we've still got Hello G here in
black. I need to sort that out.
Um, but yeah, so that solved our
problem. We're now
able to refresh the browser window and
remain logged in. So that makes for a
much better user experience. You don't
want the user to perhaps inadvertently
refresh the user's browser and be logged
out of the system. That doesn't make
sense. So, but we want the user to be
able to explicitly log out on their own
terms, which is working now. Great. So,
the user now needs to reauthenticate.
Go genenkins@hotmail.com
password one exclamation point
and now the user just remains logged in
no matter whether the user refreshes the
browser or not. Okay. And so that works
as expected as you as one would expect
that to work. Hello go that needs to be
in white I think.
Um,
so I'm going to just sort that out.
Um,
so I'm just going to add a class
to. We can just leave that running
there. Go here. Let's go to the header
component.
And where it says hello goth, let's just
include these classes to style it
better. And that should propagate right
through. And there it is. Hello Goth.
Excellent. So we've got now Hello Goth
appearing appropriately in white on a
black background. That looks much
better. Excellent. So that's all working
for good measure. Let's just try and
refresh this a few times. And we're
good. We're remaining logged in as Goth
Genkins.
Okay, great. So, but we can log out
whenever we choose. So, let's log out
now.
And we're logged out. And you can see we
need to reauthenticate if we want to go
to the recommended page now. Okay. So,
we're good there.
Brilliant. I'm really happy with that.
Okay. Now, the next thing I want to do
is create
kind of a substitute for the streaming
functionality. And in fact, all we're
going to do is play
a YouTube video, a trailer for each of
the videos, which just substitutes for
the streaming functionality. So, we're
going to bypass creating actual
streaming functionality and just
substitute in playing the trailers in a
React component called React
Player. So, the first thing we need to
do is actually install React Player. And
it's very easy to do. Okay. So now we
want to be able to stream our movies and
we're not going to actually create an
actual actual streaming functionality in
its place. We'll just run a trailer that
exists on YouTube
um within a component called react
player. So we're going to use a third
party component to actually play the
movie the relevant trailer per each
movie. If we look here in compass, we
can actually see that each movie each
movie contains a field called YouTube ID
that contains the ID of a movie that has
been published to YouTube. And these
particular videos are the trailer for
the relevant movie document within the
movies collection.
So that's how we are able to
play a video pertaining to each of these
movies. So we're not actually streaming
the movie. We're we're in fact playing
the trailer for the movie which exists
on YouTube. And we're going to use a
component called React Player to help us
create that functionality.
So let's go into our code. So, I'm going
to start by creating a folder within the
components folder. And I'm going to call
this stream with a small s here. Within
stream, I'm going to create a file
called stream movie like this.jsx.
And I'm actually just going to copy in
the code for stream movie. But firstly,
we need to install a component called
React Player, which will be leveraged to
play the actual movies trailer within
the browser. So, let's go to the client
terminal window here and let's install
React Player. npm
install React
Player. Now, if we were to run this, it
would install the latest version of
React Player. And I don't want to do
that because there was some breaking
changes I found with the latest version.
So, the one that I know works is version
2.6.0.
So, to install a particular version, you
can just include the at symbol like that
and then the relevant version 2.1
6.0 zero and let's press the enter key
and it will install the appropriate
version of React Player. So, I'm going
to just copy in the code here
for this component and all it does is
play a movie, a YouTube video
which is the trailer for the relevant
movie. You can see a parameter gets
passed through with the YouTube ID.
We're reading that parameter and we're
including that as part of the URL. And
then within React Player, the component
we're importing here, it plays the
relevant trailer for our movie. Okay. So
then we want to include it within the
protected roots here within app.jsx.
So let's copy this code. I'm just going
to copy this code in here.
that will point to our stream movie
component. We need to of course import
streamov
within the app component. We can do that
with this code here. So we're importing
it there.
And we're mapping our root path here to
the relevant stream movie component
there. And you can see that it's got a
YT
ID parameter. So this is how we're
passing in the YouTube ID to the
relevant trailer to the stream movie
component. Okay.
And then we need to go into the movie
the movie component here. And you can
see offscreen I've already created the
code for this here. We've got a key here
that points to that uh makes this div
tag unique. This is of course the
MongoDB unique identifier for the
document that we're using here as the
key for this div element here. And then
within here we've got with we've
imported the link
component from React Rout and we're
wrapping the card each of the movie
cards within the link element and we're
pointing through this two attribute.
We're pointing to our stream component
and we're passing in, you can see here,
we're passing in the YouTube ID so that
the relevant trailer can be played
within React Player and that's the
component that we recently installed.
And that we're using we're using that
third party component to help us play
the movie within the browser. Okay,
excellent.
So, you can see we're closing the link
down here. That's wrapping the card
within a link. And that should be great.
We should be able to play our trailers
for our movie. So, let's make sure that
the server is running. So, let's run the
server. Go run dot and let's test to see
if we can now play the trailers for each
of the movies by clicking on the
relevant movie cards. Okay. So, let's
run the client mpm rundev like that.
and let's copy this to our clipboards.
Let's open the browser window and let's
see what we've got here. We've got a
problem straight off the bat. Okay.
Right. So, streamoviecs
failed to resolve. Okay. Super JSX.
Okay.
Ah, I didn't include the relevant uh
stylesheet.
Okay. So, I just want to include a
stylesheet within the stream folder
here. It's a scoped stylesheet. So,
let's create a new file called
stream
movie.css like that.
Okay. And the CSS code is very basic.
It's just ensuring that the movie takes
up the movie screen react player takes
up 90% of the viewport and it's going to
be responsive on smaller screens with
this particular style here. You can see
here we're setting the class name to
that style. Okay. And now I think we're
ready to test out the functionality for
playing a movie within React Player.
Okay. and let's go npm rundev like that.
Okay, let's copy that to our browser
window
and let's test our code. Let's log in as
G Jenkins. Remember, it's a protected
route because
we only want
logged in users to be able to utilize
the streaming functionality provided by
our Magic Stream web site. So pass word
one exclamation point and let's log in
as goth Jenkins and let's try run one of
our movies.
Excellent.
Look at that.
I love that stuff.
Okay.
And now we're good. We can run our
movies here.
He's the best.
And this is just
not really ready for a relationship
and no one.
Okay, so clearly it's streaming our
movies. Well, it's not
the warden. I didn't say since you ask
the outside was an honest man.
Okay. And we're now able to to simulate
the streaming of our movies just by
playing the trailers on YouTube.
blue. I am a bag.
Great. I'm really happy with that. And
there's only one thing left to do really
before we've finished, and that's brand
our web application. And I've prepared a
a little icon that I actually generated
through chat GPT
um for the magic stream branding. And
then we just need to put it in place.
So, we want to place it here within the
assets folder that I mean the the actual
icon that represents Magic Stream. So,
I'm going to
So, I've got my icon here that
represents Magic Stream. I'm just going
to copy this from the prototype code
that I created to
this folder here. So I'm just going to
go reveal in file explorer going to
assets and paste it there and it should
show up there. There it is. Magic
stream. As I say I just generated this
through artificial intelligence. And now
we're going to place it with
appropriately within the header. So
where it says brand here or somewhere
here uh there brand we want to include
an image.
here just above where it says magic
stream. Let's include the image.
Okay. And you can see we've got this
logo variable here. So we need to import
the image from our assets folder and we
can do that with this line of code here.
So we're importing this magic stream
logo
into the header component and we're just
we're branding our web page. So, and
then we want to include this also
in our registration dialogue. So, let's
go to the register component here. So,
we want to include our logo here also
within register.
Okay,
with this line of code here,
so we need to make sure we're importing
the logo and we can do that with this
line of code. And then we want to do it
again for the login.
form the login dialogue the login form
the login screen. So let's import logo
there and then
include the image
within that login form
to brand our website. Great. So let's
test what we got. MPM rundev.
Let's see if our website is now branded
appropriately.
Okay, let's go there.
Let's run that. Look at that. We got
Magic Stream there now. So, our website
is appropriately branded. And let's go
to login. There it is. Magic stream
register.
Let's register a user. And I think that
looks pretty good. And now we can make
use of our
magic stream website
and we can
taste the streaming.
Those are two boys.
Okay, that's looking good.
So, if we log out, we shouldn't be able
to stream these movies
because we're not authenticated. So,
let's try that. So, if we click on that
movie, we need to authenticate before
streaming the movie.
Whoops.
And let's log in. Brilliant.
So that's all working. Knives out. Let's
try that. Daniel Crane,
ladies and gent.
So that's all working perfectly. I'm
really happy with that. And that is
basically our
functionality already in place and we
can see the admin reviews
here.
Brilliant.
and we can run our movies.
There's one other thing I'd like to do
and that's just putting in a play button
just in the middle of the movies just
for effect. So we can do that quickly
and that'll be it. Then I think our
functionality is all in place and you
can see all the code on GitHub on the
relevant repository which has been
included below in the description. So,
we want to include on the movies on each
of our movies a play button.
So, I'm going to include a stylesheet
here,
new file,
movie.css.
And let's include the code for a
transform effect when the user hovers
the mouse pointer over the movie.
And it also includes a play icon that
will be situated over each of the movie
cards in the middle of the movie image.
Okay, let's go back to movie.jsx and
let's implement the code for that. And
in fact, we need to in order for that to
work, we need to install Font Awesome.
Okay.
Okay. So, we want to include a play icon
in the middle of each of our movie
posters. So, we're in the movie uh
component here, the code for the movie
component, and we're going to use Font
Awesome to include our play button icon
within each of the movies. So, we've
already created the CSS for that. And
we've got these classes here that we're
going to leverage. One to create a hover
effect over each of the movie cards and
the other to place this one here, play
icon overlay. We're going to overlay the
play icon within the movie card. So each
of the movies will have a play icon in
the center of where the poster is in the
movie card. Um, so let's go back to the
movie component code.
Okay. And we want to firstly install
font awesome. Okay. So we want to
install two font awesome packages. Let's
install the first one. npm install
at Fort
or some
slash
free dash solid
dash SVG
dash icons.
Press the enter key.
Okay, brilliant. That's installed that.
Great. So, we've got free-solid- SVG
icons installed. Let's clear the screen
and install the second package we want.
So npm
and install
for oops
at fort.
Awesome
for slash react
dash font or some like that.
Let's press the enter key.
Brilliant. So we've now got our font
awesome packages installed and let's
import them up here so that we can
leverage their functionality. So import
And it's font or some
icon
from open quotations
at fort awesome free solid SVG icons
like that.
Okay excellent.
And then we want to I'm just going to
duplicate that. And we want to import a
particular icon called FA
circle
play. That's the icon we want to import.
And we need to import it from
oops sorry.
This one should be react
react dash react dash
font. Awesome. Like that. And this is
the one we want for free solid SVG
icons. We're importing a particular icon
from free-solid- SVG
icons like that. And then we want to
place that within the center
of our
of each of our movie images. So to do
that, we're including a span element
below the image element here. And then
we are handling
the
actual placement of the FA circle play
icon with within this movie- CS
movie.css file here with this
CSS code here. If we go back to movie,
you can see that we are leveraging that
class, that CSS class to place the FA
circle play icon within the middle of
our images. And there's other
functionality here for hovering our
mouse pointer over the card. And you'll
see that we've created a hover effect, a
custom hover effect
over each of our movie cards when the
user hovers the user's mouth pointer
over each of the movie cards
to highlight that that particular movie
is in focus. I think that looks pretty
good.
But one thing we haven't done is we
haven't imported our movie.css file. So
we got to do that. And we can do that
with this line of code here.
Okay,
great. Let's run our code. mpm rundev.
Our serverside code is already running.
And let's copy
this URL to our browser window. Close it
down
here like this.
Okay, excellent. So, that's worked
perfectly. Our play icon
is appearing within the middle of each
of our movies.
But when we hover our mouse pointers
over the movie, that currently doesn't
seem to be working. And that is because
we haven't applied a particular class.
So let's leave that running.
And we need to go back to our code
here.
And which class are we missing? We're
missing a particular class.
Um, and it's movie card that we're
missing. And we move missing movie card
here.
See, we got our card Bootstrap icon
which turns this div tag into a card.
And we need to apply our custom CSS code
which is in the movie.css
file
to this element. So movie card and that
should include the font.
That should include the hover effect.
now within our code. Let's see if that
works.
Oops.
Go here. And there we go. And we've got
our hover effect now working. So, we've
got our play button
signifying that we can play. We can
stream the actual movie. Of course, it's
just going to play the trailer of the
movie as a substitute for that streaming
functionality.
So, let's see if that works. So, if we
play that there, it's prompting us to
authenticate because it's a protected
route. We can only stream the movies if
we are authenticated.
Genkins@hotmail.com
[Music]
password one exclamation point
log in and it should take us to the
movie and stream it for us.
I suspect
it had no suspects.
Well, that's looking pretty good. Jack
Reacher.
Okay. Good old Tom Cruz.
He knows what I did.
Nothing to lose.
Okay, let's go to the recommended
section. We got Unforgiven. Fantastic
movie.
Maybe I ain't
bad man no more. We're 11.
Oh, they wouldn't want to come looking
for
kill them cowboys.
And that's it. Basically, we've got all
of our functionality now in place. And
that is awesome. So, let's see what a
review looks like here. We can review
our movies. We can Well, if we're just a
user, we can view the reviews, but we
can't update the reviews. So, let's log
out and log in as a an administrator.
Bob Jones@hotmail.com.
Password one exclamation point. We
logged on as an administrator. This
movie wasn't too bad actually. Should be
this movie. It's not very good. Okay.
And we can actually change this review.
This movie isn't too bad. Let's just say
this movie. This movie was absolutely
fantastic.
Exclamation mark. Submit the review.
Excellent. So, it's changed the
sentiment from good to excellent. This
movie was absolutely fantastic. And we
can even watch our movie if we want.
There we go.
And that is looking really good. I'm
really happy with that.
I don't think I've ever been this hung
over.
What's on your arm?
The face.
Yeah that's
brilliant. And that's our application
done.
Let's log out.
So, we've pretty much coded our full
stack web application. We've created our
data store using MongoDB. We've created
a web API component written in Go that
runs on the Jinggonic web framework. We
have created a UI client using React so
that the users of our application can
benefit from the high-erforming user
interactive real-time UI responsiveness
provided by React.
But there's no point leaving our code
hanging about on our local machines.
It's great for testing and development,
but we are eventually going to want to
share our application with the public.
So in this part of the course we are
going to publish our application to
various cloud platforms. The components
of our application are going to reside
on three disparate platforms. So this is
an example of a distributed scalable
application. We have also employed cyber
security techniques so that our client
and server components can communicate
with one another across domains using
HTTPS. We are firstly going to publish
our MongoDB database to the Atlas
platform. We'll then seed our deployed
MongoDB database on Atlas with the data
we have stored on our local machines
through the use of MongoDB database
tools. MongoDB Atlas is MongoDB's fully
managed cloudnative database service
designed to simplify the deployment,
management, and scaling of MongoDB in
the cloud. We are going to publish our
ginonic web API component written in Go
to Render. Render is a modern cloud
hosting platform that provides
developers with a way to easily deploy
and scale web applications, APIs, static
sites, databases, and background
workers. It's often described as a
middle ground between traditional
infrastructure services like AWS
and fully abstracted platforms like
Heroku.
One of the reasons I chose render is
because it still has a free tier that we
can use. We are going to deploy our
react client code to versel. Versel is a
cloud platform optimized for front-end
development especially for building,
deploying and scaling web applications
with modern frameworks like nextjs,
react, view, spelt and more. It focuses
on speed, simplicity and a smooth
developer experience. Versell also
provides a free tier that we can use.
Great. So when we created the code for
our application, we configured our
server and clients to communicate with
one another via HTTPS
even if they reside on two separate
domains. So HTTPS ensures that the data
exchanged between client and server is
encrypted on the wire which is part of
our overall cyber security strategy. We
are using token-based authentication and
storing the tokens within HTTPON cookies
so that for example malicious JavaScript
code cannot be injected into the client
and steal the tokens. So we have
protected the client from XSS attacks,
cross-sight scripting attacks. As
discussed the tokens are like keys and
keys can be stolen and used to gain
access to protected resources. So using
HTTP only cookies is another significant
part of our overall cyber security
strategy. So we have built a robust and
secure full stack web application where
our client and server can reside on two
different domains on the internet and
communicate with each other securely.
So let's get going. We're going to start
by creating a MongoDB database in the
cloud and we're going to use the Atlas
platform for this purpose. So firstly we
need to create an account on Atlas. So
we need to navigate to https/mongodb.com
and then
products platform atlas database.
Great. And I'm going to sign in. I've
already created an account. If you
haven't yet created an account, please
create an account. Um, you can just
click this get started button here. I'm
going to sign in. I've already created
an account with my Google credentials.
So, I'm just going to press Google here.
Signing me in
automatically.
Excellent. And then it's very
straightforward. Um, and if you haven't
yet created a cluster, you can create a
free cluster. So you can use a free tier
on Atlas and create your MongoDB
database. Um, so the first step to doing
that is to hit this create button here
to create a cluster.
And I'm going to select AWS here as my
provider. And then this free option
here. And I'm going to name my cluster
Magic Stream Movies.
like that. Um going to choose America
here. North Virginia and America free.
Okay. Free forever. Your free cluster is
ideal for experimenting in a limited
sandbox. So this is really for just for
testing purposes. And we can just create
deployment now. So let's click the
create deployment button here.
Okay. Excellent.
Okay. And then access your Atlas data
using MongoDB's native drivers. For
example, Noode.js, Go, etc. And we want
Go to be able to uh access our uh
DB database. So, I'm going to click this
option here, Node.js driver. I'm going
to select go from this drop-own list
here.
Okay. And then we have our connection
string set up here. Okay. So, we need to
replace
DB password with the password for the
Gavin London Database
user. Okay. So, we can copy this to our
clipboards. We've got our connection
string. Let's copy that to our
clipboards. We're going to be using that
within our application to connect to our
deployed version of our MongoDB
database. And of course, we need to
replace
this with the actual password which we
haven't yet created. Okay, great. So,
we've created our cluster here, but we
need to include a password within our
connection string. You can actually add
users to your cluster so that you can
include that in the connection string
that you'll use from the application to
connect to the relevant database, our
magic stream movies database. And you
actually do this at the cluster level on
Atlas. So, how to do that is you go to
this database access option here
and you can either add a new database
user, but what I'm going to do is just
edit my Gavin Lon D user here and add a
password that I want here. So, I'm going
to go edit password.
Okay. And choose your own. I'm just
going to use just for simplicity. Let's
show it. Password. I'm just going to
include password one here. It doesn't
have to be uh super secure. We're just
testing it. We're testing the
deployment. So, I've just chosen a very
simple password. Password one. Like
that. Okay. And I'm going to update
user. So, the next thing I want to do is
actually seed the database with the data
that we've got stored locally in our
local version of the database here,
Magic Stream Movies. And we can actually
do that through the command line prompt
through MongoDB database tools. So you
firstly need to make sure that you've
got the relevant tools installed and you
can actually do that by navigating to
this URL www.mongodb.com
[Music]
try
download
and then data
face
hyphen tools like this. Press the enter
key.
Okay. And then it's detected. The site's
actually detected my platform. And it's
just a case of downloading the relevant
MSI file and running the installation to
make sure that you've got those tools
installed. Um, which we're going to use
so that we can deploy or that we can
seed the database that we've got running
on Atlas.
um with the data that we've got running
locally. And you can verify whether
you've got the tools installed by typing
mongod dump mongodump-
version like this.
Okay. So I do I've got mongod dump
installed. So now the first thing we
want to do is find out where
this is the path to this location here.
So we can rightclick that there. Show
connection info. Okay. And we've got
local host
2707.
So we need that connection there.
I'm just going to copy that there. So we
got a record of it there. So local host,
this is the path. This is the connection
string to our local databases, MongoDB
databases.
So
we first want to make basically we want
to create a we want to kind of export
it. We want to create a copy of our
database and we can do that using mongod
dump. So we're going to do that with
this code here.
MongoDump
MongoD dump and then dash dash URI
the d- URI option equals to and then
within quotation marks go
db colon slash slash and then we want
localhost
colon
27 1 27 7017
017 should I say. So localhost colon and
the port numbers 27017
and then we want the name of our
database which is the name of our local
database which is
magic stream movies. So include magic
stream
movies like this and then a dash dash
out option
equals to and then dot slash
dump like that. So we're creating a copy
of this data if you like from so we're
creating a copy of this local database
with this command. Press the enter key.
And now we want to update uh the
database we've just created on Atlas
with the data that we've just dumped out
here on on our local
on our local machines. Okay. So to do
that we go
restore
dash
uri
equals to
and then we need to include this
connection string here. So I'm going to
copy that and paste it there.
And we need to replace this password
with the password that we chose through
Atlas. So let's replace this here
with
pass
word one like that.
Okay. And let's see if that works.
Okay. And that looks pretty good. I
think that's worked.
37 documents restored successfully. Zero
documents failed to restore. So now we
can actually go to our server side
and see whether that has worked, whether
we've got data in our database.
Um so
view all projects
project zero. Let's go to our project
here
and we should be able to browse
collections now. So we can click the
browse collection option here and see if
our data has been restored
within the database that we've got on
running on Atlas. Look at that. That's
exactly what we wanted. So we've now got
a copy of our database that we created
locally. We've now got that data seeded
on the database that we created remotely
in the cloud on Atlas on the Atlas
platform.
Brilliant. So, we've deployed our data
pretty much. Great. So, we're going to
upload our serverside code, our Go Jonic
code to render, the render platform. But
firstly, in order to do that, we're
going to push the code that we've got
for the client and server to the
to a GitHub repository.
So if that's the first step, we need to
create a GitHub repository and then
we're going to push the code to that
GitHub repository. So let's go on to
GitHub. So this is how you would do it.
So once you're logged on to your GitHub
account to do that, you want to create a
new repository. So we go new repository.
And we're going to call this one magic
stream
movie like this. Okay. And it's
available so we can use this name magic
stream movies. And we're going to I'm
going to make this private for now but
I'll make it publicly available once the
course has been released. Okay. So let's
do that. Let's create the repository
there.
And uh the thing to note we've created
this magic we've created the repository
named magic stream movies is that if you
go to go.mod and you'll recall that we
created when we initialized our server
side code our go code we created the the
module based on this name here. So you
can see that this first part of this
path here is
is the path to the repository that we've
just created. So this path would be
similar only it would uh contain your
GitHub username here within the path.
Okay. And now we want to push our code
to GitHub. So firstly let's open an
appropriate git bash uh terminal window
like this. We're going to use git bash
to uh create our repository and push our
code files to GitHub. So first thing we
need to do is initialize our root folder
that contains the client and server code
as a repository. And to do that we can
type git init like this. So that's the
first step
and it's all turned green then. Then I
want to make sure that certain files
like for example. Eenv files are not
included in the upload. We don't want to
upload those to to GitHub. So to do
that, let's create a git ignore agit
ignore file touch
and then dot get
ignore like this.
And we've created a dot get ignore file
here. And I've prepared offscreen the co
the
settings I want in the get ignore file
here like this. Okay.
So you can see it's grayed out there and
env there because we don't want those
included.
And of of course all these node modules
we don't want that included within the
within the within the the remote
repository. So it's already Visual
Studio has already detected these
settings that I've included here. Okay.
And that looks good. And now type get
add dot. So we're adding the code files,
the relevant code files to our
repository.
Great. And then we want to
I'm just going to clear the screen. And
then we want to commit that action. So
get commit.
And this is at this point we can include
a message for this particular command
associated with this uh commit. And I'm
just going to type initial
[Music]
commit of
full
stack
magic
stream app like this.
Okay, that looks good. Let's press the
enter key.
Okay, that looks good. Excellent. And
now we want to push those files to our
new magic stream movies folder that we
just created on GitHub.
So to do that we can run these commands.
So the first one is get remote
and then add origin. So we're adding
from origin and then we can include.
So this path that we're going to include
is
based on this base path here which
includes github.com and then my GitHub
username. So obviously it'll include
your particular username um in your
particular case. So https
slash slash and then let's include we
need to include this path here.
Okay, cuz that's where we want to upload
our code to on GitHub. And then let's
press the enter key. Brilliant. That
looks good.
And then let's make sure that we're
pushing to the appropriate branch, which
is the main branch.
Branch
dash m
main. Like this. Press the enter key.
Okay, all good so far. And now we're
pushing
our local code
to GitHub.
So the remote repository.
So get push
dash u
origin main. And this is the main branch
we're pushing to.
Okay. And let's press
the enter key to do that. And hopefully
all goes well. Well, that looks good.
Excellent. So, now let's go on to GitHub
and see if that code has been
successfully pushed.
Let's refresh this.
Excellent. So, our code is there. And do
we have environment thev file there? No,
we don't. And that's what I want. We
only want the code files there. And we
don't have things like the node modules
pushed to GitHub. We don't need those
there. And we don't have thev file here.
That's brilliant. Okay, so now we're
ready to publish the server side code
from GitHub to render. So the first
thing we need to do is go to the render
platform render.com.
Excellent. And
I can go to the dashboard here
like this. But I need to sign in. So I'm
going to sign in using my GitHub
credentials. I've created my account
using my GitHub credentials. So you can
do that too. I guess it makes it easier
to publish from GitHub if you do it like
that. Okay. So the next step is we can
click we can select this option here
deploy a web service.
Okay. And then let's select our
repository. It's already detected all my
repositories in GitHub.
So, Magic Stream Movies.
Okay.
US, we want to keep it on the east of
US. So, Virginia would be ideal. That's
where our MongoDB database resides.
Okay. So, we want to include a root
directory here. So for the root
directory it is server
magic
stream
stream server. I believe it's movies.
Let's just double check that.
So if we go to our code here. Yep.
Server. Magic stream movies server. So
magic stream. Oh, magic stream movie
server. So that's not right. Magic
stream
movies
server like that. So that's from our
root from the root of the magic stream
movies repository. We want the root
directory where our commands are run to
be run from this directory because
that's where our go code resides.
And we want the free tier there. So all
our settings look good now. And I think
we're ready to deploy, you know. Ah,
very important step. We're not using the
environment variables. So, we need to
add our environment variables here.
Okay. So, we're going to do that step by
step now. So, let's go to our code here.
Let's go to thev file here, which has
been grayed out appropriately. So, let's
just start database name.
Okay. So database name
and that's magic stream movies like
this. Got that there. Now add
environment variable MongoDB URI and
this is going to be different now
to the one that we've got here the local
one. Um so let's
so let's select let's go back there
MongoDB URI. And in fact, what we want
is
this that we copied from from Atlas. It
generated this connection string for us.
So this is the connection string we
want. We don't want a local connection
string. So let's copy that and paste it
there. We got database name and we've
got the relevant connection string. Uh
and we need to change this password here
to password one.
That's what we changed the password to
for our MongoDB database on Atlas. So,
it's password one just to keep things
simple. This is obviously just for
testing purposes
um for your actual production
environment. You obviously want to
select a more secure password. Okay. And
then let's go back here.
Secret key. And we're just going to keep
it your secret key.
environment variable secret key
like that. Add another environment
variable
secret refresh key. Okay.
secret refresh key.
And we just keep going through it like
this until we've
copied all the settings,
all the environment variable settings to
the render platform like this.
Okay,
back there base prompt template. And of
course that's
prompting open AI.
Okay. And we need to
copy all of this here
right to the end.
Okay.
Copy that.
go here, paste it in there. Add another
environment variable. What's the next
one called?
Okay. And this is our open API key that
we created on the open API, the open AI
platform. So, this is the open AI API
key that we created on the open AI
platform. Let's include that there.
Let's go back here and let's
copy the relevant key here. I will
deactivate this key once I'm finished
with this course.
So, you will need to obviously generate
your own key if you want to
try out the open AI functionality.
Okay, let's go back there. Paste that in
there.
Okay, let's add another environment
variable
and recommended limit. Nearly there.
Back there. Recommended limit. And it's
just a value of five like this. And we
know we got one more environment
variable to configure here. Allow
origins. And I'm actually just going to
paste those in because we haven't yet
published the client. We'll have to come
back and um adjust this setting once
we've published the React client. And
we're going to publish the React client
to Versell, the Versell platform, which
also offers us a free tier, which is
great. allow origins and let's just I'm
just going to paste these in as
placeholder settings for now, but we'll
replace that once we've published the
React code. Okay, and that's our
environment variables done.
And then it's just a case of deploying
our web service. So, let's do that.
Let's hit the deploy web service button.
Hopefully, everything works.
H required. Oh, we haven't included that
there. Okay, we've got to include
we've got to include dot slash app like
that. Okay, so that all looks right now.
Okay, let's do it. Wow. Okay, deploy web
service.
Hopefully everything goes smoothly, but
when does it ever go smoothly?
cloning from repository and we're
getting all of the status as it
publishes our code.
We're getting all the relevant status
and I'm really hoping this goes okay.
So you can see it's building building it
uploading build now
and it gives us a blowby-blow status of
what it's doing
regarding the deploy deployment process.
Deploying build successful. That's
really great. Okay. Excellent.
Deploying. That's exciting. And
hopefully we don't have any issues.
Warning. Unable to fund file. Oh, okay.
That's just me. I wrote unable to fundv
file. I meant unable to find file, which
is actually expected. So that's not a
fatal error. So there's just a whole
bunch of warnings here. Okay.
New primary port detected. 8080
restarting deploy to update network
configuration. So you just note that you
do need to to have your port set to port
8080.
So it defaults to port 8080.
So it's best to include that as your
port. Port 8080
available at And there we have we've got
it. We've got our URL. And I'm going to
copy that URL because we're going to
need to connect our client to that URL.
So
I'm going to
copy that URL to
Notepad here. So we got all our settings
there. And we're going to need to
include that for our client side
environment variable settings cuz it
needs to connect obviously to the
various endpoints. And this is the base
URL of our endpoints on render.
Okay,
go back to render. And that's looking
good. And let's see if we can test one
of our end points
through the browser just to see.
It says it's live.
And let's test the endpoint for
slashmov. Like that. We got to endpoint.
And it returns all the data for our
movies which means we are ready to
deploy our client
on Versell.
Excellent. Okay. So now we're going to
deploy the client to Versell. Now that
we've got Magic Stream, the serverside
part of the Magic Stream application
running on render, we're going to deploy
our client code to Versel. So to do
that, we first go to the Versell
platform, the Versell website here.
Okay. And you can see I'm already um
logged in here. So you want to log in.
I've actually logged in with my GitHub
credentials. Just makes it easier to
deploy from GitHub. Okay. And then once
we've logged in to Versell,
you can create your deployment. So first
thing we need to do is go add new
and then project like this.
Okay. And we want to import from magic
stream movies. So we go we import the
magic stream movies repo. So we hit
import like that.
Project name magic stream movies. Okay.
Choose where you want to create your pro
project and give it a name. So project
name Magic Stream Movies and you want to
edit the root directory here. So we want
to edit this field here
and we want to set this to magic stream
client there. Continue. So we've
adjusted that route to where the our
client code is within the repo the React
code and it's detected that the
framework is Vit for us.
Okay. And we can just call this project
magic stream movies. That's okay.
And then we want to add our we I think
we've only got one environment variable.
So it's considerably less arduous than
when we were configuring the server side
environment variables. So let's go to
ourv file in our code. And we just want
to
add this environment variable. So vit
API base URL.
Let's include that there. And then the
value for that now is our render URL.
So let's go there. And it's this here
now that we want to include
as our environment variable because that
is the root path the base URL of where
our API endpoints are. Okay. So let's
just double check that. Yep.
Yep. So that's correct. So now we've
we're not on local host anymore. We're
on we're on render. So we include this
URL where our magic stream server side
code resides. And this is the base URL
for the relevant endpoints the magic
stream endpoints. Okay. So the output is
dist
run build.
So this is going to be our build command
will be npm run build here
directory is going to be dist. So that's
correct
dist. And I think that's it. I think
we're ready to deploy that now.
Let's try it anyway. Let's go for it.
Deploy.
Deploy mint cued.
Okay. And off it goes. It's doing its
thing.
Wow. Okay. Gavin's project, you just
deployed a new project. Sure. That was
pretty quick. Loading.
Okay. Okay, let's click on that to see
if it's error fetching movies. Okay,
well that's not great.
Um, error fetching movies.
Let's check what's going on there.
Developer tools. Ah, it's cause
and that is because we need to configure
this new URL within render. So let's see
what happens when we do that. So we got
to go back to render here and to our
allow origins here. We need to
we need to adjust this setting here to
allow the relevant origin.
So to do that, we go edit.
And I'm just going to put a comma here
and add
the HTTPS address for where our React
code is running. Let's take out that
forward slash there and let's try again.
Let's save. Rebuild.
Okay, it's rebuilding.
So, it's redeploying our application
again.
Okay,
let's try again. See if that makes it
work.
Okay, so that's been deployed now. Just
check
um
render.
Let's go forward slashmov.
So, that's working. It's returning the
expected data, all of our movie data.
Let's see what happens now when we try
and run
Magic Stream movies the client.
There we go. Working. Look at that. So,
if I
run one, if I try to play a movie. Okay,
let's authenticate. Goth
jenkins atotmail.com
password one exclamation point. Let's
see what happens.
It's logging us on.
Hopefully it plays one of our movies.
And there it is. Look at that. And our
application is working perfectly.
What's on your arm?
0 to a suspect, baby.
Oh my god.
Let's go to recommended. These are
recommended movies for GT Jenkins. Let's
play Unforgiven.
They're paying $1,000 to shoot them.
Head on.
Are you really going to kill them
cowboys?
Very cool. And that's all working
perfectly. So let's log out and log in
as Bob Jones so that we can include our
own movie reviews. Bob Jones at
hotmail.com
password one exclamation point. And we
should be an administrator now. Let's go
to recommended. So these are Bob Jones's
recommended movies. And we can actually
add reviews now. So, um, let's create a
review for The Hobbit. Okay. The movie
was awful. I really hated it. Terrible.
What a an amazing movie.
Movie. I have never seen any like this.
Okay. Submit review. And let's see what
sort of sentiment. Excellent. And that's
what you would have expected.
Um, so we got The Hobbit there is
excellent within our recommended movies.
And this is fantastic. Everything's
working. I'm really happy with this. Got
another Harry Potter there with the
Empire Strikes Back. Let's see what
Darth Vader's up to.
And
where's Darth Vader?
[Music]
A big turn in the strikes back singing.
Let's
There he is. There's Darth Vader. Okay,
cool.
R2D2.
There we go. Loads our movies. I didn't
put the spinner for the loading. We need
to
I didn't update the spinner for that,
but that's okay. We got the spinner
going for the reviews here. Okay,
excellent. Movie was absolutely
fantastic. And everything seems to be
deployed and working now. So I mean
that's amazing. We've got our serverside
code running on render. We've got our
client side code running on
Versel. And we've got our MongoDB
database running on Atlas. So it's fully
distributed and everything's talking and
all of our components are talking to
each other and playing nicely together
over the internet over HTTPS. So all the
data exchanged between the client and
server are
encrypted and therefore protected. So
nobody can steal our data and that is
excellent. I'm really really happy with
that. And let's log out. Excellent.
Thank you for joining me on this
journey. I hope you've enjoyed going
through this course as much as I enjoyed
creating it. So now you can create and
deploy a fully secured distributed web
application using Go, React, and
MongoDB.
You've also learned how to integrate
sophisticated AI functionality into your
serverside web application code by
prompting an LLM on Open AI through
Langchain Go. These are excellent skills
to have as a developer moving forward
into the future. I hope to see you soon.
Thank you and take care.
Lean how to build a complete full-stack movie streaming app with AI-powered movie recommendations. You’ll use Go with the Gin-Gonic framework on the backend, React on the frontend, and MongoDB for data storage. For the AI features, you’ll connect your Go backend to OpenAI using LangChainGo. Sign up for MongoDB Atlas: https://www.mongodb.com/ ✏️ Course developed by @GavinLon 💻 Code: https://github.com/GavinLonDigital/MagicStream 🏗️ MongoDB provided a grant to make this course possible. ⭐️ Contents ⭐️ - 0:00:00 Introduction and Overview of the Course - 0:10:14 Setup Development Environment - 0:10:19 Install MongoDB and Compass (GUI for MongoDB) - 0:14:05 Install MongoDB Shell - 0:17:54 Launch Compass, Create and Seed MagicStream MongoDB Database - 0:23:38 Use MongoDB Shell to Interact with MongoDB Database - 0:29:42 Install Go (Golang) on Dev Machine - 0:34:31 Create Basic Go Application using Command Line Interface and Notepad - 0:38:45 Install Visual Studio Code on Dev Machine - 0:42:42 Create Full-stack Project Infrastructure in VSCode - 0:44:38 Create go.mod file for Go/Gin-Gonic Web API - 0:47:32 Install Go Extension for Visual Studio Code - 0:51:00 Start Creation of Code for Go/Gin-Gonic Web API - 0:51:26 Install Gin-Gonic (Gin) Web Framework - 1:06:33 Create Movie Model and GetMovies End Point - 1:31:01 Create Movie Controller - 1:33:01 Create GetMovies Gin-Gonic Endpoint Handler Function - 1:44:20 Create Reusable MongoDB Connection Code in database Package - 2:16:46 Create GetMovie Endpoint Function Handler - 2:35:58 Create AddMovie Endpoint Function Handler - 3:02:34 Install Postman and use Postman to test Post Request to AddMovie Endpoint - 3:14:41 Create User Model - 3:44:00 Create RegisterUser Endpoint Handler Function - 4:20:25 Create Login Functionality - 5:29:52 Create Middleware Functionality to Validate Incoming Access Tokens - 5:50:23 Configure Protected and Unprotected Gin (gin-gonic) Routes - 6:06:49 Update Movie Review - Engage with openAI through LangChainGo - 6:34:22 Sign up for Account with openAI - 6:59:32 Create Movie Recommendation Service Powered by AI - 8:13:57 Go/Gin-Gonic Best Practices - Minor Code Amendments - 8:54:51 Create Frontend Code Using React - 9:02:57 Create UI for Home Page - 9:59:05 Create Header Component - 10:24:43 Setup Routes using react-router-dom - 10:24:58 Create User Registration UI - 10:56:59 Create Login UI - 11:10:30 Create Auth Provider and Auth Context Functionality - 11:39:31 Create UI for Movie Recommendation Service - 12:05:37 Create UI for Updating Admin Movie Reviews - 12:38:29 Amend Code so that Access Token is Stored in http-only cookies (prevent XSS attacks) - 13:41:01 Using react-player to Play Movies in Browser - 14:04:27 Deploy full stack Web App to the Cloud - 14:08:13 Deploy MongoDB to Atlas and Seed Data from Local MongoDB - 14:18:42 Deploy Go/Gin-Gonic Web API to Render - 14:35:31 Deploy React Client Code to Vercel - 14:44:48 Outro