The Ultimate FastAPI + React Full Stack Project (Deploy This and You’re Set)
By Tech With Tim
Summary
## Key takeaways - **AI Generates Branching Adventure Stories**: We're going to use AI, so an LLM to generate this story for us and then be able to play it interactively on the website. It creates this interactive kind of branching choose your own adventure game that we can play through directly on the site. [01:20], [01:35] - **Backend First for Heavy Lifting**: What we're going to do for this particular project is we're going to code out the backend first. We're going to test the backend and make sure that it works because the backend is doing a lot of heavy lifting for this project. [04:26], [04:31] - **UV Beats PIP for Dependencies**: UV is just a better version of PIP. UV is automatically going to handle what's referred to as a virtual environment so it will keep these dependencies kind of private to this project and isolated from the rest of our system. [05:58], [06:36] - **FastAPI Schemas Auto-Validate Data**: Schemas are going to define the data that comes into our API and that comes out of our API. This is one of the huge advantages of FastAPI is that it will automatically document the API for us as we write it based on us using the correct typing system. [09:31], [09:42] - **Background Jobs Prevent API Hanging**: The idea is frontend essentially submits a job and then the backend here returns the job so we're able to view the job. Then the frontend will say ask if job is done, the backend will report the status of the job and then if job is done backend can send story. [43:39], [43:57] - **Choreo Deploys Full Stack Free**: They are free. You do not need to pay for the platform in order to deploy this. Coro gives us an automatic staging environment as well as a production environment so that we can test what we're doing before we promote our build to production. [03:16], [03:05]
Topics Covered
- Full Video
Full Transcript
In this video, you'll learn how to build a full stack application using fast API in Python and JavaScript and React. Now,
I'll cover databases, AI integrations, backend and front-end communication, designing the API, and various best practices. I'll even show you a free way
practices. I'll even show you a free way to deploy this project at the end so that you can share it with anyone that you want. Now, while I will explain
you want. Now, while I will explain every line that I write in this video, this tutorial is designed for intermediates and those looking to learn how to build a full stack application and connect the different components
together. It'll be a good idea to have
together. It'll be a good idea to have some experience with both Python and JavaScript and ideally some knowledge of both React and Fast API, but obviously you don't need to be an expert. Now,
lastly, all of the code that I write in this video will be available from the link in the description. Feel free to pause the video, adjust the playback speed, and reference that code from the description in case you're getting lost.
With that said, let's hop over to the computer and dive into this tutorial.
All right, so I'm on the computer now, and I want to give you a quick demo of what it is that we're going to build.
Now, what we're going to make here is an interactive choose your own adventure game. Now, choose your own adventure
game. Now, choose your own adventure game is one of my favorite games. It's
what I always played as a kid. And if
you ever remember those books where you essentially would flip to different pages based on the decisions that you make, that's kind of what we're doing here. So it'll make sense when I show
here. So it'll make sense when I show you the example, but essentially we're going to use AI, so an LLM to generate this story for us and then be able to play it interactively on the website. So
we can put some kind of theme in here.
So for example, it gives us an idea like pirates. I can press generate story. We
pirates. I can press generate story. We
just wait a second and then what will happen is it will create this interactive kind of branching choose your own adventure game that we can play through directly on the site. Okay, so
it just generated the story. You can see that we have a title and then we have some text. I won't read through
some text. I won't read through everything, but you'll notice that it gives us some options. So it says we can either sail here or we can explore for clues. So what's going to happen is
clues. So what's going to happen is you're going to go down these different paths based on the decisions that you're making. one path will lead to victory or
making. one path will lead to victory or like you're kind of ending the story successfully and a bunch of other ones will lead to you kind of failing or the ending, right? So, if I press on this,
ending, right? So, if I press on this, okay, it brings me to another path. So,
as your ship approaches the aisle, uh, you know, what do you want to do, right?
Do you want to battle through the storm or turn back? Okay, we turn back and it says our adventure has come to an end because, well, we decided to go back, right? So, we could restart. We can go
right? So, we could restart. We can go here. Maybe we battle through the storm.
here. Maybe we battle through the storm.
And then this way the uh story kind of keeps going and we have all of these different branches and options and choice and it's all generated by AI which I think is really really cool. And
also once the story is generated then it kind of has a unique ID and you can share it with someone. So like here I have you know 21 plus stories that I've already made when I was testing for the video and you can see here's another
one. So you could just take this URL
one. So you could just take this URL especially once we deploy this send this over to one of your friends and they can play through the same story and see if they can get to the end. So, that's
basically what we're going to be building. I know it seems simple, but
building. I know it seems simple, but this does require some kind of complex backend stuff, which we're going to talk about, as well as some front-end coding, and then connecting the components together. So, what I'm going to do now
together. So, what I'm going to do now is hop over into my code editor. I'm
going to walk you through all of the steps, but I do quickly want to let you know that the platform we'll use to deploy this later is Coro. Now, I've
teamed up with them for this video.
Again, they are free. You do not need to pay for the platform in order to deploy this. And I actually made another video
this. And I actually made another video about a year ago where I showed you guys how to make a Django in React full stack application where we used Coro and you guys really enjoyed that. So that's why we decided to team up again. Anyways,
let's get over to the code editor. Let's
start writing some code and building this project. All right, so I'm on the
this project. All right, so I'm on the computer now and we're going to start coding out this project. Now, you'll
notice that I'm inside of PyCharm.
That's the editor that I'm using. And
this is typically what I recommend for larger Python projects, especially when you're using the frameworks like fast API where there's actually native support built into this editor for those
types of frameworks. Now, I do have a long-term partnership with PyCharm where I'm able to give you an extended free trial of their Pro subscription. So, if
you want to try it out, you can do that by clicking the link in the description.
But obviously, you're free to use any editor that you want. And you'll see in this video why I prefer PyCharm because of the various features that it has.
Regardless, let's get into this. What
I've done here is in PyCharm, I've just opened up a new folder. So, just kind of start a new project in whatever editor you want to use. Open up a new folder and we're going to go from there. Now,
within this folder, I'm going to make a new directory and I'm just going to call this backend. What we're going to do for
this backend. What we're going to do for this particular project is we're going to code out the backend first. We're
going to test the back end and make sure that it works. And then once we have that, it's relatively simple for us to build a user interface around that using React. Now, for other projects, I
React. Now, for other projects, I sometimes go in a different order, but for this one, because the backend is doing a lot of heavy lifting for this project, we're going to code this out first. So, even if you're someone who
first. So, even if you're someone who doesn't like React, for example, you can still follow along with probably the first hour of this tutorial, get the backend spun up, and then you can do whatever you want for building your own custom front end that connects to the
API that we're going to build. Speaking
of which, I want to give you a quick definition. API stands for application
definition. API stands for application programming interface. And this is what
programming interface. And this is what we're going to build using fast API.
What we'll do is we'll expose various endpoints. So things like
endpoints. So things like slashgeneratetory slashgettory slashgettory result etc. And then our front end or really anything will be able to use this
various endpoints to either request data from our backend or to kind of send data to our backend to generate a new story.
This will make more sense as we go through, but that's what the backend's role is going to be here to be a secure place where we can handle all of the story generation and then returning the
story to our clients or to our front end. Okay. So, from our back end, what
end. Okay. So, from our back end, what we're going to do is we're going to open up our terminal here. We're going to cd into the backend directory. So, it would help if I spell that correctly, okay?
Because this is created. And we're going to use a tool called UV to initialize a Python project. Now, UV is just a better
Python project. Now, UV is just a better version of PIP. I'm going to put a video on screen right here that talks about everything you need to know about UV in case you don't know what it is, but you can follow along with me here after you
install the UV tool. So, if you don't already have it, just go to the web and just type UV install or something. Find
the installation docs. Very, very simple to install it. Install it on your system. And then once you have it, you
system. And then once you have it, you can run the exact commands that I'm going to do here. So, the first command is going to be uvnit and then dot. This
is going to create a new UV project inside of your backend folder. So you'll
see some files get created here. Then
all we need to do is just install our Python dependencies. And UV is
Python dependencies. And UV is automatically going to handle what's referred to as a virtual environment. So
it will keep these dependencies kind of private to this project and isolated from the rest of our system. So we're
going to start by typing UVAD and then fast API and then in square brackets we're going to put all. Okay.
We're then going to install lang chain and then lang chain-openai because we're going to use openai to um how do I save this here to generate the
stories for us. Okay. So we're going to use an llm call which I'll talk about later. Then we're going to install
later. Then we're going to install python.env.
python.env.
We're going to install SQL and then alchemy like that and u okay we have one more. This is going to be sigh. So psy
more. This is going to be sigh. So psy
and then comp cop g2-binary.
This is going to allow us to connect to a postgress SQL database which we'll need to do later when we deploy this application. Okay, so these are all the
application. Okay, so these are all the dependencies that we need. So let's
install all of them. Just hit enter. It
will create the virtual environment for us and then install and it happens very quickly too whereas pip usually takes a little bit longer. Okay, so now that all of these have been installed, we can close the terminal up and we can start
kind of templating our backend project.
Now, what I want to do is I want to create a skeleton of all of the files and directories that we're going to need so that it kind of gives us that project structure to begin. And then what I can do is start coding out individual files
with you and explaining what it is that we're building. So, inside of the root
we're building. So, inside of the root of our backend directory here, we're going to make a new Python file. And
this is going to be underscore_nit_.
Okay, so two underscores here at the beginning and the end. This is going to make this a Python package. Uh what is it saying here? Do you want to add the following? Okay, we can just press
following? Okay, we can just press cancel for right now. Uh so that we're able to import things using a convention that you'll see later on. Okay, so we have a nit.py. Then we're going to make another folder or sorry another file and
this is going to be called env. Now this
file is going to store some credentials.
It's going to be private. is just
something that we're going to keep on our local computer when we're doing development and it's going to allow us to store things like our API keys for interacting with OpenAI or doing LLM calls. Then we're going to make a few
calls. Then we're going to make a few folders. So we're going to go new
folders. So we're going to go new directory. The first directory we're
directory. The first directory we're going to have is core. This is for the core operations for our application like generating the story. Then we're going to have another new folder and this is
going to be DB for our database operations. Then another folder. This is
operations. Then another folder. This is
going to be models. This is for our database models. We're then going to
database models. We're then going to have new and another new folder. This is
going to be routers. This is for actually handling all of the API routes that we're going to have. So like
slashcreate story slash get job slash whatever. You'll you'll see the ones
whatever. You'll you'll see the ones we're going to have in a second. And
then we're going to have schemas. Okay.
And schemas are going to define the data that comes into our API and that comes out of our API. This is one of the huge advantages of fast API is that it will automatically document the API for us as
we write it based on us using the correct typing system which I'm going to show you here in Python. So all of the stuff while it may seem complex what I'm doing here is writing the API in the best possible practice and that's really
what I want to show you in this video so you understand how this can scale up to be some larger software. Okay, so we have the different folders. Now let's go ahead and create the files. Again, some
of them will be a little bit confusing right now because we haven't coded anything out yet. But once we have everything, we kind of have a skeleton of the project and then the whole kind of vision will come together. So, we're
going to go inside of core and we're going to make a new Python file. And
this is going to be underscore_nit_.
This file is going to make this folder something called a Python package, which makes it easier for us to import the different files and functions that we define within this folder. So, whenever
you see a nit.py, Pi, it just means that we're making this a package. Okay? And I
have entire videos that go over that in case you want to check those out on my channel, like packages and modules in Python. Then we're going to have a
Python. Then we're going to have a config.py
config.py file. Okay, so we're going to make
file. Okay, so we're going to make config. All right, we're going to make
config. All right, we're going to make another Python file, and this is going to be models. All right, I'm just going to ask this to not ask me again because we don't need to do this right now. I'm
going to go another new Python file.
This is going to be the prompts. So this
is going to have our kind of OpenAI prompts for communicating with our LLM.
And then we're going to have another one here which is going to be the story generator. Now whenever you're naming
generator. Now whenever you're naming Python files, make sure that if you want to have like a space in the name that you use an underscore rather than like having a second capital letter or something. This is the convention in
something. This is the convention in Python. It's called snake case. Okay. So
Python. It's called snake case. Okay. So
we have init config models prompts story generator. Okay. And we're going to
generator. Okay. And we're going to close all of these so that we don't get confused. All right. Now, we're going to
confused. All right. Now, we're going to go to database. We're going to do the same thing where we make our underscore_nit file to make this a Python package. And we're going to make
Python package. And we're going to make another file. And we're just going to
another file. And we're just going to call this database.
py. Okay. And I should have pressed Python file, but that's fine. This will
still make it a Python. Okay. So, those
two files inside of DB. Then for models, we're going to have a new file again.
this underscore_nit.
py.
Then inside of models, we're also going to have one for our story. So, anytime
that you have different models, I suggest that you put them inside of different files. So, we're going to have
different files. So, we're going to have essentially, okay, how do I explain this without getting into too much depth?
We're going to have these database models or a definition of the type of data that we want to store in our database. It's called a model. That's
database. It's called a model. That's
what we call it here inside of fast API.
So for our story model, we're going to have a story file. And then we're going to have another new file called job. And
this is going to represent the status of creating a story. So something will be a job. And then as soon as the job is
job. And then as soon as the job is finished, the story will have completed generation. And then we'll be able to
generation. And then we'll be able to grab the story. So that's why we have these two files here. One for the story and one for the job. Okay. Then we're
going to go to routers. We're going to make another new Python file. And inside
of here, we're going to have the same thing. We're going to have job. Okay. So
thing. We're going to have job. Okay. So
this is going to be all the routes for handling our jobs. We're going to have another Python file. This is going to be story. So all the routes for handling
story. So all the routes for handling our stories. And then we're going to
our stories. And then we're going to have an init.py file to make this a module. Okay. And it's fine to have
module. Okay. And it's fine to have duplicated file names as long as they're in different directories. So in this case like the models and the routers, they have the same file name, but because they're in different directories, we can distinguish what
they mean. Now continuing from there,
they mean. Now continuing from there, we're going to go to schemas. We're
going to make a new Python file and we're going to say underscore_nit to make it a module. And similarly to before, we're going to have one for our story schemas. Uh this needs to be a
story schemas. Uh this needs to be a Python file. So let's refactor that to
Python file. So let's refactor that to py. Okay, that's fine. Uh actually,
py. Okay, that's fine. Uh actually,
sorry, not refactor, rename. Okay, so
that is now story.py. And then we'll make another new Python file. And this
one is going to be job. Okay, as I said before, the way that the schemas work is that they're going to define the type of data that our API will be returning and the type of data that our API will
expect because really an API is just a way for us to communicate data between different services. So something like
different services. So something like the back end and the front end are communicating. In order to communicate
communicating. In order to communicate properly with best practice, we should define the types of data that we expect to come in and the types of data that we expect to come out. It makes our life a lot easier. And then if other developers
lot easier. And then if other developers were to come into this project, they would be able to view these schemas which we'll write later on and understand what our application is actually meaning to do. Okay, so now we
have the whole kind of project structure set up and we're going to start by just writing our basic fast API definition for kind of creating the fast API application. So we're going to go inside
application. So we're going to go inside of this main.py file and for now we can pretty much just delete everything that's inside of here. So, we're going
to say from fast API import fast API.
We're going to say from fast API dot middleware.cores
middleware.cores import the corores middleware. Okay, like that. Now, you're
middleware. Okay, like that. Now, you're
going to notice that it's telling me I have an invalid Python interpreter.
Depending on what editor you're using here, you may need to configure the interpreter to get your autocomplete working properly. So for me, I just want
working properly. So for me, I just want to select the correct interpreter for my project. So I'm going to press on
project. So I'm going to press on configure Python interpreter here. And
it's going to show me a few different options of where I can pick the interpreter. It's not actually picking
interpreter. It's not actually picking up the one that I have in my particular project. So what I can do is press on
project. So what I can do is press on add new interpreter and add local interpreter. Okay. Again, this is only
interpreter. Okay. Again, this is only if you're working inside of PyCharm.
Now, rather than creating a new one, I'm going to select an existing one. And for
the path, I'm just going to press on this button right here. And I'm going to locate the path for my interpreter. Now,
the path for my interpreter is going to be in the folder that I created. In this
case, I called this choose your own adventure game. You can see I have a lot
adventure game. You can see I have a lot of projects here. So, I'm going to go choose your own adventure game. I'm
going to go into the backend folder. I'm
going to go into the venv folder. This
is what will be created by UV when you do that UVIT command. And then inside of here, I'm going to go to scripts and I'm going to find the script called Python.
So, python.exe. This is the interpreter location. I'm going to press on okay and
location. I'm going to press on okay and okay. And when I do that, you can see
okay. And when I do that, you can see that now we actually get the autocomplete and that error goes away.
So you just need to select the interpreter from inside of here. Okay.
So now from there, what we're going to do is we are going to start defining our application. So we're going to say our
application. So we're going to say our app is equal to fast API. And when we define a fast API application, we can specify some details. So we can give our
API a title. So we can say the title is something like choose your own adventure game API. Okay. If we want we
can give this a description. So I don't know what we want to have as the description. Let's do a comma you know
description. Let's do a comma you know API to generate cool stories or something. And then we can specify a version for the API. So if
we did want to version this, which is typically best practice, uh we can do that. So I'm going to go with 0.1.0. And
that. So I'm going to go with 0.1.0. And
then if we are in production, we could kind of update this version every time we make a change. And then we could say docs URL is going to be equal to /doccks. And the redocock URL is going
/doccks. And the redocock URL is going to be equal to /redoc. The reason why we have this is all of our fast API APIs will automatically come with authentication or sorry not
authentication documentation which we'll be able to view from our web browser. So
I'm going to show you what that looks like in a second. Okay. So after this, we're going to add our middleware. So
we're going to say app.add_m
middleware and we're going to add the corores middleware. Okay, what this is
corores middleware. Okay, what this is going to allow us to do is have our API be used from a different origin. Now
whenever you're running these kind of web-based services, the origin refers to essentially the URL or the domain that you are running these services on. So
our front end, for example, is going to be running on like localhost port 5173 or something along those lines. But our
backend, which is this API that we're writing here, might be running on port 8000. So by default, our backend has
8000. So by default, our backend has some security built in, they won't allow it to communicate with anything that's not on the same origin. But by adding this middleware here called corores,
which stands for cross origin resource sharing, we're allowed to enable certain origins, so certain URLs to interact with our backend. So this is pretty much just a security thing. So for now, we're
going to go allow origins, and we're just going to make this star. We're
going to change this later on. We're
going to go allow credentials equal to true. We're going to go with allow
true. We're going to go with allow methods. Okay, so allow methods equal to
methods. Okay, so allow methods equal to asterisk and then allow headers.
Okay, equal to asterisk as well. Again,
feel free to pause here to catch up because I am going to use some autocomplete because it makes the video a little bit faster for me to actually get through rather than spending, you know, five hours building out the project. Anyways, point is what we're
project. Anyways, point is what we're doing here is we're saying okay, we're going to allow someone to send credentials to our back end. We're going
to allow them to use any API type method. So there's various methods like
method. So there's various methods like get, post, put, which specify different operations that you want to do on the API. For example, get is retrieving
API. For example, get is retrieving data, post is like making data, put would be updating data. So we're going to say, yeah, you can do any of those operations. And then headers is like
operations. And then headers is like additional information that you can send with the request. So, we're going to allow all of the headers. Now, typically
in production, you would want to make this a little bit more limited, but in our case, we're just going to allow everything so that we don't have any errors when we're writing this out.
Okay. Then, below here, we're going to say if_ame is equal to_ain then we are going to say import uicorn.
Okay, I'm just going to write this and then I'll explain what it is. And then
I'm going to say uicorn.run.
and we're going to run our main colon app. Okay, we're going to say host is
app. Okay, we're going to say host is equal to 0.0.0.0 and our port is equal to 8,000 and we're going to say reload is equal to true which means anytime we save the server
will reload so we don't need to keep rerunning it. Now, Uicorn is what's
rerunning it. Now, Uicorn is what's known as a web server and it essentially allows us to serve our fast API application. So out of the box, we can't
application. So out of the box, we can't run a fast API API unless we have some web server that is connected to. So we
use uicorn to just kind of run the fast API application. Now if you're wondering
API application. Now if you're wondering about this line here, this is just a standard Python practice. That purely
means only execute what's inside of this if statement. If we directly execute
if statement. If we directly execute this Python file, so if you were to import something from this file, then this stuff wouldn't run. But if you run this Python file, this stuff will run.
Okay, so that's our main app. We can run this to test it out if we want. To do
that, we can open up our terminal. We
can cd into the back end and we can type uv run and then the name of our file which is main.py. We should see that it runs. Gives us application startup
runs. Gives us application startup complete. And then it shows us where
complete. And then it shows us where this is actually hosted. So if we were to open this up in our browser, okay, it's saying it can't be reached. Uh I
guess cuz there's nothing really there.
And we go to something like /redoc. Um
it should show up. And actually, sorry.
The reason why this isn't working is because we need to change this 0000 to be localhost port 8000. And when we do that, we change this to localhost. Now,
the red do is appearing. Okay. So, we
can go to redoc or I think we called it docs. And you'll be able to see the
docs. And you'll be able to see the documentation for your API once we actually have any routes in there. Right
now, we don't have anything, so you're not seeing anything. Okay. All right.
So, we can leave this running if we want. And now, anytime we make a change,
want. And now, anytime we make a change, it should automatically update. However,
sometimes we will need to restart it. If
we do something like a database operation, um it will require a restart.
Okay, so what we're going to do now is we're going to start setting up some configuration. So I want to go into my
configuration. So I want to go into my environment variable file and I want to specify some of the variables that we're going to need to use. So in all capitals here, I'm going to specify database,
okay, underscore URL. And this is going to be equal to and we're just going to call this SQL. Okay? And then light colon and then three slashes and then
dot slash and then this is just going to be database db. Okay. What this means is we're just going to run this database locally on our own computer using something called SQLite. Later on we
will change this to use a production database or at least one that's hosted and deployed, but for now we'll use this. Then we're going to have an API
this. Then we're going to have an API prefix. Okay. Okay, so we're going to
prefix. Okay. Okay, so we're going to have slash our API prefix is equal to slash API. This is what we're going to
slash API. This is what we're going to have at the beginning of all of our API requests or routes. We'll then have debug is equal to true. This is
something we may need to use later when we start doing some deployments. And
then we're going to have two last variables. We're going to have allowed
variables. We're going to have allowed origins is equal to https colon slash localhost colon 3000. Okay. And then I'm
going to put a comma and I'm going to paste this again. And I'm going to change this to port 5173.
Now allowed origins here is important because anything that you specify in this commaepparated list here is what the API will be able to receive requests from. If for some reason your origin or
from. If for some reason your origin or your front end is not working, you're going to have to check the allowed origins to make sure that you've specified the correct URL. Again, this
is a security thing. And then after here, we're going to have open AI API_key.
And we'll specify the credentials for our OpenAI key later on in the video when we start using the LLM to generate our stories. Okay, so that's it for the
our stories. Okay, so that's it for the environment variable file, but I want to show you how we load in these environment variables into our project.
Now, the way that we do that is we're going to go to core and we're going to go to config. py. Now, again, this is standard practice. This is why I'm
standard practice. This is why I'm showing it to you. And this allows us to take all of these environment variables and map them into a Python object that we can then use and reference throughout
our code. So what we're going to do is
our code. So what we're going to do is we're going to say from typing import list we're going to say from paidantic
settings and then we're going to import the base settings and then we're going to say from piantic import the field if we can spell this correctly validator.
Okay. Now, Pyantic is a library that allows us to do some advanced Python kind of type handling and to again map data that's not a Python object into a Python object. So, we're going to make a
Python object. So, we're going to make a class here called settings and it's going to inherit from the base settings from Pantic. Now, inside of here, what
from Pantic. Now, inside of here, what we're going to do is essentially just specify all of the variables that are inside of this environment variable file and then what their corresponding type should be. And then what will happen is
should be. And then what will happen is automatically these settings will be loaded in for us um from this environment variable and we'll be able to use it inside of our code. So we're
going to say API prefix is a string and we're going to say by default this is equal to / API. You don't need to set a default option but I'm going to here.
We're going to say debug in all capitals is a boolean. Okay. And by default this is equal to false. We're then going to say the database, if we spell this
correctly, underscore URL is a string.
And for this one, we're not going to put any default options. We're then going to say allowed origins is a string. Okay? And by
default, this is equal to an empty string. And then we're going to say the
string. And then we're going to say the OpenAI API_key is also a string. Okay? And we're going to do something else in a second, which I'll show you. But you need to make sure
that whatever you specify here maps with what you have in your environment variable file. If you add something else
variable file. If you add something else here like you know new var or something the program actually won't load correctly because the config will tell you that you haven't specified that variable in the configuration. So you
need to make sure that the environment variable file and the config match.
Okay. Then what we're going to do here is we're going to add a field validator which is a little bit more complicated on the allowed origins. So we're going
to say at field validator and then this is going to be allowed origins here. Okay, let's put the
origins here. Okay, let's put the underscore and we're going to define a function. We're going to say define
function. We're going to say define parse allowed origins. We're going to take in our class. We're going to take in v which stands for value. We're going
to say that's a string and we're going to return a list of type string. Okay.
Now what I'm doing is I essentially I'm going to look at allowed origins which is going to be a string right that contains this comma and I'm going to convert it into a Python list because environment variable files do not
support lists like this. I can't do that. I need to take this string and
that. I need to take this string and convert it to a list because that's the type that we want our allowed origins to be in. So what I will do is I will
be in. So what I will do is I will return v dotsplit. I will split this at a comma
v dotsplit. I will split this at a comma if some value exists. Otherwise, I will return an empty list. Now, what this is going to do is it's just going to replace whatever we have in allowed
origins here with this field validated allowed origins, which will either be an empty list or a list containing the allowed origins. So, we're separated by
allowed origins. So, we're separated by commas. Now, lastly, we're going to have
commas. Now, lastly, we're going to have class config. Okay, this is inside of the
config. Okay, this is inside of the settings class. Make sure you put it
settings class. Make sure you put it inside. We're going to say the env file
inside. We're going to say the env file is equal to env. The env_file
encoding is equal to UTF-8.
And we're going to say case sensitive is equal to true. Okay. Now, this is just setting the configuration so that Python knows how to correctly load our environment variable file. If we called
it something else, then we would change the name here, but we called it env. All
right. And then lastly, we're just going to instantiate this by saying settings is equal to settings like that. When we
create this class, it will automatically load the environment variable file, do our field validation, and give us all the values inside of here. Now, in order to use something like this, we're going
to go back to main. Okay. From here,
we're going to import the settings. So,
we're going to say from core dotconfig import the settings in lowercase and then we're going to replace our allowed
origins here with settings dot allowed origins. Okay, so we're able to kind of
origins. Okay, so we're able to kind of grab that variable directly from our settings and use that here. Okay, very
good. So, now that core or we've done the configuration, we're going to start handling our database models. So, we're
going to go to models. And the reason why I like to do this at this stage is because it's important to understand the data that your application is going to use. Typically, all applications are
use. Typically, all applications are doing are really just flowing data through some type of system. So really,
you want to start by understanding what data it is you're actually going to have and the relationships between those pieces of data. Once you understand that, the system becomes significantly easier for you to build. So I want to
start with story. py and I want to define the information that we need to store for a story and how this is going to work. Now remember that a story is
to work. Now remember that a story is going to look something like this. We're
going to have maybe a story name. Okay,
so some name for the story. It's going
to have some kind of theme and then it's going to have essentially a first option or like a first kind of choice. So first
option and then for each option there's going to be children, right? Right? And
the children are going to be kind of like go left, you know, or go right.
Like these are the possible choices we could have. So the first option might be
could have. So the first option might be something like, you know, you come across a path. Do you want to go left or right? Now the children are if you
right? Now the children are if you choose to go left or you choose to go right. Now if you choose to go left,
right. Now if you choose to go left, then you have kind of this new node here where you have I don't know some text and then you have additional options, right? More options that you can pick
right? More options that you can pick from. So we have this kind of branching
from. So we have this kind of branching structure that gives us something similar to a binary tree if you've ever seen that before. So pretty much the way it works is you have something like X, right? And I'm going to try to kind of
right? And I'm going to try to kind of draw this out for you. You have some like option here which is Y. And then
you have some option on this side which is Z. And then from here if you go down
is Z. And then from here if you go down from Y again like you have some option here. Okay, maybe this option like leads
here. Okay, maybe this option like leads to the end of the path. and then you have some other option on the right side and you get this kind of like branching pattern. Okay, so I hope that makes
pattern. Okay, so I hope that makes sense but I'm going to show you how we represent that in this data model that we're about to define. So what we're going to do is we're going to import SQL Alchemy. So we're going to say from SQL
Alchemy. So we're going to say from SQL Alchemy and funny enough it knows literally everything that I want to import which is kind of crazy. Uh well
actually there's a few that are wrong.
So let's just type it out. We're going
to import column, integer, string, datetime,
boolean, foreign key, and JSON. Okay,
let's make this a bit smaller so that you guys can read the whole line. Don't
worry about it being gray. It's just cuz we haven't used these yet, so it's grayed out. Now, SQL Alchemy is what's
grayed out. Now, SQL Alchemy is what's known as an OM, which is an object relational mapping, similar to how we mapped the uh settings from our environment variable into this kind of
Python class. Fast API has these modules
Python class. Fast API has these modules like SQL Alchemy that allow us to kind of map data into these Python classes so that we don't need to write SQL code.
SQL is structured query language. It's
typically how you interact with the database. So rather than us having to
database. So rather than us having to write all of this SQL code and understand it, we can use standard Python operations and this library will handle all of the complexity of converting that into SQL and working
with our database. Okay, so we're going to say from SQL alchemy again. Did I
spell that correctly? I don't think I did. We're going to say SQL. We're going
did. We're going to say SQL. We're going
to import funk. Okay. And then we're going to say from SQL alchemy and we don't need that. We're going to say import the relationship. Okay, which
allows us to make a relationship. And
that should be good for now. We're then
going to say class story. And actually,
I'm just going to put a pass right now because I need to write something inside of my database file first before I can actually start writing out all these data models. So, my apologies, guys.
data models. So, my apologies, guys.
We're going to leave this. We're going
to go to DB. We're going to go to database.py. And I need to define the
database.py. And I need to define the kind of database initialization so that I'm able to then create these database models. Okay. So up here we're going to
models. Okay. So up here we're going to say from SQL alchemy import create engine. Create engine will create this
engine. Create engine will create this engine that kind of wraps around the database that we're interacting with.
Then the next line is going to be this from SQL alchemy.
import the session maker which makes a session that we can connect to to interact with our database engine. We're
then going to say from SQL Alchemy so it knows exactly what I want because it's very standard.exd.declarative
very standard.exd.declarative
import declarative base. Now this is a base class that all of the data models need to inherit from so that we know which data models we have in our database. We're then going to say from
database. We're then going to say from core settings and we're going to import these settings so we can use that here.
Okay. Uh, so we're going to say engine like this. And sorry, it's not core.
like this. And sorry, it's not core.
settings, it's core.config.
So we're going to say engine is equal to create engine. And then we're going to
create engine. And then we're going to say settings dot database URL for the uh kind of database URL that we want to create the engine around. We then are
going to make this session local which is equal to session maker. Okay, I'm
just going to do the autocomplete. So
we're going to say auto commit is equal to false. Auto flush is equal to false
to false. Auto flush is equal to false and bind this to our engine. Okay. So
we're just going to bind the session to the engine that we've made. Next, we're
going to make the declarative base. So
we're going to say base is equal to declarative base like that. Again, we
need this uh so that all of our models can inherit from this base class, which essentially gives it all of the properties to work with our kind of SQL OM here. All right. Then we're going to
OM here. All right. Then we're going to write a very simple function and we're going to call this get database. What
this is going to do is it's going to say db is equal to session local. So it's
going to instantiate this new session.
We're then going to say try and we're going to yield the database and we're going to accept db.close or sorry not accept we're going to put finally which essentially means when the program is
shutting down we're going to make sure that we close the connection to the database. Now if you're not familiar
database. Now if you're not familiar with the yield keyword don't worry too much about it. All this function does is give us access to a database session. Uh
and ensures that we don't have multiple sessions open at one time. All right.
We're then going to have another function. We're going to call this
function. We're going to call this create tables. All right. And inside of
create tables. All right. And inside of here, we're going to say base metadata.create all. And then we're
metadata.create all. And then we're going to say bind is equal to engine. Now, this is because when we first create this application, we need to create all of the tables
based on uh the data models that we've defined. So, we're going to call this
defined. So, we're going to call this function when we spin up the application uh to make sure that we create those tables in the database. Okay. And don't
worry about this. I'm not sure why uh it's kind of giving me these errors.
We'll look at that later if for some reason the import doesn't work. Okay.
So, now we can go back to story and we're going to go here and we're going to say from db database import and we're going to
import that base and use that. Okay.
Okay. Now, for some reason, this still is giving us this uh kind of unresolved import error. So, I'm just going to
import error. So, I'm just going to change this to say from backend.db.database.
backend.db.database.
And I'm hoping that's going to work.
Yes. Okay, that did fix it. And I'm just going to go back here to where we had core and I'm going to say from backend.core.config.
backend.core.config.
And I hope that's going to fix the problem for us. We might need to change these later, but for now, we'll leave it at this because it looks like PyCharm is resolving the import correctly. Okay, so
now, sorry, we want to go back to story and we're just going to inherit from this base class. Now, effectively what we're going to do is just define all of the fields that we want to have on our story. So, let's go through this. The
story. So, let's go through this. The
first thing we're going to do is specify the table name. So, we're going to have table name uh so underscore table name is equal to and then we're just going to call this
stories. This will be the name that we
stories. This will be the name that we store in SQL. Then we're going to have an ID. The ID is going to be a column.
an ID. The ID is going to be a column.
Okay, so we're going to say the type of this column is an integer. We need to have a unique ID for all of our stories.
So that's what we're representing here.
We're going to say the primary key is equal to true and we're going to say index is equal to true. Now a primary key means that this is a unique value.
So every single story has to have a unique identifier and you can use that as the primary key. Now index means that we'll be able to look up these stories very quickly. So we are just indexing
very quickly. So we are just indexing all of the stories so that we can find them pretty much instantly. Okay. Now
what we're doing is we're defining all of these columns. A column is just a piece of data that we will store for any individual story. Okay? And this aligns
individual story. Okay? And this aligns with how tables are created in SQL if you're familiar with that where you have rows which is kind of an entity and columns which is the data type for
particular entities. Okay, or values
particular entities. Okay, or values within the entity. So we're now going to have a title. So obviously for our story we want a title. So this is going to be a string column. Okay. And we're going to say again index is equal to true. So
we can look up stories by their title.
Then we're going to have the session id.
All right. And this is going to be equal to a column. We're going to say this is a string. And then index is equal to
a string. And then index is equal to true. Now the reason for this is that
true. Now the reason for this is that every user that creates a story, we're just going to track. Um so when someone creates a story, even if they're not signed in cuz we're not doing authentication in this video, we'll just
see what session ID created this story.
So later on, we could potentially get all of the stories created by a particular web browser session. We'll
talk about the session later on, but for now it's not a big deal. We're then
going to have created at is equal to column. And again, it already knows what
column. And again, it already knows what I want. So, it's going to be a datetime
I want. So, it's going to be a datetime column. And when we do datetime, we need
column. And when we do datetime, we need to specify the time zone. So, we're
going to say time zone is equal to true, which means include the time zone in the date. And then the server default is
date. And then the server default is going to be funk.now. This comes from SQL. Well, will just automatically grab
SQL. Well, will just automatically grab what the current time is and put that inside of the call. So this means it will pretty much automatically specify when this is created for us. We
don't need to do it oursel. Okay.
Lastly, we're going to say nodes is equal to a relationship. Okay. And this is going to
relationship. Okay. And this is going to be a story node and we're going to say back_populates
is equal to story. Now when we have tables and we want to have relationships between different pieces of data, we can create these various different types of relationships. Now you can have
relationships. Now you can have relationships that are one to one meaning you have like one thing connected with one other thing. You can
have one to many which in our case we have one story which is going to be connected to multiple nodes or various options kind of underneath this story.
Or you can have many to many where you have many things connected to many other things. So in our case, we're going to
things. So in our case, we're going to have this one to many relationship type, which you're going to see here as we start populating these values. Now, this
back populates thing here is essentially a field that's going to be added to all of the nodes that are related to this particular story. So they know what
particular story. So they know what story they belong to. Okay. So now we're going to say class story node. So the
idea here is that we're going to have this overarching story which contains kind of the metadata about the story as a whole. Select the title created at and
a whole. Select the title created at and then the different nodes. Then we're
going to have all these nodes which contain all of the different kind of options and paths for the story. So each
node has some different properties. Now
these properties will be things like the content of the particular node, if it's a root node, so if it's like the first node of our story, if it's an ending node, if it's a winning ending node, uh,
and then the other options that it has.
So let's just write this out. We're
going to start with the table name which we can just call story nodes. Okay. We
then want to have an ID. So we're going to have ID is equal to column integer and then same thing primary key and index equals true because we need to have a unique ID for all of our story
nodes so that we can reference them.
We're going to have the story ID which is going to be a column. Okay. and this
is going to be an integer and this is going to reference a foreign key to stories ID and this is going to be index equals true. Now what we're doing is we're just making sure that not
only does the story have a reference to the various nodes but each node in the story has a reference back to the story.
So a foreign key denotes this type of relationship where we're saying we want to have a column here called the story ID. It's an integer and it simply
ID. It's an integer and it simply references the stories ID. Okay, so
we're doing the table name do ID. All
right, and that will be um populated by this back populate kind of story thing which you're going to see in a second.
All right, then we're going to have content is equal to and this is going to be column and we're going to have string.
Okay, we then should spell content correctly. And then beneath content,
correctly. And then beneath content, we're going to have is root. Now, this
is important because we need to know if this is the first node that we have or not. So, we know where to start our
not. So, we know where to start our story from. So, we're going to have
story from. So, we're going to have column and then boolean and we're going to have default is equal to false. We're
going to have is underscore ending. Same
thing. This is a column boolean default equals false. So, we know if this is an
equals false. So, we know if this is an ending node or not. We're going to have is winning ending equal to the same thing. col column boolean boolean
thing. col column boolean boolean default equals false. And then we're going to have the options, okay, which is going to be equal to a JSON column. So we're going to have JSON
JSON column. So we're going to have JSON and then default is equal to a list. Okay, and then we're going to have story is equal to
relationship and we're going to say story and then back populates notes. So
sorry, uh, ignore the foreign key thing that I was referencing before. uh this
is just going to store the ID of the story whereas we're then going to have this whole other relationship. So we
define the relationship here in nodes as well as in story and then for each node we're going to have this story reference right and we're back populating that with nodes. Okay so these kind of
with nodes. Okay so these kind of reference each other I know it's a little bit confusing I don't want to get too into the foreign key relationships in this video but this will create that relationship for us. So that's it for
our story tables. Okay, feel free to pause the video here and kind of code this up. Now, what we're going to do is
this up. Now, what we're going to do is go over to job and this one's going to be quite a bit simpler. Now, the reason we need to have a job model is because the stories take a little bit of time to
populate or to be created by the LLM.
So, basically the flow is going to be here that when someone submits a request to create a new story, it won't be created right away. So, we're first going to create a job. Now, a job is
going to represent the intent to make a story. And this is going to have a
story. And this is going to have a particular progress. It's going to be
particular progress. It's going to be like in progress or completed for example or failed. Now we will essentially keep checking for the status of this job to be completed. And once
this status is finished then the story will be complete and we can grab it. So
we'll talk more about how we do that later on. But that's why we have this
later on. But that's why we have this job. Okay. It's very common when you
job. Okay. It's very common when you have these kind of longer operations on the back end that you don't want the back end to be waiting for this operation to finish before it returns a
request. So the idea is
request. So the idea is front end essentially submits a job.
Okay. And then the back end here returns the job. So we're able to view the job.
the job. So we're able to view the job.
Okay. Then the front end will say ask if job is done. the backend will report the
status of the job and then if job is done backend can send story. Okay, so I hope that makes a little bit of sense.
Uh it will when I start coding this out more, but the basic idea is the job will tell us the status of the story creation. So we're going to copy a few
creation. So we're going to copy a few things from here just to save us some time. In fact, let's copy all the
time. In fact, let's copy all the imports and then we can just remove the relationship because we're not going to need that here. We can remove the foreign key and uh JSON fields as well.
And actually I don't think we need the boolean field either. Okay. So let's
come down here. We're going to say class and this is going to be a story job.
This will inherit from base. We're going
to say underscore table named is equal to the story jobs. And we'll
start defining the fields. Now we're
going to have ID. Okay, this is going to be column integer. Primary key is equal to true. Index is equal to true as well.
to true. Index is equal to true as well.
We're going to have a job ID which is going to be a string. Okay, we're going to index that as true and we're going to set a unique constraint. So we're going to say unique is equal to true which means that all the job IDs need to be
unique. The reason we have a job ID is
unique. The reason we have a job ID is we want to have a longer value that's a string uh rather than just this numeric ID. Okay. Then we're going to have a
ID. Okay. Then we're going to have a session ID of the person who created this job. So we can say column string
this job. So we can say column string same thing index equals true so that we can look this up quickly. Then we're
going to have the theme of this. So this
is going to be a column which is a string.
Okay. We're going to have the status.
This can also be a column that is a string. We're going to have the story
string. We're going to have the story ID. Okay. This is going to be a column
ID. Okay. This is going to be a column that is an integer. Now we could make this a foreign key but I am just going to make this an integer to make it easier. and we're going to say nullable
easier. and we're going to say nullable is equal to true and this is going to store the story ID that was generated um by this job. Okay, we're then going to have an error and this is going to be
equal to column string and we're going to say nullable is equal to true. When you say nullable equal to true, this means that this can
have no value. It can be null or empty.
Okay, we're then going to have created at and same thing, we're going to copy what we had before. So this is a datetime column with the time zone equal to true and the server default uh function now so that it will generate
that for us automatically. And then
we're going to have completed at so we can see how long the job took. So
completed at is equal to column and we're going to do the same thing except this time we'll just have nable equal to true because it's possible this job never completes if it fails. Right? So
that's why we have that. Uh hopefully
that is making sense. I think that is pretty much all we need for the story job. So now we have our story, we have
job. So now we have our story, we have our job models. Okay. And we're going to go and we're going to start writing the schemas and then the different API routes that we need. Okay. So let's
close database. Let's close models and let's go to schemas. Now these will seem a little bit vague uh until we get into writing the routes, but just trust me they're important and once we have these
then we can write the API routes uh and start kind of testing the API a bit.
Okay, so let's go to schemas and let's start with story. Okay, now essentially what we're going to do here is we're going to define these Python classes
that specify the type of data that we want our API to accept and to return.
This is really important because it allows Fast API to automatically do some data validation for us to ensure that the data is correct that's coming into the API. So you'll see what I mean as we
the API. So you'll see what I mean as we start writing it, but just bear with me here until we start utilizing it. So I'm
going to say from typing import. Okay.
List optional and dictionary. All right.
We're then going to say from datetime import datetime.
Okay. And from piantic import the base model. All right. Now
for all of our schemas they need to inherit from the base model or at least have one parent class which is the base model. That's because paidantic will do
model. That's because paidantic will do automatic data validation. So
essentially someone can send something like you know theme is Dubai or whatever to our API but they're going to send it like this. Now in a traditional API
like this. Now in a traditional API framework we would need to check to say okay does theme exist? All right it does exist. Okay is it not empty? Okay is it
exist. Okay is it not empty? Okay is it a string? Right? We'd have to check all
a string? Right? We'd have to check all these values to make sure that the data being ingested by the API is correct before we start processing it. However,
Pyantic can automatically do this for us if we tell it what we want the shape and structure of our data to look like. So
that's what the schemas are, the structure of the data that's coming in so that Pyantic can automatically validate it. So first we're going to say
validate it. So first we're going to say class story options schema. Okay, and
this is going to inherit from the base model. Now we have a few schemas, so
model. Now we have a few schemas, so just work with me here. And some of them inherit from others. So we're going to say text is string and node id is
optional. Okay. And then int equal to
optional. Okay. And then int equal to none. Now this is going to be a class or
none. Now this is going to be a class or a type that we're going to use in 1 second. We're then going to say class
second. We're then going to say class story node base.
Okay. And this is going to inherit from base model. And here we're going to say
base model. And here we're going to say each story node is going to have some content which is a string. It's going to have is ending which is a boolean which by default can be equal to false and an
is winning ending which is also a boolean which will by default be equal to false. Okay. So we
have these two kind of subasses which we'll use here in one sec. Now we're
going to have class complete story node response. Now notice
the naming convention. Okay. So when I say base, what this means is that I'm not going to directly use this in my API. I'm going to use it as a parent
API. I'm going to use it as a parent class to make things a little bit simpler when I write more schemas later on. When I write response, this means
on. When I write response, this means that this is what the response from my API is going to be. So what gets returned to the front end when I write request, which you'll see in a second, that is the data coming from the front
end to the back end that we're accepting right into the back end. So from here this is going to inherit from the story node base. So a completed story node
node base. So a completed story node response is always going to have content is ending and is winning ending. So
we're going to have all of that plus we're going to have an ID which is an int. Then we're going to have options
int. Then we're going to have options and this is going to be a list of the story options schema which is why we wrote that and by default that's going
to be equal to an empty list. We're then
going to say class config and this is going to be from undersc_attributes equal to true. All right. So what we're saying is that a complete story node response also includes the ID and some
options and those options look like this. They have some text and they have
this. They have some text and they have a node ID. Okay. So that's why we have the story option schema here for the options from a complete node response.
Again, I know this is confusing. Just
bear with me here. And if you really need to, you can ask the LMS to kind of clarify what's going on. if you want to know line by line what's happening here.
This one's just a bit too complex for me to get into all the details without spending too much time on it. So next
we're going to move on to another schema which is the story base. Okay. And this
is going to inherit from the base model.
And what this is going to have is a title which is a string. It's then going to have a session ID which is optional
string equal to none. And then again we're going to copy in this class here.
Okay. So class config then what we're going to do is have class create story request. All right this is going to inherit from base model and
this is simply going to have a theme which is a string. So all we're saying is when you want to make a new story here you can pass uh we can use this schema where we just expect that you pass us some theme and that theme is a
string. And then lastly, we're going to
string. And then lastly, we're going to have a class which is the complete story response. Okay, this is going to inherit
response. Okay, this is going to inherit from the story base. All right. And then
in here, the complete story response is going to have an ID. It's going to have a created at time which is going to be datetime. It's going to have a root node
datetime. It's going to have a root node and that root node is going to be this complete story node response that we specified right here. Okay, for one particular root node and then we're
going to have this all underscore nodes here which is going to be a dictionary where we're going to have an int as the key and a complete story node response
as the value. Okay. And then we're going to do again same thing the class config equals from attributes which just means when we define this we can specify the attributes that we want to have here.
Okay. Again I know it's a little bit confusing with the schemas. Now that
they're written, we don't really need to come back to them at least for the story. Let's just do them for job and
story. Let's just do them for job and then once we start using them in the routes, they should make a bit more sense. But again, the way that we've
sense. But again, the way that we've defined them here is that Pyantic can automatically look at the data and validate that it's correct so that we can use these Python objects rather than
relying on just normal Python dictionaries or JSON strings or things that aren't typed correctly. This also
allows us to have really good documentation, which you're going to see later on. Okay, so we're going to go to
later on. Okay, so we're going to go to job.py and inside of job.py, this is
job.py and inside of job.py, this is going to be a little bit simpler. We're
going to write the schemas here. So
we're going to say from typing import optional from datetime
import date time from paidantic import the base model. Okay, we're then going to have our class and this is going to
be a story job base which will inherit from the base model and we're just going to say that okay, so for all of our jobs we're going to have some theme which is a string. Okay, we're then going to have
a string. Okay, we're then going to have class which is a story job response and this will inherit from not the story job base but from the base model. So let's
fix that. Now, for our response, we're going to have our job ID, which is an int. We're going to have our status,
int. We're going to have our status, which is a string. We're going to have our created at, which is date time.
We're going to have our story ID, which is an optional int because if the story is not generated yet, then we won't have this.
We're going to have our um completed at oops completed at which is also an optional datetime equal to none because if the story isn't completed yet well we
don't have one. And then we're going to have our error which is also an optional string equal to none because again if there's no error well there's no error, right? And then we're going to have
right? And then we're going to have class config and then from attributes is equal to true. Okay, that's pretty much it. Um, we'll do one last one just
it. Um, we'll do one last one just semantically to make this kind of follow our convention where we're going to have a story job create. Okay. And this is
just going to inherit from the story job base and then just pass. Now, the reason why I'm doing this is because we may use the story job base somewhere else. And
then what I want to do is I just want to have something that specifies, okay, this is going to be used for like a request. So like creating something. So,
request. So like creating something. So,
I'm just essentially renaming this so that when I use it in my code, it's a little bit easier to understand the meaning of it. Um, understand that might be a little bit confusing, but I'm just
trying to keep like the same convention.
So that now when I use this, I use it for ingesting data and rather than it being the story job base, which doesn't make sense, it's the story job create.
So, I've just given a new name essentially to this base class and then later on if I want to add anything to the job creation process, I can add it inside of here. Okay, so that's job and
story schemas. Now, we're going to go to
story schemas. Now, we're going to go to routers and we're going to start with story. Okay, so we're going to go to
story. Okay, so we're going to go to routers. Now, a router is where we
routers. Now, a router is where we actually write the end points that are going to be hit by our user. Now, this
story router, uh, what we'll do is we'll write all of the endpoints and then we'll write the implementation later.
So, let's start with our imports. We're
going to import uyu ID, which is a unique identifier that we can generate for our story um, ids. We're going to say from typing import optional. We're
going to say from datetime import date time. We're going to say from
fast API import the API. Oops. Let's get
out of here. Import the API router depends HTTP exception cookie response
and background tasks.
All right, we're going to say from SQL Alchemy import session and then we're going to say from backend
db dot what is this database import the get database and the session local we're
going to say from the models dotstory import the story and the story node and this needs to be backend.mmodels so
let's fix backend.mmodels.
backend.mmodels.
Okay, we're going to say from backend domodels job import the story job and we're going to
say from schemas dotstory and again this needs to be backend.s
schema. So let's fix that up. We're
going to import the complete story response, the complete story node response, and the create story request.
Okay. And we can fix the indentation here and continue. Okay. Next, we're
going to say from backend dots schemas.
import the story job response. And that
should be good for right now. Next, what
we're going to do is we're going to make a router. Now, these routers allow us to
a router. Now, these routers allow us to write different endpoints in different files and have different API prefixes for them. So, you'll see what I mean,
for them. So, you'll see what I mean, but we're going to say uh define the API router. We're going to say specify the
router. We're going to say specify the prefix, which is going to be equal to slash stories like that. And then we're going to say tags is equal to and then
stories. This is just for documentation.
stories. This is just for documentation.
Now what will happen now is that we have an overlying or an underlying I guess uh API prefix called slappi which means anytime we want to access any of our
routes we have to go to / ai. So we go to like backend url / ai but then for this part uh particular router we're adding another prefix called sltories.
So every time we want to access any of the routes here we're going to go to the backend URL /tories slash endpoint. Endpoint being the
slash endpoint. Endpoint being the specific URL that we want to hit. So
something like you know create story.
Okay. So that's what we're doing. So
we're organizing the story specific routes. Now what we're going to do is
routes. Now what we're going to do is we're going to write a few functions that we need. So we're going to say first define get session ID. This is
going to take in a session ID which is optional string equal to a
cookie of none. So essentially a session will identify your browser when you're interacting with a website. Now sessions
can expire after a certain amount of time but they can store information such that the website doesn't need to keep uh kind of loading new state for you or sharing new information with you. So for
example, if you ever go to a website and you don't deal with any authentication stuff, what ends up happening is it might remember like buttons that you pressed on or things that you've done on that site for a particular amount of time. And that's because it's
time. And that's because it's identifying your particular web browser and storing the ID of that session so that it can kind of repopulate the state that you had the last time you were on
that site. So we're doing a similar
that site. So we're doing a similar thing here where we're going to get the session ID which again is has nothing to do with authentication and it's not something that's like super secure or
reliable but that will identify the particular browser session that's using this website. Okay. So, if you ever see
this website. Okay. So, if you ever see things like your, you know, your session timed out, that means, well, the session expired, so now a bunch of the data you had on the site kind of got cleared and you need to, uh, what do you call it?
Refresh that again or load it in again or fill in the form again or whatever the information is. That's what that means. Uh, and anyways, let's continue.
means. Uh, and anyways, let's continue.
So, we're going to say if session ID, uh, or if not session ID. So, for some reason, no session ID already exists, then we can make a new one. So we can
say session ID is equal to the string of UU id dot uyu ID4 and then call this function and then we can simply return the session ID. So
what this is going to do is just look for your session ID and then if you don't already have a session ID, we're just going to make a new unique one for you and then return it. Okay, let's
continue. Now let's make our different endpoints. All right, so the first
endpoints. All right, so the first endpoint we're going to have we're going to say at routouter.post.
Post is what you use when you want to create something new. And we're going to type slashcreate. And then we're going
type slashcreate. And then we're going to specify the response model is equal to the story job response. Okay. Then
we're going to go here and say define create_tory.
And what we're going to specify in here is all of the values that we need. So
first we're going to say request is equal to the create story request. Now
notice I'm using the schemas that I defined. So I'm saying, hey, we're going
defined. So I'm saying, hey, we're going to return a story job response, but we are going to accept a create story request, which will contain the theme that we need, right? We're then going to
have some background tasks. So we're
going to say background tasks is equal to background tasks. We're then going to say the response is equal to response.
We're going to say the session ID is a string that depends on the get session ID function. And then we're going to say
ID function. And then we're going to say the database is a session that depends on the get DB function. Okay, it'll uh go away from being gray in a second
here. Now, what this is going to do for
here. Now, what this is going to do for us is it's going to inject these dependencies or these values into these parameters. So there's this really cool
parameters. So there's this really cool thing in fast API called depends and again what it will allow us to do is essentially call this function anytime this uh endpoint is hit and grab the
value of our session ID. Same thing with the uh get database it will just grab the database and throw that in this database value. And then we have the
database value. And then we have the request which we'll be able to get the response the background task which we'll use to create a background task which can run independently of the main thread which I'm going to talk about in a
second uh when we start setting this up.
Okay. Okay, so now we're going to say response do set_cookie and we're going to say the key is equal to session ID and the value is equal to
session ID. Now what this effectively is
session ID. Now what this effectively is going to do is just store what our session ID actually is so that we're able to use it later. It's not secure.
Someone would be able to see what their own session ID is from their browser, but it's fine. We're just using it to store what the user's session is. And
even the session stuff is not really necessary for us to do. But I'm just trying to show you a bit more complexity in case you want to identify users based on their web browser instance um rather than like some authentication thing cuz
we're not using authentication here. And
I also am just going to add this HTTP only is equal to true. Okay. Next, what
we're going to do is we're going to generate a new job ID. So effectively
what's going to happen here, right, is that as soon as the user wants to create a story, really what we're going to do is create a job. Once we make the job, that job will then trigger a background
task to run which will go to OpenAI to call an LLM to make the story for us. We
haven't yet written that component, the LLM component. But this is going to
LLM component. But this is going to handle kind of from our backend at least what we need to start setting up. So the
job right that we can return before that task runs. So, we're going to say job uh
task runs. So, we're going to say job uh id is equal to string uyuid doyuid4 like that. We're then going to say job
like that. We're then going to say job is equal to story job.
And this is going to create a new instance in our database because I imported this from my models. Right? So,
we're going to make a new story job. So,
we're going to say job ID is equal to job ID. We're going to say session ID is
job ID. We're going to say session ID is equal to the session ID to identify the person who made this. We're going to say the theme is equal to the request theme
which is from this right the create story request which we can look at and the status we're just going to hardcode this is equal to pending. Okay. So now
we have our job and we've kind of created this job but in order to add it to the database we need to do the following. We need to say database.add
following. We need to say database.add
add the job and then db.comit. Okay, so
anytime you add something, what you're doing is kind of like staging it. You're
putting it in a place where it's ready to be finalized in the database. And
then as soon as you run this commit command, it will actually commit this.
So it will save it into the database.
And that's the job of the OM. Okay, the
object relational mapping, it will automatically kind of run the proper SQL code to save this job story or this story job. Okay. Now, beneath here, I'm
story job. Okay. Now, beneath here, I'm going to put a to-do, and I'm just going to say add background tasks because in a minute, we're going to have to add a background task that will actually run
so that we can generate the story. So,
because right now, we're not actually generating the story, right? So, we're
going to say generate story. So, we know that okay, later we got to actually generate the story here. But for now, we can return this job, which will give us the job ID. And then what will happen is
once we have the job ID, we can kind of keep checking like, all right, what's the status of our job? Is it done? Did
it fail? Is it still pending? Um, and
we'll be able to get that. And then once the job is ready, we can return the entire story. Okay, so we are almost
entire story. Okay, so we are almost there. What we're going to do now is we
there. What we're going to do now is we are going to write a function called generate_tory task. Okay, and this is going to take in
task. Okay, and this is going to take in the job id, which is going to be a string. It's going to take in the theme,
string. It's going to take in the theme, which is going to be a string, and the session ID, which will be a string as well. Now, what we're going to do here
well. Now, what we're going to do here is we're actually going to create a new database session. So, we're going to say
database session. So, we're going to say DB is equal to session local. And the
reason why I need to do this is because if I use the same session that I'm using from here, so inside of all of my API routes, then I'm going to have some hanging operations where essentially
when this background task is running and it's using the database, my API will not be able to use the database at the same time. So this is a kind of a weird thing
time. So this is a kind of a weird thing and it has to do with running background tasks and something called asynchronous operations. So let me break this down a
operations. So let me break this down a little bit. What's going to happen,
little bit. What's going to happen, right, is that when they send this request, it's going to take us maybe 20 seconds to generate the story. We don't
want to wait 20 seconds before we return the story to the user because if we were to do that, then this request is just hanging. It's just sitting there and
hanging. It's just sitting there and it's not doing anything and we're waiting on something out of our control like the LLM to return us some data. So
rather than us kind of waiting this really long amount of time and essentially hanging the whole program, which means nothing else can happen during this time, what we're going to do is immediately return this job to the
user, telling them that their task is currently in progress. We're then going to create a background task, which will run in the background using a new thread, which means that anything that
the background task is waiting on is not going to interfere with our API. So
other users will be able to come in here and make new jobs. So we can have multiple of these jobs running at the exact same time and they can all be waiting for the LLM to give them the story. And then as soon as the story is
story. And then as soon as the story is generated, they can update this job and we can return the new job to the front end. Now, it gets tricky here though
end. Now, it gets tricky here though because if we were to try to use the same database instance that we had from this endpoint that we grabbed here inside of this generate story task, then
because the database is going to be waiting for certain operations to be performed, we are going to end up having this kind of weird synchronization and hanging problem going on where we're
using this object that exists in this thread inside of this thread and it's waiting on certain things to happen. Um,
I don't know how to explain this better because it gets into like some pretty complex threading and asynchronous operations. The point is this line is
operations. The point is this line is necessary to make sure that we have a new instance of our database. So, a
separate session. So there's kind of two database sessions active at the same time. So that one session can be doing
time. So that one session can be doing something while the other one's kind of waiting on something else to occur rather than if we use the same one then we're going to be clogging up our background tasks and they're all going
to be just waiting on this one session to be available when we could just create another one. I hope that makes a little bit of sense. Um, again, that's kind of the best I can explain without
spending like 20 minutes going through threading and async operations and more advanced coding. Okay, so we're going to
advanced coding. Okay, so we're going to go into a try block now. Uh, so we're going to say try and what we're going to do is we're going to look for this job.
So once this job is created, we're going to get it from the database. All right.
So, we're going to say job is equal to db dot query the story job dot filter. And we're going to say story
dot filter. And we're going to say story job dot job
id okay is equal equal to job id dot first. All right. So this is how you
first. All right. So this is how you perform a query operation in the database. What we can do is we can say
database. What we can do is we can say all right we're going to look in our database. We created a new session,
database. We created a new session, right? We're going to query the story
right? We're going to query the story job table or the story job model and we're going to apply this filter. We
want to see if the story jobs job ID is equal to the particular job ID we have here. And then we're going to grab the
here. And then we're going to grab the first entry that we find. Now again the reason why we need to do this is because if I were to pass this job here uh to this function we would get the same
problem that we had if we were to pass the database because we're passing or sharing kind of references to the same object across different threads which is problematic. So I'm going to say if not
problematic. So I'm going to say if not job then return. So if there's no job available then there's nothing to do here. So we're going to return.
here. So we're going to return.
Otherwise, we're going to have another try block and we're going to say job status is equal to processing. So, we're
actually modifying this job model. Then,
we need to save this change in the database. So, we're going to say dbom
database. So, we're going to say dbom commit. We're then going to say story is
commit. We're then going to say story is equal to and for right now I'm going to put an empty dictionary, but later we'll actually generate the story here. Just
right now we don't have a way to generate it. So, I'm going to put to-do
generate it. So, I'm going to put to-do generate story. Okay, then we're going
generate story. Okay, then we're going to say job.story
id is equal to for now we're going to put one and we're going to put you know update story id. All right, we're going to put a to-do because again we haven't generated uh
written the story generation script yet.
Then we're going to say job status is equal to completed and we're going to say job completed at is equal to datetime.now. and we're going to save
datetime.now. and we're going to save this in the database. So, dbcommit.
All right. Now, if an error occurs here, we're going to accept an exception as e and we're going to do a similar thing.
So, we're going to copy these four lines. Okay, we're going to put this in
lines. Okay, we're going to put this in here. We don't have a story ID because
here. We don't have a story ID because well, none was generated, but the status we can set to failed. We can have the job completed at still be this. And then
we can have the job.
is equal to string of e. Okay, so this is what's going to happen for this except block. Now, we also need to have
except block. Now, we also need to have another accept block. So, we're going to say exception or except exception as e. And actually, we
actually don't need the except sorry, we're going to say finally. And then
this is going to be db.close. So just no matter what after we do this operation I want to close the database instance so that I don't have this open thread or this open reference we don't need. All
right so now this function is written we actually can use this function from here. So where we have our to-do we're
here. So where we have our to-do we're going to say background tasks okay do add task and we're going to add the task which is this function. We're going to
say generate story task. And then the way this works
story task. And then the way this works is we need to pass the parameters here that we want to pass to this. So we're
going to say that the job id is equal to and then this is going to be the job ID.
We're going to say the theme is equal to request theme and the session ID is this user session ID. And then we can remove this to-do. So what will happen is we
this to-do. So what will happen is we will add this background task. It will
start running this particular function.
this function will actually generate the story. But right now, of course, we
story. But right now, of course, we don't actually have anything to generate. So, it's not doing that right
generate. So, it's not doing that right now. But at this point, this would be
now. But at this point, this would be the operation that will take a bit of time to run. So, we can just have this in the background and not interfere with the rest of our API. Okay. So, that's
almost it. Now, there is another endpoint that we need to write here. And
this is to retrieve the finished story.
So, all of this that we wrote was to kind of create the story. And we're
still not done with the creation because we need to write a few more functions.
But we also need the ability to get the story once it's finished. So we're going to say at routouter.get.
Okay. And we're going to say slash and we're going to put a dynamic uh variable here called story ID and then slashcomplete.
And we're going to say the response model is equal to the complete story response. So from here we're going to
response. So from here we're going to return the entire completed story. So
we're going to say define complete story. Okay. Or we can say
complete story. Okay. Or we can say maybe get complete story or something.
And we're just going to take in the story ID. Okay. Which is going to be
story ID. Okay. Which is going to be this. All right. So we're just getting
this. All right. So we're just getting it from the path parameter. And then
we're going to say the DB is session.
And this is going to be equal to depends on the get database function. Okay. Now
it's important that the parameter you have here called story ID matches this path parameter. This is a convention
path parameter. This is a convention directly in fast API where if you want to have a dynamic value in the URL, you wrap it in these braces and then as long as you write the same variable name and specify the type that you want it to be
in the parameter for the function definition, it will automatically populate this value for you. Okay, so we have that. Now what we're going to do is
have that. Now what we're going to do is we're just going to look up the story based on its story ID to see if it actually exists. So we're going to say
actually exists. So we're going to say story is equal to db dot query the story table. We're going to filter so that the
table. We're going to filter so that the story do id is equal to the story underscore id and then we're going to get the first value if it exists. Now if
there's no story so if not story then we're going to raise an http exception with a status code 404 and the details that we couldn't find the story.
Otherwise, we're going to return the story. But before we return the story,
story. But before we return the story, we need to actually parse it. So, I'm
going to say to-do parse story.
Okay. Now, before I do this, I want to write some other parts because it's going to make be more clear once we actually generate the story what the story looks like and why I mean we need to parse it. But essentially, before we can return the story, we need to turn it
into a format that is going to be acceptable by our front end, which does require a little bit of logic. So down
here we're going to have a function called build_complete story tree. Okay, this is going to take
story tree. Okay, this is going to take in a database session and it's going to take in the story and it's going to
return a complete story response. Okay,
so for now we're just going to say pass.
But what we're going to do is we're effectively going to generate the story here. So we're going to say complete
here. So we're going to say complete story is equal to a complete uh story tree. We're going to pass the database
tree. We're going to pass the database and the story that we found and then we're going to return the complete story. Okay, which will be generated
story. Okay, which will be generated from this function or not really generated but turned into a format in which we can return. All right, so that's all we can write right now for
the story file until we complete the story generation via LLM. So what I'm going to do now is go to job. This is
way simpler. Don't worry, we get a bit of a break with this file. Then we're
going to do the story generation and then the back end's pretty much done.
And I can show you how we test all of it as well. So, we're going to import uyu
as well. So, we're going to import uyu ID here from job. We're going to import typing uh or sorry, from typing import
optional. Okay. And in case I didn't
optional. Okay. And in case I didn't mention this, we're inside of the job.py
file for our routers. Uh did I spell something wrong here? From typing. Yes,
import optional. That's why. Okay. Then we're
optional. That's why. Okay. Then we're
going to say from fast API import import the API router. We're going to
import depend the HTTP exception and cookie. Okay. Then we're going to
and cookie. Okay. Then we're going to say from SQL import the session. We're
going to say from backend. DB. database
import the get database. We're going to say from backend domodels job import the story job and from
backend.s schemas job import the story
backend.s schemas job import the story job response. We then are going to
job response. We then are going to define our router similar to before. So
this is going to be for the jobs this time. So we're going to say define API
time. So we're going to say define API router. We can specify the prefix and
router. We can specify the prefix and the tags with slash jobs. Okay. Sorry,
quick pause here. Let's come down here.
I want to write the endpoint that we need. So we're going to say at
need. So we're going to say at router.get.
router.get.
This is going to be slash job id. The
response model, okay, is going to be equal to the story job response. And
this is going to get us the status of a current job. So we're going to say get
current job. So we're going to say get job status as the function. We're going
to say the job ID is a string. and we're
going to say the DB is a session that depends on getting the database function. Okay. And then all we're going
function. Okay. And then all we're going to do is we're going to query the job.
Uh and then we are going to return that.
So it actually is giving me all this but I don't want to use the autocomplete too much. So we're going to say db is equal
much. So we're going to say db is equal to query story job dot filter story job dot job id is equal to the job ID the
user is asking about. And then we're going to get the first value. Okay,
we're going to say if not job then we're going to raise a HTTP exception with status code 404 job not found. Otherwise
we can just return the job. Okay, which
is going to be based on this schema. Now
actually a few of the things I imported here I don't need because I was going to be writing something that I realized we don't need in this function. So this one is quite simple just getting the job ID or sorry getting the job status based on
the job ID. Okay, so now we have our job and we have our story routers. What we
need to do is we need to connect the routers to the main part of our application. So we're going to go to
application. So we're going to go to main.py here. We're going to import the
main.py here. We're going to import the routers that we wrote and we're going to include them in our application because right now they're not actually connected. So we're going to run this
connected. So we're going to run this file, but we need to connect the routers to this. We're going to say from routers
to this. We're going to say from routers and then we're going to import story and job. Okay. Then we're going to go down
job. Okay. Then we're going to go down here after the middleware and we're going to say at app or app dotincclude router and we're going to include the story.outer
story.outer and we're going to say the prefix is equal to settings prefix and then we can copy this and we can do the same thing for the job
router. Okay, so now our endpoints
router. Okay, so now our endpoints should be set up and connected. Now,
they're not all going to work right now, obviously, because we haven't actually generated the story, but we can still test this if we want. So, if we open up our terminal, we can run the backend.
So, we go cd backend uvun main. py.
Okay, let's see if we get any errors.
Uh, this is no module named backend.
Okay, so let me just have a look at why we're getting this and I'll be right back. Okay, so I was getting an error
back. Okay, so I was getting an error here that was essentially saying that this backend cannot be resolved. Uh, and
that's because it needs to just be this DB type imports like from DB.database.
We need to remove all these backend prefixes to our imports. However,
PyCharm is giving us a problem here because of the way that it's indexing the project. It thinks that we need
the project. It thinks that we need backend. So, we have this conflicting
backend. So, we have this conflicting thing where PyCharm is telling us that we need backend, but when we actually run the application, we don't. So, what
I'm going to do is I'm just going to fix this by opening up this folder directly in PyCharm. So, I'm going to go here.
in PyCharm. So, I'm going to go here.
I'm going to go file open and I'm just going to open backend directly. Okay. In
this window and then I'm going to fix some of the imports. And I believe that this should now work. Yes. So now when I open this directly, if I change this to just be core, right? So I remove all
those backend prefixes, we're good to go because now Python treats this as or PyCharm sorry treats this as the root of my project. Later we can go back and we
my project. Later we can go back and we can still write the front-end directory but for now let me reconfigure the interpreter. So I'm just going to select
interpreter. So I'm just going to select the interpreter here. Okay, so we're good to go. And let's just fix up those imports. So pretty much anywhere where
imports. So pretty much anywhere where you see backend, you can just remove the prefix there. Okay, so we'll do the same
prefix there. Okay, so we'll do the same thing here. Remove that. Go to routers.
thing here. Remove that. Go to routers.
Same thing here. Remove all of these.
And then we should be good to go. And
I'm going to remove that for right now.
Story. Okay, got a lot of backends here.
So let's get rid of all of those. Clean
this up. And this is just be part of coding, guys. Errors happen. I like to
coding, guys. Errors happen. I like to include it in the video so that u kind of everyone can see that I'm not perfect, that I do make mistakes. And
let's just kind of scroll through here and make sure that we don't have any more errors. Okay, looks okay. And then
more errors. Okay, looks okay. And then
lastly, let's go to main.py
imports. I think we should be good.
Okay, so now assuming that I fixed all of those, I think I did. I'm going to go uv run main.py directly from my backend directory. Okay. And you can see now
directory. Okay. And you can see now that the server is running. So if we go back to our browser now that this is running, let me open up the correct tab here. And we go to localhost
here. And we go to localhost 8000/doccks. You can see that we now
8000/doccks. You can see that we now have all of the API stuff showing up. So
you can see we have this post request that we can test if we want. We have
this get request and we have this get request here for the job ID. Okay. So if
we wanted to create a story, we can post something. So we can change this. So the
something. So we can change this. So the
theme, let's try it out. go try out uh session ID actually we don't need to pass anything for that but for the theme we can go with you know Dubai or something where I currently live and go
execute and then you can see here that we got an internal server error okay so let's test to see why we're getting that error uh but this is how you test the API at least one way right so you can go
here can go try it out again pass something in here and let's look at the error that we got okay so good error to run into actually something that I forgot to do it says no such table story jobs. The reason why we're getting that
jobs. The reason why we're getting that is we forgot to create the tables. So,
we need to actually just import something that will create the tables for us. So, what we're going to do is
for us. So, what we're going to do is we're going to bring in one more thing here. We're going to say from DB
here. We're going to say from DB database. Okay. Import. And we're going
database. Okay. Import. And we're going to import the create tables. And then
we're going to create the tables before the application runs to make sure that they actually get added. So now we're going to have to shut down the application and restart it. Okay. And
now if we go back here, let's refresh.
Okay. And let's try this again. So we'll
go try it out. Change this to some theme, Dubai, whatever. Press on
execute. And we got another internal server error. So let's debug this
server error. So let's debug this together. Okay, so it says we had one
together. Okay, so it says we had one validation error. I actually know
validation error. I actually know already where this is coming from, but it's saying the job ID needs to be a valid integer. Unable to parse a string.
valid integer. Unable to parse a string.
So we can kind of scroll through and see where we were getting this from, but I believe it's because we didn't actually properly handle the um generation yet of the stories. So if we go to routers
the stories. So if we go to routers job.py or sorry, story.py and in here,
job.py or sorry, story.py and in here, let's see where we're returning the job ID. So we're returning job here. It says
ID. So we're returning job here. It says
job ID is equal to job ID, which is a string. Uh but it's saying that this
string. Uh but it's saying that this needs to be an int. So that would tell me that we probably have an error here in our schema, the story job response.
So if we go to the story job response which I believe is here. Yes, you can see that we've specified the job ID as an int when actually this should be a string. So we just need to change this
string. So we just need to change this to be a string. Okay. So if we just go to schemas, this is the only fix. I'm
just debugging it live with you. If you
go to schema/job.py
and you change int to be string, then you should be good. Again guys, I apologize about the errors, but I like to just make this as realistic as possible for you so you can see that yes, you know, I make mistakes, too.
Okay, so let's go back here. Let's
refresh and let's try this now. So we'll
try it out and we can just pass this theme. That's fine. Execute. And now you
theme. That's fine. Execute. And now you can see that we actually get a job back.
So it says here's the job ID. The status
is pending. It was created at this and this is the information. Now let's take our job ID and let's go and try some of the other endpoints. So let's close this one and let's try to get the job. So I'm
going to go try it out. I'm going to paste in the job ID and hit execute. And
you can see here now that it says, okay, we have a job that was completed. It was
created at this time. Um, what is it?
Completed at null. So, that's something we got to look into because I think we should have that. And it says story ID is one. Okay. So, for some reason, we
is one. Okay. So, for some reason, we weren't getting that completed at result. So, I just went into this job.
result. So, I just went into this job.
py model file. And I noticed that we accidentally had completed job rather than completed at. So, I'm just changed this to completed at so that now we actually have a field that we're storing in our database so that we're able to
modify it and return it. So the problem is just that we had this uh this field misnamed. So that was giving us the
misnamed. So that was giving us the issue for the job right for the completed at field here. Okay. So I
think that's all good. Um that's pretty much it. Now what we need to do that
much it. Now what we need to do that we've kind of tested the APIs. I'm going
to shut this down and I need to handle the story generation. So we need to actually use an LLM to generate the story because of course this is no good unless we actually have some story to generate. So what I want to do is I'm
generate. So what I want to do is I'm going to start by going to prompts. py.
I'm going to copy in a few prompts that we need here, which you can find from the link in the description, so you can kind of see what it is that we're going to be doing and then we will start doing the LLM calls. So, I'm going to paste
this in here and let's start by looking at our system prompt. Okay, let me just make this a bit smaller so we can see this. Okay, so pretty much what we're
this. Okay, so pretty much what we're going to do is we're going to tell the LLM, you're a creative storyw writer that creates engaging choose your own adventure stories. Generate a complete
adventure stories. Generate a complete branching story with multiple paths and endings in the JSON format that I'll specify. Now, we're going to have a
specify. Now, we're going to have a compelling title, a starting situation, so root node. Each option should lead to another node with its own options, and some paths should lead to endings, both winning and losing. At least one path
should lead to a winning ending. Each
story requirement, each node should have two to three options. The story should be 3 to four levels deep, including the root node and a variety in the path length. Now, if you want the story to be
length. Now, if you want the story to be longer, you can change this to be like, you know, five to six. But the bigger that you make this, the more expensive it's going to be to run the LLM call.
So, that's why I'm going with 3 to four for now. Going to say add a variety in
for now. Going to say add a variety in the path lengths, some and earlier, some later. Make sure there's at least one
later. Make sure there's at least one winning path and output your story in this exact JSON structure, which we're going to specify in a second. Now, the
JSON structure will look like this.
Okay, so we're going to ask it to generate us that something that has this kind of data model, right? We have a root node, have all the information, have the options, we have the next nodes, etc. And then we're going to
parse in that information and store it in our database and return the story to the user. So again, you can copy this
the user. So again, you can copy this from the link in the description. You
can go to GitHub, you can find this prompts. py file and you can bring it
prompts. py file and you can bring it into your program. Okay, so now we're going to go to story generator. py. And
from story generator, we're going to start writing a very very simple AI model or not AI model, but we're going to call an AI model that will allow us
to generate this. So let's begin. We're
going to say from SQL alchemy import session. We're then going to say
import session. We're then going to say from and we can actually just go with I think core dot uh config import
settings. Okay. And we're going to say
settings. Okay. And we're going to say from langchain openai import chat open aai. We're going
to say from langchain_core.prompts
import the prompt template. And from
langchain_core output parsers import the no not that the paidantic output parser.
Essentially, what we're going to be able to do is we're going to take a uh string response coming from our LLM and kind of pipe it into a Python class uh which is going to be very useful for us to use um
what is it in order to get all of the data that the LLM gives us. We're then
going to say from core.prompts
import the story prompt and then we're going to say from models.tstory
models.tstory import story. So we can save the story
import story. So we can save the story and then we're going to go quickly over here to models. py within core. So in
core we have this new file models. py
and we're going to define a few pyantic models that are very simple that we need for loading in our LLM data. Okay. So
bear with me here. We're going to say from typing okay import
list dictionary any and optional. We're
going to say from paidantic import the base model and the field. And then we're going to start writing our classes. So
we're going to say class story option llm is base model. Okay. and we're going to say text
string is equal to a field and we're going to say the description is equal to the text of the option
shown to the user. Now the reason why I'm writing this is that we're essentially going to create this very detailed class that we want the LLM to give us the data in. So, we're going to
specify all of the fields that we want to be included in the data that's comes back from the LLM by writing all of these descriptions. And then the LLM
these descriptions. And then the LLM will know how to populate this so that we get the correct data. Okay, we're
then going to say next node is a dictionary that is a string key and any value and this is going to be a field
which is a description. and we're going to say the next node content and its options. Okay, so that's the first part.
options. Okay, so that's the first part.
We're going to use that within our next classes in a second. We're then going to say class story node llm inherit from the base model and we're going to say content and then this is going to be a
string and this is going to be field and this is going to say description is equal to and this is going to be the
main content of the story node. We're
then going to say is ending this will be a boolean which is equal to a field that we will describe as the following. We're
going to say description and this is going to be whether this node is an ending node. We're then going to say is
ending node. We're then going to say is winning ending boolean is equal to field
description. Okay. And this is going to
description. Okay. And this is going to be equal to whether this node is a winning ending node. Okay. And then
we're going to have options. And this is going to be optional because some nodes don't have any options if they're ending nodes. And this is going to be a list of
nodes. And this is going to be a list of the story option LLM, which is equal to a field with a default equal to none and
a description that's going to say the options for this node. Okay, let's make this smaller so we can read all of it.
Lastly, we're almost done. We're going
to have a class which is the story llm response. Okay, this is going to be a
response. Okay, this is going to be a base model and we're going to say the title is a string equal to a field with the description which is going to be
equal to the title of the story. Okay. Okay. And then we're
the story. Okay. Okay. And then we're going to have root node. And this is going to be a story node llm. And this is going to be a
node llm. And this is going to be a field with a description. Okay. That is going to say the root
node of the story. Okay. So essentially
what we're doing is we're mapping out exactly what we want a story to look like within this Python class structure so that we can pass it to the LLM. Okay.
Okay, so let's go back to our story generator and inside of here we can start writing the class that's going to generate the story. So we're going to say class story generator and we're
going to define a class method. So we're
going to say class method. Okay, this is going to be define_get llm cls. Now the reason why I'm writing
llm cls. Now the reason why I'm writing a class method is this is not going to be specific to the instance. In fact,
nothing here is going to be specific to the instance. We're just writing a class
the instance. We're just writing a class so that um we can kind of organize some of the functions that we have for our story generator. Now, when you write
story generator. Now, when you write something with an underscore, this means it is a private method. So, it should only be called internally from the class. Um it's just a convention in
class. Um it's just a convention in Python. Don't worry too much about it if
Python. Don't worry too much about it if you haven't seen it before. Now, what
we're going to do is we're going to return chat open AI. Okay? And the model we're going to
AI. Okay? And the model we're going to use is going to be GPT for Turbo. Of
course, you can use any model that you want. Now, in order for this to work, we
want. Now, in order for this to work, we are going to need our OpenAI LLM key, but I'm going to get that in a second.
And if you already know to get it, you can just put it inside of your environment variable file. And if you have it inside of here, then you are good to go. Okay, so that's our first class method. The next class method is
class method. The next class method is going to be one that actually generates a story. So, we're going to say define
a story. So, we're going to say define generate story. This time it will not be
generate story. This time it will not be private. We'll take in the class. We'll
private. We'll take in the class. We'll
take in a database which is a session.
The session ID which is a string. The
theme which is a string. And by default we'll just make it equal to fantasy or something. And then we are going to
something. And then we are going to return a story.
Okay. So from here we're going to generate the story. So we're going to get our LLM. So we're going to say llm is equal to cls.get
llm. We need to use the underscore.
Okay. Okay, so we get our llm. Uh, what
is the problem here? It's we have two colons. Okay, let's get rid of that.
colons. Okay, let's get rid of that.
We're then going to say our story parser is equal to the pi dantic output parser and the pi dantic object is going to be
the story llm response. Okay, now we need to import this. So we're going to say from models. Okay, so actually from
this is going to be core dot models import the story llm response. Okay. And we're
also going to bring in the story node llm.
Great. So that's going to make an output parser which we're going to use with our llm in a second. And we're now going to specify our prompt. So we're going to say our prompt is equal to let's put
this on a new line. A prompt template.
Now actually this needs to be a chat prompt template. So let's change the
prompt template. So let's change the import up here. And let's change the value down here to chat prompt template.
And from here we're going to say from messages and we're going to pass in here a list of messages. Now when you pass a message you need to specify the role. So
we're going to say this is a system message and then we're going to pass our story prompt. Okay. So this needs to be
story prompt. Okay. So this needs to be all capitals story prompt. So the first message is a system message that says this right large system prompt. Okay.
Now the second message is going to be the user message. All right. So, we're
going to say here uh actually human and we're going to put an fstring and we're going to say create the story with this theme. Okay.
And then we're going to specify the theme. Okay. So, that's it for the
theme. Okay. So, that's it for the prompt. And then here we're going to say
prompt. And then here we're going to say partial and we're going to say the format instructions are equal to the story parser dot format
instructions. And this is actually sorry
instructions. And this is actually sorry get format instructions. So when we make this pyantic output parser we pass a pyantic model to it. So story lm response we then can use this fancy
function called get format instructions which will give us a string that will specify exactly what it needs to look like when the LLM generates the response. So what we're saying here is
response. So what we're saying here is okay we're going to embed the format instructions which are these. The format
instructions we specified in this variable right here in the prompt. So we
will get the format instructions from our model pass it here inside of the prompt and then we also pass the theme and say hey create a story with you know this given theme and then it will do that for us when we invoke the LLM. So
this is our prompt but we need to send this to the LLM. So to do that we say raw response is equal to llm and actually we're going to say llm
invoke and we're going to invoke the prompt.invoke invoke and when we prompt
prompt.invoke invoke and when we prompt uh when we invoke the prompt sorry we're just going to pass an empty dictionary okay this is because you could pass additional variables to the prompt template if you want to in this case we
don't need to so what we're doing is we're saying we're going to invoke the llm with this particular prompt and when we just do invoke it just generates the full prompt by actually passing in the format instructions then we're going to
say the following we're going to say response text is equal to raw response okay And we're going to say that if has
adder which means has attribute and we're going to say the raw response and then content then we're going to say the response text is equal to the raw response content. Okay, so we're going
response content. Okay, so we're going to strip out the text content. If that's
the case then we're going to say the story structure is equal to and this is going to be the story parser.parse
parse and then the response [Applause] text. Now, this is just going to confirm
text. Now, this is just going to confirm that this actually is in the correct format and parse it into the Python object that we want to use. So, it's
going to give us a uh kind of returned version of what do you call it? This the
story LLM response that we'll be able to actually use to access the values. Then,
we're going to say story database is equal to story. We're going to say title is the story structure.title
and the session ID is the session ID.
We're then going to say db.add
the story db and we're going to say db.flush. Now what flush is going to do
db.flush. Now what flush is going to do is it's just going to update this story database object with all of the automatic populated fields like the ID of the story for example the created at time. So we can continue to use the
time. So we can continue to use the story later down here as we start adding some more values to it. So now we're going to say the root node data is equal
to the story structure dot uh root node like that. Okay. Now let's go back here.
like that. Okay. Now let's go back here.
We need the root node and we're going to have to actually process some data here to put it into the correct format which you're going to see in one second. So
we're going to say if is instance and actually we don't need parenthesis here. We're going to say if
parenthesis here. We're going to say if is instance the root node data and this is going to
be dictionary. Then what we need to do
be dictionary. Then what we need to do is say the root node data and is equal to the story node. Okay, llm. And then
what we're going to do is say dot model_v validate the root node data. All
right. Now, I know this a lot of pyantic code, but essentially what we're doing is we're saying, all right, let's get the root node because remember, if we look at our models, we're going to return a root node, which is the start of our story, which will have all of the
options here. We're going to just say,
options here. We're going to just say, all right, if this is a dictionary, we want to validate that it is actually in the correct format of our root node. So
if we go back here, we can see that a node should look like this, right? Story
node LLM should look like this. So we're
going to say, all right, let's make sure it's in that correct format. And if it's not, then this will raise an error.
Okay? Otherwise, we'll be able to continue moving forward. So from here, we're going to say process data, which we're going to do in one second. So
we'll say to do this, and then we're going to say dbomit, and we're going to return our story database object. Now what I'm about to
database object. Now what I'm about to write is one last function here which is going to go through all of the data returned by our LLM and convert it into the correct Python data tape data type
sorry that we can store in our database because by default it's going to return us JSON. So the model's going to return
us JSON. So the model's going to return JSON. So what we're doing is we're
JSON. So what we're doing is we're essentially enforcing that the JSON returned by the model is correct. We're
then putting it into the correct Python object so we can store it in our database. Okay. So, we're going to put
database. Okay. So, we're going to put another function down here. This is
going to be a class method. And we're
going to say define, and this is going to be underscore process_tory node. Okay, we're going to take in the
node. Okay, we're going to take in the class. We're going to take in the
class. We're going to take in the database, which is a session, and we're going to take in the story ID, which is an int. We're going to take in the node
an int. We're going to take in the node data, which is story node llm, and we're going to take in is root. Okay, which is
a boolean which by default is equal to false. Again, let me close this so we
false. Again, let me close this so we can read it. All right, this is what our function looks like right now. And what
is this going to return? Well, this is going to return a story node. Okay, so
we're taking in the class database session story ID, the node data, and if this is a root node or not because we're actually going to recursively call this function in a second, which you will see. Now, story node is something that
see. Now, story node is something that we need to import because we don't have it imported. So, where we have
it imported. So, where we have models.story story. We'll also import
models.story story. We'll also import the story node up here. Okay, let's come back down. What we're going to do now is
back down. What we're going to do now is we're going to start processing our nodes. So, we're going to say node
nodes. So, we're going to say node is equal to story node. All right. And
what we're doing is we're taking the story node llm response which is different from the story node that we have in our database. So, we're now going to say story ID is equal to story
ID. We're going to say content is equal
ID. We're going to say content is equal to node data.content. content. Sorry. If
the has adder again setting for has attribute of node data content otherwise it's going to be node data content. The reason why we're doing this
content. The reason why we're doing this is we're not sure if we're going to be able to access the content attribute or if we're going to have to access content from the JSON dictionary that's returned. So we're just doing kind of a
returned. So we're just doing kind of a safety check here uh to make sure that it is uh loaded in correctly. Okay.
Okay, so we have content, we have is root. So is root is equal to is root
root. So is root is equal to is root which is passed here. We then are going to have is ending.
Okay, this is going to be the same as this. So it's going to be node data is
this. So it's going to be node data is ending if has adder node data is ending otherwise node data is ending. Okay,
same thing. And then we're going to have is_winning ending. And this is going to be equal to
ending. And this is going to be equal to the same thing. No data is winning ending. If has adder, no data is winning
ending. If has adder, no data is winning ending. Else no data is winning ending.
ending. Else no data is winning ending.
Okay, lot of this similar code, but that's almost all. And then we're going to have options is equal to an empty list which we are going to populate. So
again, because we have this JSON structure that's being returned to us, we need to convert it into the correct Python objects, which requires a little bit of processing, which is what we're doing here. So now we're going to add
doing here. So now we're going to add this node to the database. So uh db.add
node and db.flush.
So what will happen is here we're going to take the root node. We're going to pass it to this function. We're going to process the root node. We're going to add the root nodes to the database. And
then we're going to look at the children of this root node. We're going to add those to the database. We're going to look at the children of those root nodes. We're going to add those to the
nodes. We're going to add those to the database. And we're going to continue
database. And we're going to continue that recursively. So we can actually go
that recursively. So we can actually go ahead and call the function here. We can
say cls.c_process
story node. We can pass the database the story dbid the root node data and is root is equal to true only this time for this first node. Okay. Then what we're going to do
node. Okay. Then what we're going to do is the following. We're going to go down here and we're going to say if not and
this is at node is ending and we're going to go in parenthesis has attribute. So has adder node data
attribute. So has adder node data options. So if the node is not an adding
options. So if the node is not an adding node and it does have options and the node data.options
node data.options are not empty. Okay, that's what we're checking. We're saying, are you not an
checking. We're saying, are you not an ending node? If you're not an ending
ending node? If you're not an ending node and you have some options that are not empty, then we need to process those nodes. So, what we'll do is we'll say
nodes. So, what we'll do is we'll say options list is equal to a list. We're
going to look at all the options that were returned from the LLM and we're going to convert them into a node that we can store in our database. So, we're
going to say for option data in node data dot options. Okay. Then we're going to
dot options. Okay. Then we're going to say next node is equal to option data dot next node. So for each option we
have this next node uh property which is telling us okay this is the next node that's coming up. So we're going to look at that and we're going to say if is instance all right and this is going to
be next node dictionary. So if it's a dictionary then what we're going to do is validate that it's correct. So we're
going to say next node is equal to story node llmodel validate. So same thing that we did
validate. So same thing that we did before of the next node. This needs to be is instance not in instance. Okay. So
we validate that it's correct. And then
we're going to say the child node is equal to cls.process
node. We're going to pass our database our story ID our next node and false.
Okay. For isroot.
So what's happening here is we're looking at our node, right? So we're
getting the root node to start. The root
node will have some options. Again, the
options are not already in the correct format. So what we're doing is we're
format. So what we're doing is we're saying, all right, let's look at all these options if there are any. We're
going to say, let's loop through all the options that were present on the node data that we passed in, not this node object that we created in the database.
Then we're checking, all right, is this next node actually valid for us to process? If it is, then we're going to
process? If it is, then we're going to process it the exact same way that we process the root node. So we'll come back and we'll repeat this function.
This is called doing this recursively.
This time it will not be the root node.
Again, we'll check to make sure it has options. If it has options, we'll
options. If it has options, we'll process all of those. And as soon as there's no options, right? So we've
reached those ending paths. Then we'll
stop processing. Okay, so we have the child node. We're then going to say the
child node. We're then going to say the options_list.append.
options_list.append.
And we're going to append the following.
We're going to have text, which is the We need to do this in a string, sorry, because it's Python. We're going to have text, which is the option data.ext,
and we're going to have the node ID, which is the child node ID. All right,
we're then going to come out of this for loop, and we're going to say node.options
node.options is equal to the options list. Okay. So,
what this is going to do is it's going to kind of create this mapping structure where for every single node, rather than storing all of the node objects in there, we're going to store a simplified
version where we have the text of what that option is and we have the ID of that next node so that we can always look up what the next node is, but we don't need to store all of the node data
in this kind of binary tree structure that would be created otherwise. I know
this is a little bit confusing, but what's happening is we have this root node. We then are essentially just
node. We then are essentially just populating the options of the root node.
For that, we're going to have the options looking like this, where each option includes the text of the next option and the node ID for that particular option. In our game, if we
particular option. In our game, if we decide to press on the next option, for example, we can load that next node and then we can do the same thing from there. we can look at the options of
there. we can look at the options of that node, see the text and see the node ID. Okay. And this creates a simplified
ID. Okay. And this creates a simplified way for us storing this in the database without having to store too much data at once. This is probably the most complex
once. This is probably the most complex part of this application is this kind of binary tree structure. But that's the whole point of me doing this is I wanted to make this more complex than something just like a super simple node app,
right? Okay. So, we're going to go
right? Okay. So, we're going to go dbflush down here. Notice this is outside of the
down here. Notice this is outside of the if statement. So after we've populated
if statement. So after we've populated all of the options, we're going to flush the database and we're going to return this particular node. Okay, so that's how this works. And this kind of
recursive call stack will uh go through and make sure that all of the children nodes are processed correctly. So that
is pretty much it. That's all we need for this function here. And now we just need to make sure that we get our OpenAI API key. So let's populate that in our
API key. So let's populate that in our environment variable file. So to do that, we're going to open up our browser. Let me open the correct one
browser. Let me open the correct one here. And we're going to go to
here. And we're going to go to platform.openai.
platform.openai.
Okay.
Uh API keys. All right. I'll leave this link in the description. Make sure that you're signed into OpenAI. I'm going to generate a new secret key here. I'm
going to call this adventure game. Okay. Let's copy that.
adventure game. Okay. Let's copy that.
Obviously, don't leak this with anyone.
And we're going to paste it here. Now,
in order to use this OpenAI key, of course, you need an account on the OpenAI platform and you are going to need a credit card on there because this will cost you a little bit of money.
It'll be like a few cents, especially if you hardly use it. It might not even cost you a scent, but if you want to use these LLMs, you know, they do cost money unless you run them locally on your own computer. Okay, so we have the story
computer. Okay, so we have the story generator. Uh, we now have everything
generator. Uh, we now have everything hooked up and we pretty much just need to make sure that we now call this function from our router. So if we go to our router story. py, what we're going
to do here inside of story. py is we're going to replace this kind of empty story that we had. So right here with the call to that story generator. So
we're going to go to the top of our program. We're going to say from
program. We're going to say from core.story generator import the story
core.story generator import the story generator like that. We're going to come down here and we're going to say story is equal to story generator
dot generate story. and we're just going to pass the theme. Okay. Then for the story ID, we're going to say this is story do ID. So we're actually using this value now. And then we will know
the story ID for this particular job.
And then we can then look at that story from our um what do you call it? API. So
that should actually be it. Uh that
should finish our backend unless I miss something which to be honest I probably did. And yes, there we go. We missed
did. And yes, there we go. We missed
this function. Build the complete story tree. However, before we do that, we're
tree. However, before we do that, we're quickly going to test the current state of the API. Then we'll write this. Then
the back end will be finished. Then we
can move on to the front end which is significantly simpler. Then we can do
significantly simpler. Then we can do the deployment and then we are good to go. All right. So let's run the API.
go. All right. So let's run the API.
Let's go to the back end and let's go uvun main.py.
uvun main.py.
Let's see if we got any errors. Okay.
Looks like it's loaded up here. Let's go
back to the browser to our documentation and refresh. And let's try to create a
and refresh. And let's try to create a new story. Okay. So from here we're
new story. Okay. So from here we're going to go try it out. Let's put a theme. Let's go like I don't know water
theme. Let's go like I don't know water park or something.
Okay. Actually, is water park one word?
I don't know. Two word. Water park.
Let's go with that. Let's go execute. Uh
and internal server error. Okay. So,
let's see what happened here. Uh we got a problem saying the table story jobs has no column completed at. Okay. Uh
interesting. Let's look at why we are missing that. Okay. So, the reason why
missing that. Okay. So, the reason why we're getting this is because we had changed this but we didn't delete the old database. So, what we're going to do
old database. So, what we're going to do is we're just going to go to our database.db here. Uh, we're going to
database.db here. Uh, we're going to just first turn off the server and we're going to just delete it. When we delete this, so just go ahead and get rid of that. Uh, now when we rerun the code,
that. Uh, now when we rerun the code, it'll make a new database and then it should have that column and then we should be good to go. So, let's go uvun main.py. It should now create that new
main.py. It should now create that new table for us. Okay, looks like it did.
We can now go here, refresh, and try it again. So, we're going to go try it out.
again. So, we're going to go try it out.
Let's go water park and execute. And
there we go. We get our job status.
Okay, so it says job ID is this. So now
we can actually uh test this out. So
let's go to job and let's check the job status.
Okay, so execute and it says completed at this time and it gave us an error.
Story generator missing one required positional argument. Uh can I open this
positional argument. Uh can I open this up? Let's make it a bit bigger. Session
up? Let's make it a bit bigger. Session
ID. Okay, so let's go fix that. We need
to go to story generator. So where do we call the story generator? We called that from story.py.
from story.py.
And right here, generate story. This
requires the database, the session ID, and the theme. So we're going to pass DB, session ID, and theme. And DB is right
here. Okay. So that should be good to go
here. Okay. So that should be good to go now. So, let's go back and let's try
now. So, let's go back and let's try this again. So, we're going to have to
this again. So, we're going to have to go to make a new story. So, refresh. All
right. We're going to go create try water park.
Okay. Execute. And we get our job ID.
And let's now go to look up the job status. Okay. So, try this. Paste this
status. Okay. So, try this. Paste this
in here. and the API key client option must be set either by passing the API key to the client or setting open AI API key environment variable. Okay, so it just looks like we're missing the open AI key. So let me look at why that's not
AI key. So let me look at why that's not loading. I think I already have an idea.
loading. I think I already have an idea.
We're going to close all of this. So
close all tabs. Going to go to core story generator. And I mean I know we
story generator. And I mean I know we have it from settings. So we might be able to just pass the API key directly here. But I think another way that we
here. But I think another way that we can do this is the following. We can
just go from enenv import load.env and
then we can just call the load.env
function and we can remove this line up here where we're importing settings.
This should load the environment variable file for this particular Python script and then give it uh give us access to that environment variable. So
let's see if that works now. So let's
shut down the program and restart. You
are going to need to shut it down in order to test this. We'll go back and we'll try again. Again, just debugging slowly with you guys. Okay, so let's go
here to create. Let's go to try it out.
Let's fix this to say watermark and execute. Okay, we've got our job ID.
and execute. Okay, we've got our job ID.
So, let's copy that and let's go here to job ID. Try it out. Paste it in.
job ID. Try it out. Paste it in.
Execute. And it is processing. Okay, so
this is good. So, it's still processing.
That's what we actually want to see.
It's going to take a second to process.
So what we can do is we can just keep executing here and we can just wait until eventually it says it's not processing. Now this is what's referred
processing. Now this is what's referred to as polling where you essentially pull or you keep hitting this endpoint until it gives you a particular response that you're looking for like now where it
says okay it was created at it's completed the story ID is one and this is the time that it finished at and you can see if you look at the time delta that it took like 20 seconds to finish
this. So now I can get the story ID
this. So now I can get the story ID because now it's done. And I can go just there's so many things open. I want to close these so it's a little bit easier
to see. Let's close that. We can go to
to see. Let's close that. We can go to stories. We can put in the story ID of
stories. We can put in the story ID of one and execute. And we get an error now because of the way that we're returning the story, which I'm going to fix in one second. But in a minute, this will
second. But in a minute, this will actually return us the entire story and we'll be able to view it. So now let's go back here. And you can see it says input should be a valid dictionary or object to extract. Okay, I know why
we're getting that. Again, we're going to fix that right now. Okay, so let's go now to that function I promised we would write inside of routers story.py.
So we have this build complete story tree, right? For right now, it doesn't
tree, right? For right now, it doesn't return anything, which is why we're getting that error. So now we're going to need to actually write the function out which will return the correct tree.
So what we're going to do is we're going to say nodes are equal to db doquery story node dot filter.
Okay. And we're going to say the story node dot story id is equal to the story id. And then we're going to get all the
id. And then we're going to get all the nodes. So rather than first we're going
nodes. So rather than first we're going to get all and sorry this should be story do ID. Okay. So we reference this.
Now this is going to look at all of the nodes that we created in our database from the story generator. It's going to get all the ones that reference this particular story. And then we're going
particular story. And then we're going to build this kind of tree that we can return to the front end. So we're going to say node dictionary is equal to an
empty dict. We're going to say for
empty dict. We're going to say for node in nodes and we're going to say the node I don't know why that's popping up.
The node response is equal to the complete story node response. Okay.
Inside of here we're going to populate the values. So ID is equal to node do
the values. So ID is equal to node do ID. We're going to say content is equal
ID. We're going to say content is equal to node dot content. We're going to say is ending is equal to node is ending.
We're going to say is underscore.
Ending is equal to node isisc_inning ending. We're going to say options is
ending. We're going to say options is equal to node dot options. Okay, so
we're just kind of wrapping this node information in this complete story node response that we want. So we're omitting anything that we don't need and we're just including the few values that we do actually want to return. We're then
going to say the node dictionary at node ID is equal to this node response. Okay,
so we're just again wrapping it in the correct response format. We're then
going to say the root node is equal to next and this is going to be node for
node in nodes if node is_root.
Okay, otherwise none. Now what this is going to do and sorry we just need to put parenthesis around this. Okay, so
essentially what we're doing here is we're looking at all of the nodes and we're just searching for a node that is the root node. So this is kind of a fancy way of doing it, but we're saying, okay, we want to get every node that's
in our nodes, but only if that node is a root node. So we're only going to get
root node. So we're only going to get one node ideally, and then that's going to give us a list. And then we just call next. When we call next, that's going to
next. When we call next, that's going to give us the only value that's inside of here, which is the root node. And then
we're going to return that as the root node. If for some reason there is no
node. If for some reason there is no node that's the root node, then we just are going to get none. Okay, that's what the next function does. Just grabs the next value from an iterable object. And
I know it's kind of fancy syntax, but but that's what we're going to use. Now,
we're going to say if not root node, then we're going to raise an HTTP exception and say 404 and then uh story has no root node or story root node not
found. And actually, we'll make it 500
found. And actually, we'll make it 500 because that means there's some error going on. Okay. Now, we're going to
going on. Okay. Now, we're going to return the complete story response, not node, but story response where we say
the ID is the story. ID. The title is the story.title. This needs to be an
the story.title. This needs to be an equal sign.
Okay, we have the session ID equal to the session ID. And this is the story session
ID. We have created
ID. We have created equal to story dot created at. And then
we have the root node which is equal to the node dictionary at the root root node id. And then all underscore nodes
node id. And then all underscore nodes equal to the node dictionary. So what
we're able to do here is return this completed story response that contains all the information we need in order to access the data about a story. Notice
that we include all of the story nodes.
So that even though we just start at the root node, we have all of the node information. And then based on the
information. And then based on the options that the root node has, we're going to be able to navigate to those other nodes from our front end. Okay. So
that should allow us to return the whole story. So let's save. Let's make sure
story. So let's save. Let's make sure our uh API is still running. Looks like
it is. Okay. Let's go here and just refresh this. And then let's try this
refresh this. And then let's try this again. So let's go to uh actually try to
again. So let's go to uh actually try to get a story. Let's try to get story ID 1 and execute. And you can see now that it
and execute. And you can see now that it gives us the entire story. So sorry if I go here to scroll through notice that we
have the title session ID ID created at root node and then it shows us the options. Okay, in this case there's
options. Okay, in this case there's three options. So we have node ID 2,
three options. So we have node ID 2, node ID 7, node ID 10, and then we have all the nodes. So we can navigate through all of the nodes in our story uh and kind of go between them. Cool. So
hopefully that is clear and the backend now is completely finished. The next
step is to write the front end uh that will integrate with the back end and then to deploy this. So let's go ahead and set that up. All right. So let's
move on to the front end. I'm just going to close this uh here. Okay. Let's close
the back end. And actually what we're going to have to do now is I'm just going to have to open up the project again. So from the root directory. So
again. So from the root directory. So
let's do that in this window. And now we can open up the back end. So let's go new. Actually we don't need to make
new. Actually we don't need to make anything new because we're going to use a command. Okay. Sorry. So, let's open
a command. Okay. Sorry. So, let's open up the terminal. We're inside of our root directory and we're going to use npm to create a new React project. Now,
in order for this command to work, you do need Node.js installed on your system. So, make sure that you have that
system. So, make sure that you have that installed. So, we're going to type npm
installed. So, we're going to type npm create vit.
We're going to type front end dash- template react. Okay, this is the
template react. Okay, this is the command that is going to create the front-end directory for us. We're going
to cd into the front end. We're going to type npm install and then we are going to install one more library uh which is called Axios and then the front end will kind of be set up and we can start
writing some code. Now I promise you the front end is significantly simpler than the back end. It's just basic user interface stuff. So, a little bit of
interface stuff. So, a little bit of React code and a little bit of JavaScript code and it's just going to be five kind of components or simple files that we write. Okay, so now that that's installed, we're going to type
npm install Axios.
We're going to use this to simplify sending requests. So, let's install the
sending requests. So, let's install the Axios library there. Give this a second to load up. And actually, while that loads up, we can open up the front-end directory. So, in the front end
directory. So, in the front end directory here, you'll see that we have a src folder. Okay, that's done. So,
let's close this here. We're going to go into src and we'll just start cleaning a few things up. So from app.jsx, we can just remove everything that is being used inside of here because we really
don't need this. Okay, so let's remove that. And let's remove the logos because
that. And let's remove the logos because we're not going to use those. And let's
close the fragment here. Okay, we can also get rid of this use state which we don't need. Okay, so that's the app. Uh
don't need. Okay, so that's the app. Uh
we're going to go to assets. We can just delete the assets folder right here. So,
let's go. Where's the delete button?
Like that. Okay. And we're just going to make a new folder called components.
Okay. So, inside of src, we're going to go new folder components.
Okay. We now have the components folder.
And inside of the components folder, I'm just going to make five simple components. So, I'm going to go new
components. So, I'm going to go new file. I'm going to do one which is
file. I'm going to do one which is loading status.jsx.
loading status.jsx.
I'm going to do another one which is story generator.jsx.
story generator.jsx.
I'm going to do another one which is the story loader. Okay. JSX.
story loader. Okay. JSX.
Let's make this a bit bigger so we can see the names of the files. We're going
to do another one which is the story game.jsx.
game.jsx.
And then the last one is going to be so we have loader generator game status and we're going to have the theme input.js.
jsx. Okay, so those are our five components. Now, I also just remember
components. Now, I also just remember there's one more library we need to install and that is React Router DOM.
So, we're going to go back to our front end and we're going to type npm install react router DOM. Okay, now we need that because we're going to have a kind of
URL route that you can go to to view the different stories. All right, so we're
different stories. All right, so we're going to start writing some of the simpler components first. So, we can go to loading status, which will be quite a simple one. We're going to write a
simple one. We're going to write a function here. We're going to say
function here. We're going to say function loading status. We're going to take in here the theme. Okay. And we're
just going to return some very simple uh divs. So we're going to say div class name is equal to the class name can be the loading container. And by the way, for the styling, I'm just going to
give you all of the styles, which I'll show you how to get in one second. We're
going to say H2 and we're going to say generating your theme story like that. And let's spell generating correctly. And we can do a
generating correctly. And we can do a capital on your. Okay. So, we have an H2. We're going to have another div. For
H2. We're going to have another div. For
the div, we're going to have class name is equal to, and this is going to be the loading animation. And then we're going to have
animation. And then we're going to have another div inside of here with class name equal to the spinner. And then
we're going to have a simple paragraph tag. And we're going to say that the
tag. And we're going to say that the class name is equal to the loading info. Okay. And here we're just going to
info. Okay. And here we're just going to say please wait while we generate your story dot dot dot. Okay. Okay. Last
thing is we're just going to export this component. So, we're going to say export
component. So, we're going to say export default loading status and we should be good to go. Okay, so that's it for this component. Now, I just want to bring in
component. Now, I just want to bring in the CSS before I forget. So, rather than me writing all of the CSS in this video, I'm just going to get you guys to copy it from the code link that's in the description. So, the GitHub repository
description. So, the GitHub repository there, because there are quite a few styles, and it just really doesn't make sense to write hundreds of lines of CSS.
So, I always do this in my tutorials, but what I'm going to get us to do first is go to app.tcss CSS and just replace the content of that with what you find from the GitHub repository. So if you go to GitHub, right, which will be linked
in the description for all of the code, you'll find this app.css file in this exact same directory structure. Simply
copy app.tcss and just paste it in here.
Now, we're going to do the same thing for index.css. Okay, so we're going to
for index.css. Okay, so we're going to take what's in the index CSS on the GitHub and we're going to paste that in the index CSS here in our local code.
Okay, you can just copy it right from there. So now we have all of the styles.
there. So now we have all of the styles.
We finished the loading status component. So let's close that one and
component. So let's close that one and let's start writing a few other ones that we can use. So let's go to the theme input. Another relatively simple
theme input. Another relatively simple one. This is going to be the component
one. This is going to be the component to get the input uh for our theme.
Right? So we're going to say import use state from React. And we're going to go and make a functional component. So
we're going to say function theme input.
Okay. We're going to take in an onsubmit function that we can call. Uh, and let's do the function. We're going to say
const theme set theme is equal to use state. And we'll make this an empty
state. And we'll make this an empty string. And then we're going to have
string. And then we're going to have const and we're going to say error set error is equal to use state. And then
again make that an empty string. We're
then going to say const. And this is going to be handle submit. Okay. For
this, we're going to take in E. We're
going to say E.prevent
default, which is going to prevent the default form submission behavior. And
we're going to say if the themetrim.
Okay, so if sorry, not theme.trim,
which means if you didn't actually give us anything other than blank text, then we're going to set an error and say please enter a theme name. And then
we're simply going to return, okay, in lower cases. Otherwise, we're going to
lower cases. Otherwise, we're going to say set theme or sorry, not set theme.
We're going to say onsubmit and then we're going to pass to this function the theme. Okay, so this is our handle
theme. Okay, so this is our handle submit function that we'll use from inside of the form. And then we're just going to write the form and that's it for this component. So we're going to say return. We're going to have a simple
say return. We're going to have a simple div. We're going to say the class name
div. We're going to say the class name is equal to and this is going to be the theme input container. We're going to put an H2 tag
container. We're going to put an H2 tag and we're going to say generate your adventure.
We're going to put a paragraph tag and we're going to say enter a theme for your interactive story.
Okay. Did we spell interactive? There we
go. Then we're going to have a form.
We're going to say onsubmit is equal to our on uh not onsubmit our handle submit function. So handle submit like that.
function. So handle submit like that.
Then we're going to have a div inside of this form. So we're going to say div
this form. So we're going to say div class name is equal to and this is going to be the input dash group.
Then we're going to have a simple input tag. So we're going to say input and
tag. So we're going to say input and this is going to be type equals to text value is equal to the current theme.
We're going to say onchange is equal to and this is going to be e so it's a function and then we're going to say set theme e.target dov valueue. Sorry it's
theme e.target dov valueue. Sorry it's
kind of populating this so it's a little bit difficult to read. Let's go down.
We're going to say the placeholder is enter a theme and we can give some examples. So eg like you know pirates
examples. So eg like you know pirates space I don't know medieval or something. So
medieval like that dot dot dot. Okay. Okay. And then we're going to have class name equal to error question mark
error otherwise none. So essentially if there's an error then we'll kind of like highlight the box showing that there's an error otherwise we won't. So this is our input field. Pretty straightforward.
We're then just going to have a simple error message. So we're going to say if
error message. So we're going to say if there's an error then we're going to have a p tag that has the class name error not message but error text. And
we'll display what the error is. And
then lastly, underneath the div here, we're just going to have a button. Okay.
And we're going to say the type is equal to submit, we're going to say the class name is equal to the generate and then btn. So button and then we'll just do
btn. So button and then we'll just do generate story.
Okay. So that's going to complete our uh theme input. And then we just need to
theme input. And then we just need to export this. So from here we're going to
export this. So from here we're going to say export default theme input. All
right. So that's the theme input class.
We are getting close to we finished the front end. So now what we're going to do
front end. So now what we're going to do is we're going to write the story loader component. Okay. So we have loading
component. Okay. So we have loading status theme input. This right here is actually going to load a story for us and then display it on the page. Okay.
So we're going to say import and this is going to be use state from react and we need to make sure that we have this inside of braces. We're also
going to import use effect.
Okay. From down here, we're going to say import use params as well as use navigate from React.
We're going to import Axios. So, we're
going to say import Axios from Axios and we're going to import the loading status.
import loading status from the loading status component. We're then going to
status component. We're then going to define the API base URL. So we're going to say const API_base URL is going to be equal to slash API.
Okay, we're then going to say function and we're going to say story loader.
And what we're going to do here is we're going to attempt to load in a story. So
to load the story, we're going to get the ID from the URL. So what's going to happen is to access this page, we'll go to like our, you know, front end link, whatever slash one or slashstory one or
something. Uh so what we'll do is we'll
something. Uh so what we'll do is we'll grab the ID and we'll use that ID from the URL in the browser to load the correct story from the back end. So
we're going to say const id is equal to use params. Okay, we're then going to say const navigate. This will allow us to navigate
navigate. This will allow us to navigate to another page because we're going to set up react router in 1 second. We're
then going to say const story set story is equal to use state null because we need to know what story we actually have here. We're going to have const loading
here. We're going to have const loading and then say set loading equal to use state and by default this will be true because when we navigate to this page
we're going to be loading the story. And
then we're going to have const error and then set error equal to use state and then null by default. Okay. Now what
we're going to do is we're just going to write a function that will load the story. So we're going to say const load
story. So we're going to say const load story is equal to an async function that's going to take in the story ID and then attempt to load the story for us.
Okay. So to load the story, we're going to say set loading to true.
We're going to say uh what is it here?
Set error to null. So we'll refresh the error if there was any one. And then
we're going to try sending a request to our back end. So we're going to say try const response is equal to await axios.
Okay.
We are going to use back ticks. We're
going to embed the API base URL. So
we're going to say API_base URL. We're going to say slashstories
URL. We're going to say slashstories slash we're going to embed the story ID.
So story ID like that and then slashcomplete.
Okay, so let's make sure the slashcomplete is outside of that embed.
Again, we'll just close this so we can see this better. Okay, so this is what the response is going to be. We're
sending a get request to this URL to get the story. We're then going to say set
the story. We're then going to say set story and this is going to be response data and we're going to say set loading equal to false. We're then going to say
catch any error. So catch err. We're
going to say if the error dot response question mark status is equal equal equal to 404. Then we're going to say
set error and we're going to say story uh you know is not found.
Okay. otherwise. So, else we're going to say set error, let's spell that correctly. And we're
going to say failed to load story. And
then finally, we're going to say set loading to false. Okay, so that's how we're
to false. Okay, so that's how we're going to load the story. Now, we're
going to just have a simple use effect hook, which just means as soon as this page is rendered on screen. So, let's go use effect. We're going to have a
use effect. We're going to have a function and our dependency array. If
the ID changes, okay, so the ID in our browser, then we will run this use effect. And what we're going to do is
effect. And what we're going to do is we're simply going to call the load story function with the ID. So that's
it. We have a use effect. We load the story. And then we just need to render
story. And then we just need to render the story on screen, which we're going to do in one second. So we're actually going to say first of all const uh create new story.
And this is just going to be a simple function. And what this function is
function. And what this function is going to do is it's just going to call the navigate function and just navigate us to the homepage of our browser. We're
going to say if loading. So let's put this in brackets here. So if loading then we're going to return
the loading status.
Okay. And the theme is going to be equal to uh what is it?
Story. Okay. For the loading status.
Okay. So if it's loading, we show the loading indicator. Then we want to have
loading indicator. Then we want to have kind of like an error page. So we're
going to say if there is an error, then we're going to return a really simple form, a really simple page. So we're
going to say div class name is equal to story loader. We're going to have
story loader. We're going to have another div, okay, with class name equal to the
error message. Inside this div, we're
error message. Inside this div, we're going to have an H2. We're gonna say story not found. We're then going to show whatever the error is on screen. So
we're gonna have a p tag with the error.
We then are going to have a button. The
button is going to be on click equal to create new story. And we're going to say go to story generator. So if the story fails,
story generator. So if the story fails, then we'll let them go back to the story generator page. Okay, that should be it
generator page. Okay, that should be it for the error. And then lastly, of course, we need to show the successful story. So we're going to say, you know,
story. So we're going to say, you know, if story. So keep forgetting we need the
if story. So keep forgetting we need the parenthesis because we're not in Python.
So if there's a story, then we're going to return a not dove, but a div. The
class name will be equal to the story loader.
And then inside of here, we're actually going to render the story, but I don't yet have the story component component written, sorry. So I can't actually show
written, sorry. So I can't actually show it. So we'll do that in a second. But
it. So we'll do that in a second. But
then from now we're going to say export default and this is going to be the story loader. Okay. So this component will
loader. Okay. So this component will allow us to kind of go to slash whatever the story ID is and then load that up.
So for now we just need to write the story generator and the story game component and then we should be good to go. So let's go with the story game to
go. So let's go with the story game to start and then we can have the generator. So we're going to import
generator. So we're going to import use state and use effect from React. And
this is going to just render a story for us, right? So we're going to say story
us, right? So we're going to say story game and we're going to take in the story and the on new story. So
essentially what happens when you press um you know you want to create a new story, where do we go? So from this component, we're going to say const and we're going to be storing all the information about the story. So pretty
much for the story, it's actually pretty simple. We need to have the entire story
simple. We need to have the entire story including all of the nodes. But if we know what node we're currently on, then we can display that. And then when you press another option, we'll just update the node that you go to. And then we
just have a few edge cases for if the game ends, for example, or if you win.
So you're going to see it's actually not that complex. So we're going to say
that complex. So we're going to say const um current node ID and then set current node ID. And we're going to say this is equal to use state. And for now,
this is going to be null. Okay. Then
we're going to have const current node set current node is equal to
use state and then null. We're going to have const options set options. So these
are the options we're going to be displaying for the current node, right?
It's going to be set state or not set state use state and by default it's going to be an empty array. And then
we're going to have const is ending set is ending is equal to use state false.
Then we're going to have const and this is going to be is winning ending set is winning ending is equal to use state false. And now we can go and write the
false. And now we can go and write the rest of the component. So we're going to have a simple use effect. So we're going to say use effect. Okay. For here,
what we're going to do is we're going to check if the story is changing. So,
we're going to say if story and story.root
story.root node. So, if we do actually have a story
node. So, if we do actually have a story that you pass to us here, then we are able to render it. And we can say const root node id is equal to story.root node
ID. And then we can set the current node ID equal to the root node ID. Okay. Then
we can do the dependency array of story.
So essentially anytime the story changes that we're showing here, we can change the root node ID and we can start rendering that entire story. Now we also need to have another use effect. So
we're going to say use effect.
Okay, the dependency array for this one is going to be the current node ID and the story. So essentially if the current
the story. So essentially if the current node ID changes, then we need to run this effect which is going to render new options onto the screen for us. So we're
going to say if current node ID and story and story doall nodes. So if
we have access to all of the nodes and we have a current node ID and the story exists then we can say con node is equal to story.all
to story.all nodes. Okay. And then we can set the
nodes. Okay. And then we can set the current node ID or we can get the current node ID. Sorry. We can say set current node is equal to the node that
we just loaded from all nodes. Then we
can say set is ending equal to node.is
ending. Right? So is underscore ending.
We can say set is winning ending to node.is
node.is winning ending. All right. And then we
winning ending. All right. And then we can continue. Now, why is this not
can continue. Now, why is this not highlighting? Actually, no. We're all
highlighting? Actually, no. We're all
good. We're going to say if now not node do is ending.
Okay. And node dot options and node dot options.length
options.length is greater than zero. So if we do have any options to display then we're going
to say set the options to be node dot okay node.options options otherwise
okay node.options options otherwise set the options to be an empty array.
Okay, so we're just setting up all the values that we need. All right, so now we're going to go down here and we're just going to write two very simple functions. First is going to be choose
functions. First is going to be choose option. Okay, we're going to take in an
option. Okay, we're going to take in an option ID and all we're going to do is say set current node ID to option ID. So
when we set the current node ID, what's going to happen is this is going to change. So it's going to then going to
change. So it's going to then going to update all of these fields for us. Okay.
Then we have a restart story. So const
restart story is equal to a function.
Here we're going to say if story and story.root
story.root node exists, then we're going to say set current node ID to the story.root node
ID. And that will reset the story for us and bring us back to the original state.
That's all we need in terms of logic.
Now we just need to write the kind of UI. So we're going to say a return.
UI. So we're going to say a return.
Okay, we're going to return a div. We're
going to have class name which is going to be equal to the story-game.
We'll have kind of like a header component here. So we'll say header and
component here. So we'll say header and class name is equal to the story header.
We then are going to just have an H2 which is going to put the title. So it's
going to say story.title. Okay. So we'll
display the title of the page. Then we
need the actual content. So we're going to have a div with class name equal to story dash content. Okay. Inside of here we're going to check if we have a
current node. So we're going to say
current node. So we're going to say current node and then we're going to render a div.
Okay. Let's close the div. For the div, we're going to have a class name equal to the story- node. All right. Then
we're going to show the current node content. So we're going to have a
content. So we're going to have a paragraph tag and say current node.content.
node.content.
So we can show kind of what it reads as.
Let me get rid of this brace by the way.
Okay. Then we're going to have all right are we ending? So if we are ending then we need to show them that we're ending.
So we're going to say is ending question mark. And then we're going to have a
mark. And then we're going to have a div. Let's close the div. The class name
div. Let's close the div. The class name will be equal to the story ending. Okay.
Then inside of here, we're going to have an H3. We're going to say is winning
an H3. We're going to say is winning ending question mark. We're going to say congratulations if it is. Otherwise,
we're going to say the end. Okay. So, we
either tell them, you know, if they're ending, we're going to check are they winning? If they are winning, we're
winning? If they are winning, we're going to tell them, okay, good, you know, congratulations. Otherwise, the
know, congratulations. Otherwise, the end. All right. Then underneath here,
end. All right. Then underneath here, we're gonna have is winning ending again. And we're gonna have a question
again. And we're gonna have a question mark and we're going to say you reached a
winning ending otherwise your adventure has ended. Okay, then we are almost
has ended. Okay, then we are almost done. We need to now put the next step.
done. We need to now put the next step.
So essentially, all right, if this doesn't happen, right, if we're not ending, then we need to display the options. So for not ending, we need our
options. So for not ending, we need our div and we need to close our div. Please
close the div. Okay, let's try to format this a little bit better. Okay, so
almost there. We have this div. For the
div, we're going to say class name is equal to the story dash options.
Okay, inside of the div, we are going to say h3. What will you do? Question mark.
say h3. What will you do? Question mark.
All right. Then we're going to have another div. For this div, we're going
another div. For this div, we're going to have class name equal to and this is going to be the options dash list. Then
inside of this div, we're going to render all the options. So we're going to say options do map. We're going to take in the option
and the index and then we are going to render the correct thing. So from here we're going
correct thing. So from here we're going to return. Let's make sure this is
to return. Let's make sure this is correct. So, we're going to say return.
correct. So, we're going to say return.
And this is going to be a button. The
button is going to have key equal to the index. And let's fix our formatting
index. And let's fix our formatting here.
Okay. Go down. Indent that one in. Okay.
Let me just fix this up here. That's not
what I want to do. Button looks good here. Okay. So, we have key index. So we
here. Okay. So, we have key index. So we
have on click which is going to be equal to choose option.
Okay. And we're going to call the option node id. Then down here we're going to
node id. Then down here we're going to have class name is equal to the option btn. And then inside of the button we're
btn. And then inside of the button we're going to display the option.ext.
Okay. And let's format that a bit better. So there we go. That is what
better. So there we go. That is what we're doing for the options. Uh that
should pretty much wrap up this. So if
they're not ending or if they are ending. Okay. Okay. And then outside of
ending. Okay. Okay. And then outside of right here. Okay. So this bracket that
right here. Okay. So this bracket that you see, I know it's a little bit confusing, but there's going to be kind of two divs at the end here. We're going
to do a new div and we're going to say class name is equal to the story controls. Okay. We're then going to have
controls. Okay. We're then going to have a button. And for the button, we're
a button. And for the button, we're going to have an on click which is equal to the restart.
Okay, story like that. Then we're going to have the class name equal to and this is going to be the reset btn. And then
here we're going to say restart story.
Okay. Then we're going to have another button, but only if we have a new story.
So we're going to go down here. We're
going to say on new story. So if they pass us that function, we're going to have an and and we're going to have a button. Okay, we're going to say on
button. Okay, we're going to say on click is going to be equal to the on new story. Then we're going to have the
story. Then we're going to have the class name which is the new story button. That is correct. Okay. Then
button. That is correct. Okay. Then
we're going to make the actual button here. So button and this is going to say
here. So button and this is going to say new story. Okay. Then lastly, we're
new story. Okay. Then lastly, we're going to say export default story game. All right, so at this point,
story game. All right, so at this point, actually, after all of this code is written, again, you can find it all from the link in the description, we're almost ready to test the front end. What
we need to do is just go to our story loader, and we're just going to import that component we just wrote. So, we're
going to say import the story game from the story game.jsx. And then what we can do is we can render the story game down here. So we can just say like this the
here. So we can just say like this the component story game we can pass our story and we can say on new story create new story where it will navigate us to that page. So for right now we're not
that page. So for right now we're not going to have the ability to generate a new story but we should be able to view a new story once we set up React Router DOM. Okay. So everything's done other
DOM. Okay. So everything's done other than the story generator which we'll do in a second. For now we're going to go set up uh React Router DOM. So to do that we're going to go to app.jsx JSX
and we're going to bring in the DOM components that we need. Uh and we're going to start setting up our routes. So
from here we're going to say import okay the browser router as router.
Our route or roots whatever you want to say our route from and this is going to be react router DOM. Okay. We then are
going to import the storyloader from dot /component/story loader. Inside of here, we're just going
loader. Inside of here, we're just going to create the react router DOM application which allows us to navigate between pages. So we're going to say
between pages. So we're going to say router.
Okay, so let's create the router. Inside
of the router, we're going to have a simple div. We're going to have class
simple div. We're going to have class name is equal to app dash container.
We're going to have a header.
Okay, for the header, we're just going to have an H1 and we're going to say interactive interactive story
generator. Okay, let's fix that.
generator. Okay, let's fix that.
Interactive story generator. We're then
going to have main. Okay, so this actually needs to be outside of the header. So, let's go down here. All
header. So, let's go down here. All
right, for main, we are going to have our routes. So, all of them need to go
our routes. So, all of them need to go inside of here. And then we're going to do our individual routes. So we're going to have a route and we're going to say the path is equal to /story slash colon
id which just means that this is a dynamic value. So you can pass the ID
dynamic value. So you can pass the ID the element. So element that we want to
the element. So element that we want to render is going to be the story loader like that. And for our route we're just
like that. And for our route we're just going to make this a self-encclosed tag.
So we're going to end it like that. All
right. That's all that we need for right now. Uh, last thing we need to do is
now. Uh, last thing we need to do is connect our API backend to the front end. In order to do that, we need to go
end. In order to do that, we need to go to this vit.config.js
file and we just need to set up what's known as a proxy so that anytime we send a request to the / ai route, it automatically gets forwarded to our backend. So to do that, we just add a
backend. So to do that, we just add a server option here. So we're just going to say server. Okay, we're going to say proxy.
And what we're going to do is we're going to do slash API.
And then we're going to have the following options. We're going to say
following options. We're going to say the target is equal to our backend. So
it's going to be localhostport 8000. And
what this is saying is anytime we go to / API, essentially just take whatever we're sending and send it to the backend instead. We're going to say change
instead. We're going to say change origin is true because we're changing the origin of this request. And we're
going to say secure is equal to false.
Okay? So that we don't get any errors.
So now whenever we send a request to slash API, it will automatically get forwarded to our backend server assuming that that server is running. So I want to run the front end now. So I'm going
to just go to the front end, type npm rundev. When I do that, it will start
rundev. When I do that, it will start the front end. Okay. Then I need to make sure my back end is running as well. So
I'm going to cd into the back end and I'm going to go with uh uvun main.py. Uh that should run for us.
main.py. Uh that should run for us.
Okay. And now what I'm going to do is open up my front end. So I'm going to go here. So what I'm going to do now is
here. So what I'm going to do now is open up this port. And you can see we have interactive story generator. And if
we go to slash one for example, uh or maybe slash2 or slash3. Uh it should be loading our stories, but it looks like it's not. And maybe that's because I put
it's not. And maybe that's because I put the wrong URL. I think it is. Yeah, we
need to go to slstory/ ID. So let's try this. /story slash 1 or not 12. Sorry.
this. /story slash 1 or not 12. Sorry.
Let's go to slash one. And if we open up the console, looks like we are getting an error here. Something about uncut error. Use params is not a function.
error. Use params is not a function.
Okay, so let's fix that up really quickly. So if we go to story loader
quickly. So if we go to story loader says use params. We may have spelled use params incorrectly or something. So let
me see. Ah, okay. So the issue is we're importing this from React, not React Router DOM. So we're getting a problem
Router DOM. So we're getting a problem there. So we just need to change this to
there. So we just need to change this to be from React Router DOM. the correct
import. Go back here and you can see now that it's actually loaded the story for us. So, like I said, we should be able
us. So, like I said, we should be able to load the stories, which we are. So,
we can kind of press on some of the options here. You can restart the story
options here. You can restart the story or we can go through this. Restart go
through the options. Okay, so it's loading, right? Stories are working. And
loading, right? Stories are working. And
I think we only generated one story, I believe. So, this is the only one that
believe. So, this is the only one that we can view. Uh, and based on kind of the options we pick, and you can see there's a lot of them. I can't quite get to the correct option here. uh we should eventually be able to see like yes we
got to the correct option that if we press new story it brings us back to the homepage. Okay so now all we need to do
homepage. Okay so now all we need to do is write the story generator uh which will allow us to generate the story and wait for it to load and then we'll be done with the whole application and
we'll be able to move on to deployment.
So let's tackle that. So now we're moving on to the story generator. Now
this component will be used for us to actually submit the form to the back end so we can tell it what the theme of the story is that we want to create. Now,
this is also going to be responsible for pulling the endpoint. You'll see how that works. But essentially what that
that works. But essentially what that means is we're going to keep calling the endpoint to see if the job is finished.
And as soon as it is, then we'll load the story and redirect them to that page. So, let's start with our imports.
page. So, let's start with our imports.
We're going to import use state and use effect from React. We're then going to import the use navigate, okay, from React Router DOM. We're then going to
import Axios. Okay, so we can send a
import Axios. Okay, so we can send a request from Axios. We're going to import the theme input component. So the
one that we wrote already here from theme input.jsx.
theme input.jsx.
And we're going to import the loading status. So we're actually going to use
status. So we're actually going to use the loading status now so that we can see if we're loading the um story or not. Okay. Now, similarly to before, we
not. Okay. Now, similarly to before, we need to have the API_base URL which is going to be equal to / API.
Now later or actually now I'm thinking we might want to just put this in a separate file uh because we're using it in multiple places so we probably shouldn't be repeating it. So what we can do is just make a new file here and
we can just call this something like you know util.js.
know util.js.
Okay. And let's just copy this variable and just export it here. So we're just going to say export const API base URL.
Uh so this way we can just import it from where we're using it. So let's now remove this and let's go import and then this is going to be the API base URL
from utils.js and let's copy that and go back to where we load the story. So here
where we have our API base URL we'll delete that and we'll just bring in this import. So now we're using it um and not
import. So now we're using it um and not duplicating the code. Okay. So now that we have that we're going to make a function. So we're going to say function
function. So we're going to say function story generator. Okay. In this function
story generator. Okay. In this function we're going to have some state. So first
we need the ability to navigate. So
we're going to say const navigate is equal to use navigate. We're then going to say const theme and set theme is
equal to use state like that. We're then
going to say const and let's fix this here. This is going to be job id and
here. This is going to be job id and then set job id is going to be equal to use state and then null. Okay, we're
going to say const and this is going to be job status and then set job status is going to be equal to use state null.
We're then going to have the error as well. So let's go here const and we're
well. So let's go here const and we're going to have error and set error is equal to use state null. And then
lastly, we'll have the loading. So we're
going to say const loading set loading is equal to use state. And then we will start again with false. Okay. So now
we're going to write a few of the different functions and use effect hooks that we need. So the first use effect hook here is going to be responsible for pulling the job. Uh and actually before we do that, let's write the individual functions and this will make a little
bit more sense. So let's start with const and we're just going to call this reset. Okay. Now this is just going to
reset. Okay. Now this is just going to reset uh kind of this form here and the job status that we're keeping track of in case there's an error and we need to retry. So we're going to say set job ID
retry. So we're going to say set job ID to null. We're going to say set job
to null. We're going to say set job status to null as well. We're going to say set error. Let's type this correctly. So error to null. We're going
correctly. So error to null. We're going
to say set theme to an empty string. And
we're going to say set loading to false.
So just in case there is any error, we'll just reset all of the state in this function. Okay. Then we're going to
this function. Okay. Then we're going to have a function that we'll call as soon as the job is finished and the story is ready to load that will just redirect us to that page. So we're going to say
const fetch story is equal to async.
Okay, we're going to take the story ID that we want to fetch and then we're going to try the following. So we're
going to say try and we're going to say set loading to false.
Okay, we're going to say set job status to completed and we're going to call navigate. Okay.
And we are going to navigate to uh let's go back ticks here slashstory slash and then we're going to put in braces here ID with a dollar sign. Okay.
So we're just going to navigate to this story ID page as soon as we try to fetch the story. Then we'll just have an
the story. Then we'll just have an accept or a catch block because we're working in JavaScript of course. So
we're going to say catch e and then we're going to say set error. And what
we're going to do is say in backtick failed to load story. So in case there was an error loading it for some reason here. And then we're going to go e
here. And then we're going to go e dossage. Now this should never happen
dossage. Now this should never happen but we're just going to handle the case in case. And then we're going to say set
in case. And then we're going to say set loading is equal to false. Okay. So
that's what we need for fetching the story and for kind of resetting the form. Now we need a function though
form. Now we need a function though that's going to allow us to pull the job status. So we're going to say const pull
status. So we're going to say const pull job status. This is going to be equal to
job status. This is going to be equal to async. We're going to take in the ID of
async. We're going to take in the ID of the job that we want to pull. And then
we're going to do the following. So
we're going to have a try. Okay. And
let's just write the catch block now so we don't get any errors. Okay. So we're
going to catch some error there. And
we're going to say try const response is equal to await axios.get.
axios.get.
Okay. Same thing. We're going to use back ticks and then we're going to use a dollar sign and I don't know why it's doing that. One second. Let's go here.
doing that. One second. Let's go here.
We're going to use the API base URL and then we're going to say slash jobs slash and then dollar sign and then in braces here ID. Okay, so what we're doing is
here ID. Okay, so what we're doing is we're trying to get this particular job ID and we just want to see what the status of this is. Okay, we're then going to say const status
story id error and we're just going to remap this to be called job error because we already have state called error is equal to response dod data.
Okay, what we're doing is we're just kind of dissecting this object here and we're getting out the different values that we care about. So we care about the status from the data, the story ID from the data, and then the job error, which
is going to be called error, but we're just going to remap it to a variable called job error so that it doesn't conflict with the error state that we have up here. Okay. Then we're going to say set the job status to whatever the
status is there. Then what we can do is we can check the status. So essentially
if the status is completed, it means that we can fetch the story and go to that page. Otherwise, we're just going
that page. Otherwise, we're just going to keep pulling the endpoint, right? So
we're going to say if the status is equal to completed then inside of here we can say fetch story and then story ID. Now we also will just check here. We'll say and
story ID just to make sure that the story ID exists because obviously if it's completed but we have no story ID then that's a problem. Okay. Now we're
going to say else if the status is equal to failed or we have some kind of error.
Okay. Okay, then what we're going to do is we're going to say set error and we're going to set the error to be the job error or if the job error doesn't exist, we're going to say failed to
generate story. Okay, then we're going
generate story. Okay, then we're going to say set loading to false so that at this stage here we'll stop loading and allow the user to try again. Okay, so
that's all we're going to do inside of here. So either it was completed to
here. So either it was completed to fetch the story or there was an error and we show the error and then the other state will be kind of like pending or processing. If that's the case, then
processing. If that's the case, then we'll just keep calling this function and we'll call this function from a different place which I'm going to show you in a second. Now, if there is an error, what I want to do is I just want
to say if e.response response question mark status does not equal 404. Then
what I'm going to do is set an error and I'm going to say in backtick failed to check story status colon and then we're
going to do a variable and we're going to embed e dossage.
Okay. And then same thing we're going to say set loading false. Now we hope that we're never going to run into these errors here but we're just making sure we handle all of the cases. So it's only if we didn't get some 404 error here that we're going to show the message.
Okay. And I just added another equal sign so that we got rid of that warning message. Okay. So that is pulling the
message. Okay. So that is pulling the job status. Uh now what we're going to
job status. Uh now what we're going to do is we're going to write a function that we can call to actually generate the story. And then we're going to have
the story. And then we're going to have a use effect hook where after we try to generate the story, we'll keep pulling for the new job status to see when it's created. So we're going to say const
created. So we're going to say const generate story. Okay. This is going to
generate story. Okay. This is going to be equal to async. All right. And this
is going to take in the theme. Okay.
Then from here, what we're going to do is the following. We're going to say set loading equal to true. We're going to clear any errors that did exist before.
So we're going to say set error is equal to null. And we're going to set the
to null. And we're going to set the current theme equal to whatever theme was passed here so that we can show it kind of while we're loading the story.
Then similarly to before, we're going to have a try catch. So we're going to say try const response is equal to await axios.post.
axios.post.
Okay, we're going to use the API base URL again. So API base URL and then this
URL again. So API base URL and then this is not going to be the SL jobs. This is
going to be slash stories slashcreate and then we will just pass the theme data like this. Okay, so that's going to send the post request which will start generating the story. Then we're going
to say const job ID and status is equal to the response data. And we're going to set the state. So we're going to say set
job ID to be the job ID. And we're going to set the job status, okay, to be the status. Perfect. All
right. Then after this, we're going to start pulling the endpoint. So we're
going to say pull job status. And we're
going to pass the job ID. So the idea is we send the request to start creating the story. We get the information back.
the story. We get the information back.
So we get the job ID that was created and the status of that job and then we start pulling immediately which means we're going to call this function which will then go to check okay is the job ready. Then we're going to set up a use
ready. Then we're going to set up a use effect hook in a second that will just keep calling this constantly until the job is done. Okay. Then we're going to have a catch. Same thing we'll catch on
E and we can say set loading is false and we can say set error and then same thing we're going to say failed to generate story and then we're going to put the e dossage. Okay, so whatever
that is. All right, so that is pretty
that is. All right, so that is pretty much it for the functions that we need.
Now we're going to write that one use effect hook and then we'll write the actual rendered component which is fairly simple. So we're going to say use
fairly simple. So we're going to say use effect here. This is going to be a
effect here. This is going to be a function. We're going to have a
function. We're going to have a dependency array. And for the dependency
dependency array. And for the dependency array, we're going to have the job ID and the job status. Now, this is important because if either the job ID or the job status changes, then that
means that we need to run this effect to see if either we need to call the poll job status function again or if we need to stop polling because we already have the um what do you call the result. So,
we're going to say let the poll interval equal or actually we don't even need to equal. We can just say let pull
equal. We can just say let pull interval. We can then say if the job ID
interval. We can then say if the job ID and and the job status is equal to processing which is what we called it.
So if we're still processing the job then what we're going to do is we're going to set an interval and keep calling pole job status. So we're going to say the pole interval is equal to set
interval. Now, setting an interval just
interval. Now, setting an interval just means that we're going to call a function um every few seconds or on some interval. In our case, it's going to be
interval. In our case, it's going to be every 5 seconds. So, we'll have a function inside of here. And what we're going to do is we're just going to call the poll job status. Okay? Like this.
And we're going to call it with the job ID. And then we're going to go here and
ID. And then we're going to go here and we're going to say 5,000, which means we're going to wait 5 seconds before we do this again. So, an interval, very basic, just means we're going to keep calling this every 5 seconds. That's it.
Okay. Now, what we need to do though is we need to return from this effect something that will essentially close the interval. So, we're going to return
the interval. So, we're going to return this function and we're going to say if pull interval. So, if this exists, if we
pull interval. So, if this exists, if we created this, then what we need to do is say clear interval and we're going to clear interval pull interval. So what
will happen now is when we leave this page, so when this component is kind of rerendered or deattached from the screen or the DOM, this function will be called and it will see if we had this polling
going on. If we did, then it will just
going on. If we did, then it will just clear it. So we'll stop polling. The
clear it. So we'll stop polling. The
reason why this will work is because as soon as the job is loaded and it's finished and completed, then we have the story. And if we look down here, we
story. And if we look down here, we redirect them to another page. So when
we redirect them to another page, then this page kind of closes, right? this
component is no longer on the DOM. So
that function will get called and then it will clear the interval. So we'll
stop hitting that endpoint. Okay, so
we're almost done. We're just going to go now and obviously write the um code that we want to show on screen. So we're
going to say return and we're going to have a div. The class name is going to be equal to the story generator. We're then going to show an
generator. We're then going to show an error message first. So we're going to say error and end and then we'll have a div. Okay, let's end the div. We're
div. Okay, let's end the div. We're
going to say the class name is equal to and this is going to be error dash message. Okay, and then inside of here,
message. Okay, and then inside of here, we're going to put a paragraph tag.
We're going to display the error and we'll just have a button that allows them to try again. So, we're going to say button try again. Okay, like that.
Let's remove this brace that we don't need. And for the button, we'll just
need. And for the button, we'll just have an on click. So we'll say on click is equal to and then this is going to be the reset like that. Okay, the reset function. So that's the error handling.
function. So that's the error handling.
Now down here we're going to show the input for the theme. So we're going to say if not job ID and not error and not
loading. So if we're not loading, if we
loading. So if we're not loading, if we don't have a job ID and if there's no error, then we're going to show our theme input. So we're going to say and
theme input. So we're going to say and and theme input like that. And then we're going to have an onsubmit. So we're going to say onsubmit is equal to generate story.
Okay. So we're going to call the generate story function as soon as we submit the kind of theme that we want to make the story for. Then lastly down here we're going to say if we are
loading so loading and and we're going to show the loading status and we can say the theme is equal to the theme and then we can close that like that. Okay
cool. So that should be good for now.
Um, again, what's going to happen here is we'll just display any errors, we will allow them to enter a theme if they haven't already done so. And if it's not loading, and then we should be good to
go. Now, lastly, let's export
go. Now, lastly, let's export default, the story generator, and let's make sure that we actually show it on the page by adding it to one of our routes. So, let's go to app.jsx. From
routes. So, let's go to app.jsx. From
here, we just need to add another route.
So, we're going to say route. Okay,
we're going to say path, and this will just be the default path. And then we're going to say the element is equal to and then we need to import this. So we're
going to say import okay the story generator from dot/component/story generator and then we're going to show
the story generator like that. Okay. So
we have the story loader, we have the story generator. Our entire front end is
story generator. Our entire front end is actually finished minus the deployment.
So let's quickly run this or let's make sure it's still running. Okay. So we
looks like it is still running actually.
So we have the front end and the back end. Okay, good. So, let's open this up
end. Okay, good. So, let's open this up in our browser. All right, let's refresh and let's go to the main page and see if this works. Okay, so we're going to go
this works. Okay, so we're going to go to slash. You can see it says generate
to slash. You can see it says generate your adventure. Enter a theme for your
your adventure. Enter a theme for your interactive story. So, let's go with
interactive story. So, let's go with something like pirates and let's press on generate. It says that it is loading.
on generate. It says that it is loading.
So, we're going to wait while it generates this story. And then we can check and we got an error message says you exceeded your current quota. Okay,
so this is actually an error that we're getting here from OpenAI. So, I guess I have been using it too much. So, I have this kind of quota error from OpenAI.
So, I am going to attempt to fix that.
But for you guys, this should have worked. Uh, and it should have loaded
worked. Uh, and it should have loaded the story. Anyways, I'll be back in 1
the story. Anyways, I'll be back in 1 second. Okay. So, unfortunately, I think
second. Okay. So, unfortunately, I think I'm just going to have to wait for this rate limit to wear off because I guess I was using this a lot for the tutorial.
So, for now, let's just move on to the deployment. I'm very confident that this
deployment. I'm very confident that this component is actually going to work and everything else is functioning and you can see all the code from the description. So, that pretty much wraps
description. So, that pretty much wraps up the coding component. Now, we're
going to go over to Coro and we're going to start setting up our deployment.
Okay. Now, as a reminder here, I partnered up with Coro for this video.
They're a really great platform for deploying your application. And what we need to do here is just make a new account. So, I'm going to leave a link
account. So, I'm going to leave a link in the description where you can get started for free. So, you can just press that button or just kind of like sign up for a new account with Coro pretty much.
And then once you do that, it should bring you to kind of a Coro platform, which I'm going to get to here in 1 second. Now, once you've created a new
second. Now, once you've created a new account on Coro, the next step here is going to be to make a new project. Now,
there's multiple ways to do this. And
first of all, you will need an organization. So, just make sure that
organization. So, just make sure that you make one. Again, it's free. You can
just set one up. And to make a new project, you can find this create button right here. You can also click on this
right here. You can also click on this arrow and click on create project. You
can go to overview and then find the button. Okay. So, we just got to make a
button. Okay. So, we just got to make a new project. So, let's go create
new project. So, let's go create project. What we're going to do for the
project. What we're going to do for the name is I am just going to call this my kind of choose your own adventure game.
Choose your own adventure like that. Okay, for the name it's going to go with choose your own adventure. And then we are going to
adventure. And then we are going to connect this to a GitHub repository. So
let's go back to the project and set that up. Okay, so we're going to open up
that up. Okay, so we're going to open up PyCharm. I'm just going to shut down the
PyCharm. I'm just going to shut down the services that I have running here. So
just shut all of this down. We're going
to CD into the root directory and we're going to create this git repo. All
right, so I just cleaned up the screen a little bit. What I'm going to do first
little bit. What I'm going to do first of all is I'm just going to make sure that I delete any.Git folders that exist in my project. So I'm just going to open this up in my file explorer. So I'm
going to go to choose your own adventure and I'm just going to make sure that I view any hidden files here on Windows.
On Mac it might be a little bit different. I'm going to click into both
different. I'm going to click into both backend and front end and if I have any.get git folders. I'm going to delete
any.get git folders. I'm going to delete them just because sometimes these projects automatically make git folders for you, but I want the git folder to be in this root directory, not in these
individual directories. Okay. Now, I'm
individual directories. Okay. Now, I'm
also going to go to my backend. I'm then
going to go to myv file in the back end and I'm just going to remove my openi API key here so that I don't accidentally commit this to source control so I don't accidentally put it
on git. So, we'll just remove that. But
on git. So, we'll just remove that. But
we will actually just upload this whole um env file to GitHub because it doesn't contain anything sensitive now that we've removed the OpenAI API key. Okay,
later we will add the OpenAI API key in kind of production when we're deploying this. Okay, so now everything should be
this. Okay, so now everything should be kind of set up here. Uh we're also going to go into front end and sorry, we're going to make one more folder here.
We're going to make a new file. We're
going to call this enenv and we're going to say debug is equal to true inside of this file. Okay. And actually, it needs
this file. Okay. And actually, it needs to be vitug.
The reason for this is we want to know if we're running the front end in production or not, so we can determine how we're going to call our backend because we're going to have a kind of different deployed backend when we're in
production or when we're deployed versus when we're running this locally on our own computer. So, I'm going to need this
own computer. So, I'm going to need this variable. And you know what? While we're
variable. And you know what? While we're
at it, what I'm going to do is I'm going to go to this v.config.js
js file and I'm going to modify this slightly so that we only use this proxy to our backend when we are running in kind of the local environment rather than when we're running sorry in the
deployed environment. Okay, so what
deployed environment. Okay, so what we're going to do here is we're just going to bring in something called load env. This is a function from vit and
env. This is a function from vit and what we're going to do is we're simply going to load our environment variable file here inside of this configuration.
check this value that we just defined and if it's true then we'll use this proxy otherwise we don't need to use it.
So in order to do this we're going to say export default define config but we need to actually change this to a function. Okay so this is going to be a
function. Okay so this is going to be a function and we're going to accept command and mode. Okay then what we're going to do is we are going to return this here. Okay, so we're going to
this here. Okay, so we're going to actually have a set of parenthesis or a set of braces, sorry, another set of braces, and inside of here, we're going to return this as kind of like a
configuration object. Okay, so I know
configuration object. Okay, so I know this is a little bit weird, but we're just returning this. We've turned this into a function. And now what we're going to do is start using some of the stuff here. So we're going to say const
stuff here. So we're going to say const env is equal to load env. And we're
going to load env Okay, so let's spell process correctly.
CWD, which stands for current working directory. Just means load the
directory. Just means load the environment variable file from this directory. We're then going to put an
directory. We're then going to put an empty string as the prefix. Okay, so
don't worry about that. Then what we're going to do is we're just going to log out the environment variable to make sure that it's working. So we're going to say console.log and we can just log envug.
Okay. And what we're going to do here is where it says server, we're going to just put the following. We're going to put a set of parenthesis. We're going to put dot dot dot before this. And we're
going to put env.vit_debug.
Okay. Is equal equal equal to true.
Okay. And then we're going to have and and then we're going to have this proxy config that you see here. So we're going to put proxy like this and close the
parenthesis. And sorry, let me just fix
parenthesis. And sorry, let me just fix the parenthesis like that. So I think this should be good. So essentially
we're going to do all of this here.
Okay. this proxy only if v2 debug is equal to true. All right, so just kind of copy this if you need to. Again, all
of this code will be available from the link in the description. So in case you're getting lost on kind of the parenthesis or what I'm typing here, but we just want to make sure we only include this if the um what do you call it? If we're running this in development
it? If we're running this in development mode. Okay, so that's it for the
mode. Okay, so that's it for the frontend changes, at least for right now. We will need to change something
now. We will need to change something later. And we're going to go to our back
later. And we're going to go to our back end and actually we're going to make one more change before we make the git repo.
So, we're going to make a new file here called requirements.
Okay requirements.txt.
Now, this is because Coro doesn't know how to use UV. So, we need to manually specify the requirements that we need for our Python project. So, we can go to the pi project.toml file and we can just
copy the dependencies that are listed here and we can just paste them inside of the requirements.txt file and we can just remove all of the uh what do you
call it here? quotations and the commas.
Okay, so you want your requirements.txt file to look something like this. So if
it looks like this, we're good to go.
Just make sure that you have requirements.txt inside of the back end.
Okay, so continuing from there, what we're going to do is make our git repo.
So we're going to close all of this.
Okay, we're going to open up the terminal and we're going to type getit dot. Okay, and sorry, lastly, we're
dot. Okay, and sorry, lastly, we're going to go to front end. We're going to go to getit ignore and we're going to ignore the enenv file that's here so that we don't accidentally commit it to
source control because we just want this when we're running the code locally.
Okay, so now we've initialized the new git repo and we're going to type get add dot. We're going to type get commit-m
dot. We're going to type get commit-m first commit. So we're just going to
first commit. So we're just going to save everything that we've done and then we are going to type get branch dash m with a capital M and then main. What
this is going to do is change the master branch to be called main. And then we're just going to set up a git repo uh like a public git repo access. Okay. So, what
I'm going to do is I'm just going to go over to GitHub. So, you guys are going to have to do the same thing for yourself. We're going to open up GitHub
yourself. We're going to open up GitHub and we're just going to make a new repo.
So, I'm going to go new new repository.
Okay, this is going to be super simple.
You're just going to put your name as the owner. For this, we can go, you
the owner. For this, we can go, you know, choose your own adventure AI or something and spell adventure correctly. We can make it public. You
correctly. We can make it public. You
can make it private if you want, but I'm just going to make it public cuz I want you guys to be able to view it. We don't
need to add anything else. And then we can just go with create repository.
Okay. From here, we just care about these two lines. So, this get remote add origin and this get push command. So,
we're just going to copy this get remote add origin command. Okay. which is going to allow us to add the remote origin here. Then we're going to type git
here. Then we're going to type git push-u with a capital and then origin main or actually let's just check to see if that's correct. No, it's a lowercase.
So get push u with a lowercase origin main and that's going to push our code up to the origin. So now if we go back to GitHub, we should see that all of our
code is up here. Okay, so we have it backend, front end, etc. We can add a readme file if we want and now we should be good to go. Okay, so now that we have the Git repo, we're going to open Coro back up here. We're going to go back into that create project flow in case
we're not already in it. I'm just going to type the name again because for me it kind of like sign me out of this. So,
choose your own adventure. Okay. Then we're going to
adventure. Okay. Then we're going to authorize this with Git uh or connect to a public repo. So, if you want to authorize with Git um that's just going to connect to your GitHub account.
You're going to have to go through that flow. In my case, it already connected.
flow. In my case, it already connected.
So, I'm going to choose my organization which is Tech with Tim. I'm going to choose the repository which I called choose your own adventure game uh with AI. So let's pick that. And then for the
AI. So let's pick that. And then for the project directory that can just be slash. Okay. And then we're going to go
slash. Okay. And then we're going to go ahead and press on create. So we've now kind of synced our GitHub repo up here to our project. Now what we need to do from here is we need to deploy two separate components on Coro. The first
is the front end, the second is the back end. And then we just need to connect
end. And then we just need to connect them together. So from here we're going
them together. So from here we're going to go to web application and we're going to press on continue with Git. We're
going to start by deploying the front end because that's the simplest and then we will set up the back end. Now we see it's going to automatically connect to your GitHub. So we have Tech with Tim.
your GitHub. So we have Tech with Tim.
I'm going to scroll down. I'm going to find the repo again. So let's go choose your own adventure AI. For the component directory, we're going to press on edit here and we're going to select the directory which is the frontend
directory. Okay. So just select front
directory. Okay. So just select front end and press on continue. Then we can change the name if we want. I'm just
going to call it front end. For the
build preset, we're going to select on React. Okay, for the build command,
React. Okay, for the build command, we're going to type npm run build. For
the build path, we're going to type /dis, which is where the distrib distributable file story will go. And
for the node version, we can just figure out what node version we're using on our own computer and use that. So to do that, you can just type node-v in your terminal. It's going to give you a
terminal. It's going to give you a version. So you can just copy that
version. So you can just copy that particular version, come back here, and just paste that in. So 20.17.0
and then press on create and deploy.
Okay. Okay, so what this is going to do is deploy the web application for us.
And I'm going to show you kind of how these this gets set up and how we can view the web application. Now,
obviously, it's not really going to work right now because we haven't connected it to the back end, but this is the first step. Just get the kind of front
first step. Just get the kind of front end up and running. Then we can deploy the back end. Then we can connect the back end. And you're going to see how
back end. And you're going to see how simple it is to do that here with Coro.
Okay. So, right now, what's happening is it's being created and it's being built.
Now the pipeline here that you're going to see in choreo is that you need to create build and then deploy. Now
fortunately coro can connect directly to your GitHub repository. So anytime you make a new commit, it can actually automatically build this and then you can come up here and deploy it whenever you want. So if you go to the side here,
you want. So if you go to the side here, you'll see that we're in this component.
Sorry. So we're in this component front end. Okay, we can have multiple
end. Okay, we can have multiple components which we're going to add in a second. And if we go to build, you can
second. And if we go to build, you can see that this build is currently in progress. Now, if you were to toggle
progress. Now, if you were to toggle auto build on commit, what will happen is that uh it's automatically going to build this anytime a new commit is made.
So, I'm going to do that. We're just
going to have to wait a second.
Sometimes it takes like 2 or 3 minutes to build. And then if we go to deploy,
to build. And then if we go to deploy, once this build is finished and one of them is created here, we'll be able to deploy it and then we can push that into production. So Coro gives us an
production. So Coro gives us an automatic staging environment as well as a production environment so that we can test what we're doing before we kind of promote our build to production. It's
very cool. We also have a lot of other features like insights, usage, etc. We can't see them right now because of how things are set up. Uh observability
where we'll be able to view logs and stuff. Again, it depends on what kind of
stuff. Again, it depends on what kind of project we're deploying here, but you get the idea. And then we have connections. So in a second we're going
connections. So in a second we're going to connect the um back end here to the front end using this connections tab.
Okay. So the front end has actually been deployed here and what we can do is press on this URL called web app URL and it should bring us here to the application. Now you can see it's
application. Now you can see it's actually working. It's hosted on this
actually working. It's hosted on this domain which you could share with someone else. But if we try to use this
someone else. But if we try to use this now it's just not going to work because we haven't connected it to the back end.
So you can see it says failed to generate story. Right. So what we want
generate story. Right. So what we want to do now is start deploying the back end cuz the front end's actually up. And
if we go to build for example, you can see the build was successful. We can
view the details of the build and kind of view the logs and stuff as well. Just
takes a second to load here. So
initialization, build etc. And if we go here to deploy, we can see the current build, we can see the deployment here.
And we can enable autodeploy on build if we want as well, which I'll do for right now. So it will automatically deploy for
now. So it will automatically deploy for us. There's all kinds of other stuff
us. There's all kinds of other stuff here. Shows us the web app URL, bunch of
here. Shows us the web app URL, bunch of settings. We don't need to go through
settings. We don't need to go through all of it for right now, but you get the idea. Okay, so that's the front end.
idea. Okay, so that's the front end.
It's up. Now, we're going to go to create new component here. So, from this component tab, and we're going to make a new one for the back end. However,
before we do that, there is one small thing that I need to add to the backend code. So, we're going to go back into
code. So, we're going to go back into PyCharm. I'm going to go to backend and
PyCharm. I'm going to go to backend and I'm going to make a new folder in here called Coro. Okay, actually Cororeo.
called Coro. Okay, actually Cororeo.
Now, this is a folder that we need. So,
Coro like that because it's going to be a special folder that Cororeo will look for to check the endpoint configuration for our backend. So, when we deploy on Coro, we need to specify what endpoints
we want to be available. So, what I'm going to do is I'm going to make a new file inside of here called component.yaml.
component.yaml.
Okay yl.
We're going to hit enter. And what I'm going to do is just copy in the configuration. And you guys can just
configuration. And you guys can just paste it from the description or you can simply write it out. So we're going to specify a schema version and then we're going to specify the endpoints that we need. Now we can give this a name. Okay,
need. Now we can give this a name. Okay,
we have a display name. We have a service and a port. This is the most important part. We specify this is a
important part. We specify this is a REST API and that this is a project public. Okay, so this just means that
public. Okay, so this just means that anyone will be able to call this API.
That's how we're going to set it up for right now. So we're going to save this
right now. So we're going to save this file. We're going to go to our terminal
file. We're going to go to our terminal and we're going to commit this to git.
So we're going to type git add dot git commit-m say add choreo folder and then git push origin main so that this actually goes
to the github repo. Okay. So now we have this cororo folder. We have our endpoint configuration. Again you can read more
configuration. Again you can read more about this from the choreo documentation but this is like the most basic version that's going to expose our different endpoints from our backend. And now
we're going to go here and we're going to go to service. Okay. So from service we're going to go continue with GitHub and same thing. We're going to find the same repository. So let's go and find
same repository. So let's go and find our choose your own adventure repository. So here you go. Choose your
repository. So here you go. Choose your
own adventure AI. We're going to select the component directory which is going to be backend this time, not front end.
Okay. We're going to choose Python as our build preset. For the language version, we're going to go with the newest, so 3.11. And for the command, we're going to go with Python 3 and then
main.py. Okay, so not UV, we're going to
main.py. Okay, so not UV, we're going to use Python 3. Then we're going to press on create and deploy. And we're going to wait for this to be deployed. So what
I'm going to do now is I'm going to close this and I'm going to start showing you how we interact with OpenAI and how we call that endpoint to actually generate the AI story. Now, we
could just add an environment variable as a part of our build. For example, we could go here, click on configure and deploy, and then we could add an OpenAI API key here. But I'm going to show you a more secure way to do this that
involves creating a connection within Coral. So what I'm going to do is I'm
Coral. So what I'm going to do is I'm going to exit out of this backend component so that I'm just in the root of my project. Choose your own adventure. I'm then going to go to
adventure. I'm then going to go to resources and where it says Genai services here. I'm going to make a new
services here. I'm going to make a new service. So Coro allows us to actually
service. So Coro allows us to actually make a Genai service to connect directly to OpenAI. So we can proxy all of our
to OpenAI. So we can proxy all of our requests through Coro and we can keep everything nice and secure. So I'll show you how this works. What I'm going to do is I'm going to click on register. I'm
going to go to OpenAI because that's the AI service that we're using for this video, but we could use Mistral, Anthropic, etc. So, let's go next. For
registering the service, we just need to give this a name. So, I'm just going to call this OpenAI. Okay, we can just give it version v1. And then for the service URL, just leave it empty. And then same thing with the summary. Okay, we're
going to go on next. And then what we need to do here is create an endpoint.
So, we're going to go new endpoint. And
we're just going to call this whatever we want. So, I'm just going to call this
we want. So, I'm just going to call this OpenAI. Okay. And then what we need to
OpenAI. Okay. And then what we need to pass here is an OpenAI API key. So, let
me grab one of those keys and I'll paste it here. So, I just copied one of the
it here. So, I just copied one of the keys here from the OpenAI website. You
can go to platform.opai.com/api
to get that. And then I've placed that here. Then, we're going to make sure the
here. Then, we're going to make sure the endpoint URL is this. And again, just name it OpenAI. And what we can do is press on okay. So, it adds the endpoint.
And then press on register. Okay. So,
this will take a second to register. And
once this is registered, we should actually be able to use this. So from
here, if you're curious, you can look at the service definition and you can see all of the different endpoints that you could call and how you would call them.
Now, in our case, we don't really need to use these. I'll show you how they work in 1 second. But for now, we can go back to services. And you'll see that it's not available. So what I'm going to do is I'm just going to click into this here, and I'm going to press the button
that says add to marketplace. When we
add this to marketplace, the service should become available. So if we go back to services, you can now see that this says available. And now we'll be able to connect to this as a connection.
Okay. So again, what this is effectively doing for us is it's creating some form of proxy where we can actually send a request to this the service created by Coro and then that request essentially
will be forwarded to OpenAI so that we have everything happening securely inside of our application. All right. So
what I'm going to do now is I'm going to go back to my backend component. And
what I'm going to do is I'm going to create a connection between my backend and between OpenAI. Okay. So to do that I am going to go to connections and I'm going to create a new connection to a
service. So from here we should see the
service. So from here we should see the OpenAI service. So I'm going to press on
OpenAI service. So I'm going to press on that here. For the connection we can
that here. For the connection we can just call this OpenAI connection. Okay.
And then where it says environment to endpoint mapping just leave this as it is. We don't need to change anything and
is. We don't need to change anything and we can go ahead and press on create.
Okay. So that's going to create the AI service for us. And you'll notice here that it gives us a few instructions on the right hand side on changes we need to make to ingest this service. So first
of all, we need to add this dependencies um I don't know kind of config to our configuration file. So what I'm going to
configuration file. So what I'm going to do is I'm going to copy this here. Make
sure you copy what you have on your I'm going to go back into PyCharm. I'm going
to go to my components.yaml and I'm just going to paste this underneath. Okay. So
your components.yaml file should now look like this inside of the cororo folder. So I'm going to save that. I'm
folder. So I'm going to save that. I'm
going to go back and then we can continue. Okay. Now, what's going to end
continue. Okay. Now, what's going to end up happening here is that we're going to have two environment variables which will be passed to our backend by default when the backend is deployed. Now, those
variables are going to be the following.
The Coro OpenAI connection OpenAI API key, then the Coro OpenAI connection service URL. Now, what we're going to do
service URL. Now, what we're going to do is we're just going to load in these environment variables from our Python script and we're going to use them when they're available to connect to OpenAI.
So, let me show you how this works. But
for now, I'm just going to copy these two lines right here where we're loading in these variables. And let's go back into PyCharm. Okay. So, what I need to
into PyCharm. Okay. So, what I need to do now is I need to go to core. I need
to go to my story generator.py. And
inside of here, let me just close my terminal. And then from get LLM, I need
terminal. And then from get LLM, I need to make a few changes so that I load this LLM differently based on if I'm in the development environment or the production environment or essentially the deployed environment. Now, if we're
just running this locally on our own computer, we'll just load the environment variable from that variable, which is OpenAI API key, which is contained inside of our env file.
However, if we're doing this in the deployed environment, then we want to use the environment variables that are passed to us from Coro, which will happen automatically when we deploy this. And I'll show you what that looks
this. And I'll show you what that looks like in one second. So, for now, what I'm going to do is I'm going to paste in these two values. Notice that since they're using OS, I need to import OS.
So, I'm going to go up here and I'm going to import the OS module. Okay?
Okay. And then we can close that up. And
you can see now that we have these two values. Now, essentially, I want to use
values. Now, essentially, I want to use these values when I initialize this chat openai, but only if they exist. So, what
I'm going to do is I'm just going to put a simple if statement. And I'm going to say if the OpenAI API key and the service URL, then what I'm going to do is I'm going to return chat OpenAI. Same
thing. The model is going to be equal to GPT-40- sorry, this is 40- mini. And then what I'm going to do is pass two additional parameters. The first is going to be the
parameters. The first is going to be the API key which is going to be equal to the OpenAI API key. And the next is going to be the base URL. Okay, which is
going to be equal to the service URL. So
all I'm saying is all right if I have these two variables that are passed to my backend. So if they exist then what
my backend. So if they exist then what I'm going to do is return a new instance of my LLM but this time I'm going to pass a different API key the one provided from Coro and a different base URL. When you pass a different base URL
URL. When you pass a different base URL this means we're going to route all of our requests through Coro so we can handle everything through that platform rather than directly calling OpenAI. And
this again is much more secure. Okay.
So, what I'm going to do now is I'm going to save this. And that should pretty much be all of our backend code.
So, from here, what I need to do is just commit these changes. So, I'm just going to say get add dot and then get commit-m I'm going to say update backend for
OpenAI service. Okay. And then I'm going
OpenAI service. Okay. And then I'm going to push these changes. So, get push origin main. And now we can actually
origin main. And now we can actually redeploy this. So, we can test
redeploy this. So, we can test everything out and make sure it's working now that we've connected to this service. Okay. Okay. So, what I'm going
service. Okay. Okay. So, what I'm going to do now is I'm going to go back here and I'm inside of my back end, right?
So, from my back end, I'm going to go to build and I'm just going to build latest. Now, I could also check this.
latest. Now, I could also check this.
So, auto build on commit. So, anytime a new commit is made, it will automatically build. But for now, we'll
automatically build. But for now, we'll just manually build the latest kind of update to our codebase. And then once that's built, we can deploy this. Okay.
So, you can see now it's cued. It's
going to take a second to build. So,
once that's done, I'll be right back.
All right. All right. So, now that this build is finished, I'm going to go over to deploy and I'm going to deploy this build. So, from this page, I'm going to
build. So, from this page, I'm going to press on this arrow and I'm going to go on configure and deploy. I'm going to press on configure and deploy. And you
should see now that there's two environment variables that have been created which are managed by Coro and handle our connection to OpenAI. So,
let's press next, next again. And then
we're just going to make sure that we uncheck the OOTH box here. So, we
shouldn't have any of these checked because we're not using the authentication. And then we can go ahead
authentication. And then we can go ahead and press on deploy. Okay, so that's going to take a second to deploy. You
can see actually it's deployed right now. That was pretty fast. And then from
now. That was pretty fast. And then from here, of course, we have the option to promote this to production cuz right now it's in the development environment. We
can manage our secrets. We can handle things like the rate limiting for example, cores, resiliency, etc. Right from this panel. Okay. Now, I do want to actually test this application quickly.
So, what I'm going to do now is I'm going to go to this testing section and I'll just go to console. And from here it should give me a link. So you can see that this is the link for my API. So
from here I can just copy this URL and you can just put it in your browser and you should see that you get some output.
So in our case we get detail not found.
That's fine. That's actually what we're expecting. And if we want to test our
expecting. And if we want to test our real API endpoint, we could go to something like / API slash uh what did we have? I don't know stories or
we have? I don't know stories or something slash one and we could see if that story exists. Now it's telling us not found. It's fine. Okay. We could go
not found. It's fine. Okay. We could go to slash job slash one. Same thing we'll get not found just because this doesn't exist. But we can just test it quickly
exist. But we can just test it quickly from the browser like this. But what I want to do now is I want to allow our front end to interact with this back end. So we are getting some result which
end. So we are getting some result which means the back end is up and running.
And now we just need to connect it to here. Okay. So how are we going to do
here. Okay. So how are we going to do that? Well, we need to find something
that? Well, we need to find something here from choreo. So if we go to our front end. So we got to change
front end. So we got to change components to front end. Now we're going to create a connection. So if you go to the connections tab, you can see connections. We're going to press on
connections. We're going to press on service and we're going to select our backend as the service. Okay. Now you'll
see that I I have multiple here just cuz I made one as a test beforehand. So just
click on probably the only one that you see assuming you're not already using Coro. And we can just give this a name.
Coro. And we can just give this a name.
So we can just call this backend. Okay.
We're going to do public and we're going to go no authentication. Okay. So we're
going to go with create here. And now
the connection has been created. Okay.
So from here, if you were using Coro's managed authentication, which we're not doing, then you would be able to see the steps for setting up the O config. In
our case, the only thing that we actually care about here is this service URL. So what we want to do is copy this
URL. So what we want to do is copy this service URL because what's really interesting about Coro is it's going to be able to deploy this backend on the same origin as our front end. That means
that we don't actually need to have a proxy or send a request to a different origin. we can just send it to the same
origin. we can just send it to the same URL and then include this slash path and then whatever the slash URL is that we want to go to from our backend and cororeal will automatically route the
request for us. So let me show you what I mean by that. Again, just copy this service URL. Okay, when we copy the
service URL. Okay, when we copy the service URL, what we're going to do now is we're just going to go to our front-end directory. So in our actual
front-end directory. So in our actual code this time, we're going to go to src and then util.js. Now, where we have the API base URL here, what we're going to
do now is we're just going to simply append this prefix to it. So, it says /coro API/ choose your own adventure/backend/v1.
Okay, we can simply add this URL here and then slap ai. Now, when we um kind of make the commit again, we'll be sending the request to this URL, which
will automatically forward it to our backend uh and it's going to work really well. Okay, so that's all we need to do.
well. Okay, so that's all we need to do.
Now you could add an environment variable here and do this a bit more dynamically because when you do make this change it's going to now stop working locally and just work in our deployed version but in the name of time
I'm just going to do this right now and you guys can adjust that code later on.
So I'm going to go get add dot I'm going to go get commit-m say changed URL and I'm going to go get push origin main. Okay, we're going to
push this up to the git repo. Then we're
going to go back here to coro. We need
to go to our front-end component again and we can get out of this now because the connection is made and we're going to go to build. Okay, now you can see the build is automatically triggered because we had auto build on commit.
Once the build is finished, I'll be right back and then we'll ensure that this is actually working and that it's sending the request correctly. Okay, so
this is deployed. So we're going to go to deploy now. Uh it says it's active.
Okay, 21 seconds ago. So let's open up the new URL and let's try this out now.
So really what we can do is we can just try by typing Dubai or something here.
And now it says it's generating the story. It's actually loading which is a
story. It's actually loading which is a good sign. So let's wait a second here
good sign. So let's wait a second here and see if this works and generates the story for us. Okay. So the story was generated. So let's go through it. Okay.
generated. So let's go through it. Okay.
And you can see we get to the end. Okay.
Restart the story. The end. I'm just
going I'm just kind of messing with it to make sure that it actually does work.
And we're good to go. And then if we want we can try to go to story slash2 or something. And you can see here we go.
something. And you can see here we go.
We have now the story that we generated previously. Story slash one. And this is
previously. Story slash one. And this is working. Okay. So there we go. We
working. Okay. So there we go. We
deployed the application. It is fully functioning. It is on choreo. Now you
functioning. It is on choreo. Now you
could share this URL with anyone you want and they'll actually be able to use this application. So that is pretty much
this application. So that is pretty much it when it comes to the deployment.
Obviously you can promote these both to production if you want to do that. if
you're going to use this in a more serious setting. And I just quickly want
serious setting. And I just quickly want to let you know that Coro does actually have this platform engineer view as well, which is a little bit more complex that we're not going to get into in this video. But if you wanted to set up
video. But if you wanted to set up continuous integration and deployment pipelines if you want to do things that are again just a little bit more complex, you can do that with Coro and you can have kind of different views for
different people. So like a developer
different people. So like a developer view, which is what we're using right here because we're developing the app, and then a platform engineer view where you get kind of this more granular control. Okay, so let's go back to the
control. Okay, so let's go back to the developer view. Now, of course, if
developer view. Now, of course, if you're having any issues, you can press on this runtime logs button inside of observability. But it's also a little
observability. But it's also a little bit easier if you actually get out of the component view and you go to the root project view. And from here, you go to runtime logs and then you're able to see the logs for both the front end and the back end. And if you want to sort by
just one, you can go back, front end, etc. So you can view all of your components here. All right. So that's it
components here. All right. So that's it for that. But the next step here is
for that. But the next step here is actually to deploy a database because currently the database we're using is just a file that's contained kind of on the back end when really we should have a separate production ready database
which we're going to deploy with Coro.
So in order to do the database deployment, what I'm going to do is get out of my project here. So I'm just in my organization view and I'm going to go to resources and then databases. Okay,
again you need to be in the organization view to do this. From here, I'm going to press on create to make a new database.
I'm going to use Postgress SQL. And for
the service name, I'm just going to go with database. Okay, you can obviously
with database. Okay, you can obviously call this something better if you want.
Now, you can go through and you can choose the options. You should get one free database with your Coro subscription, so you don't need a credit card to do this. I do have an upgraded Coro account. That's why you're seeing
Coro account. That's why you're seeing that I have the various pricing options.
But for you, you should be able to just create one for free. So, I'm just going to go with the cheapest option right here. I'll put this on Amazon Web
here. I'll put this on Amazon Web Services. and I'll put this located in
Services. and I'll put this located in the United States. Okay. Now, it's going to take a second to create this database, but what we're able to do is we're able to copy some of these values now and just change a little bit of code
on our back end in order to connect to this remote database. So, let's go ahead and do that. All right. So, let's go to PyCharm. Let's go to config. py because
PyCharm. Let's go to config. py because
this is actually where we're going to make a few changes. And what we're going to do is start importing a few things that we need and then making some changes inside of here to load the new variables uh that we're going to take in
as environment variables for our database. So what I'm going to do is I'm
database. So what I'm going to do is I'm going to say import OS and for my database URL I'm just going to make this equal to none by default so that it's an optional value. Now what I'm going to do
optional value. Now what I'm going to do is I'm actually going to add an init method on line 16 here. And essentially
what I'm going to do is the following.
I'm going to look and I'm going to check to see if I'm in debug mode. Now, if I'm in debug mode, that means that I'm running this locally. If I'm running it locally, then there's no change that we need to make because we'll only connect
to this database if we're running this in a production environment. So, if we are running it or not production, but in the deployed environment, then we'll connect to the remote database. So, what
I'm going to do for a net is I'm going to take in self and then asterisk asterisk values. Okay. Then down here
asterisk values. Okay. Then down here I'm going to say super_nit and then again I'm going to take in my asterisk asterisk values like that.
Okay, this is just to call the default constructor to make sure that I don't mess anything up. Then I'm going to say the following. I'm going to say if not
the following. I'm going to say if not self.debug.
self.debug.
So if we're not in debug mode, then what I'm going to do is I'm going to start setting a bunch of variables here that are related to connecting to our database. So I'm going to say the db
database. So I'm going to say the db user is equal to os.get env. And this is going to be db
user. Okay. Then I'm going to say the db
user. Okay. Then I'm going to say the db password is equal to os.db password. And
then it actually is autocompleting what I want. So let's just use that. All
I want. So let's just use that. All
right. So you can see that we're loading the database password, the host, the port, the name, and then we're creating a database string. So we're saying the self.database database URL which is the
self.database database URL which is the same as the one that we have up here is equal to and then we're saying that for this we're using Postgress and then we're having the user the password and then the host port name etc. And that's
actually the only change that we need to make because if we go and we look at our database config here our database setup you can see that we end up using this database URL to create this database
engine. So when we change the database
engine. So when we change the database URL string here, when we're not in debug mode and we load in these other variables, then we should just automatically connect to that database.
Okay, so we should be good to go there.
Let's just make sure that everything is set up. And let's also just make sure
set up. And let's also just make sure that we're calling create tables. So I'm
just going to go here and go create tables just to make sure that we are calling that. And it looks like we are
calling that. And it looks like we are calling that function. So we should be good to go. Okay. So now that we have that, what we're going to do is we're going to make a new commit and we are going to add this to coro. So we're
going to say get add dot and then we're going to say get commit-m and we're going to say setup databases. You can
call this whatever you want and then I'm going to go get push origin main. Okay,
so we've made the change to our back end and now we can push this up. All right.
So, what I'm going to do now is I'm going to get out of this and I am going to go and I'm going to take these values and I'm going to add them as environment variables for my backend deployment. So,
I'm just going to copy these down into a file. So, let me do that and I'll be
file. So, let me do that and I'll be right back. Okay, so I've got all these
right back. Okay, so I've got all these values copied down and now my build is actually finished because it was automatically building when I made that change. So, make sure that you build the
change. So, make sure that you build the latest version of your backend that you've updated to GitHub first. Then,
we're going to go over to deploy. Now
from deploy we'll see this build. Now
it's automatically deployed but it's not going to work yet because we haven't yet added the variables for our deployment or for our database. So what I'm going to do is go to manage configs and secrets here. Okay. So you can see that
secrets here. Okay. So you can see that kind of let me just click it again so you guys can see it. Manage configs and secrets. From here I'm going to go
secrets. From here I'm going to go environment variables and I'm going to start adding the different environment variables that we need. So the first one that we're going to have is going to be the DB host. Okay, it's going to be
this. I'll mark that as a secret. Same
this. I'll mark that as a secret. Same
thing for the port. So we'll go db_port.
Put that in. Mark it as a secret. We
then need the DB user. So we're going to go DB user. Put that in. Mark it as a secret. Then we're going to add another
secret. Then we're going to add another one. This is going to be the DB name.
one. This is going to be the DB name.
Okay, this is the default DB. Mark it as secret. And then lastly, we have the
secret. And then lastly, we have the password. So this is going to be the DB
password. So this is going to be the DB password. Put that in. and then again
password. Put that in. and then again mark it as secret. Okay, so I've added the five values now that we need related to our database. Again, these are just the same values that you had when you created the database. And now I'm going
to go save and deploy and deploy this again with those new values. So now once we're deployed, we should be connecting to the remote database versus connecting to the local database. And we'll go
ahead and test that out and see if it's working. And by the way, just make sure
working. And by the way, just make sure that the values that you have match the ones that you put in the config here.
So name port host password user.
Make sure that again, all those environment variables kind of match what we've put inside of here so that they're loaded in properly. And actually, I just realized that we need to make one more change here. We need to set debug equal
change here. We need to set debug equal to false. So, I'm going to add another
to false. So, I'm going to add another environment variable here. I'm going to call this debug. And we're going to make sure that this is false. And we don't need to make that a secret. We can just make it false because otherwise it's going to be equal to true. And that
means we're not going to connect to the new database. So, I'm going to go name
new database. So, I'm going to go name debug value false. Let's save and deploy that and try it again. All right, so it looks like that is deployed. And now you can see if I go to something like story/2, which was a valid story that we
have previously, it's no longer loading because it's not connected to the same database. It's connected to a new
database. It's connected to a new database. So that indicates to me that
database. So that indicates to me that this is working. Of course, we could test this further by making a new story like let's generate a hello world story.
This will take a second to run, but of course, we can test that and make sure that it's functioning. But I'm fairly certain that we are connected to the correct database. If we wanted to check
correct database. If we wanted to check that, we could look at the logs. And
also, if we go back here, we can see now that this story has been generated.
Okay, sweet. So, that is pretty much going to wrap up this video. I've showed
you how to deploy the front end, how to deploy the back end, how to connect to a database, and how to connect to the OpenAI service. Obviously, Coro is
OpenAI service. Obviously, Coro is capable of doing a ton of stuff and much more advanced things than I showed you in this video. But, I hope this scratched the surface and showed you first of all how to create a really
awesome application that is a great base and template and then how to actually deploy it. so real users can interact
deploy it. so real users can interact with it. Anyways, if you enjoyed the
with it. Anyways, if you enjoyed the video, make sure to leave a like, subscribe to the channel, and I will see you in the next one.
[Music]
Loading video analysis...