0% found this document useful (0 votes)
21 views51 pages

Mesh 3 Aref

The document discusses building a REST API with NestJS. It provides an overview of NestJS, explaining that it is a Node.js backend framework that embraces TypeScript and solves architecture issues with Express. It then discusses creating a new NestJS project to build a CRUD API for bookmarks, including setting up the initial project structure and creating an AuthModule.

Uploaded by

specialgroup2023
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views51 pages

Mesh 3 Aref

The document discusses building a REST API with NestJS. It provides an overview of NestJS, explaining that it is a Node.js backend framework that embraces TypeScript and solves architecture issues with Express. It then discusses creating a new NestJS project to build a CRUD API for bookmarks, including setting up the initial project structure and creating an AuthModule.

Uploaded by

specialgroup2023
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 51

s js is a framework for building scalable Node js web applications with TypeScript in this

course,
Vlad will teach you an S JS by teaching you how to build a REST API. Hello, and welcome to
this
course. My name is Vladimir, I am a full stack software developer. In this course, we are going
to build a CRUD REST API with Nest. Yes, our goal is to build something as close as possible
to a
production app, we will implement authentications and to and tests. And we will work with
relational
databases as well as with a modern development RM such as prisoner. My teaching style is very
hands
on. I'm a firm believer that you only learn by doing all my code is very easy to follow. But if
you're stuck, you will find a GitHub repository with the completed project in the description
below. Additionally, feel free to leave questions in the comment section or on my channel code
with
flood. I will try to answer to them as soon as I can. This course requires a basic understanding
of
JavaScript ESX and TypeScript. But don't worry if you don't have it, as I will explain everything
in
a step by step fashion. So what is nest GS? Not to be confused with Nex GS, which is a
What is NestJs
front end react framework. NES GS is a no GS backend framework. It fully embraces
TypeScript.
And it's also very different problem from let's say, express GS, it solves architecture,
the weak point of Express GS, see, if you're using Express GS, it's very unimplemented, it
doesn't give you a direction of how your product should be structured, or how you should do
things.
And while Express GS can work out very fine for small projects once your product starts to
grow.
And if you are not an experienced developer that knows how to structure it and scale it
properly,
it can quickly become a mess. But NES GS is not just another framework in the sense that it
does
not try to reinvent the wheel, it actually uses Express GS under the hood. So you can see it as
an abstraction of Express GS. And in a nutshell, it allows to create testable, scalable, and easily
maintainable applications for NSGs. Modularity is very important. It uses concept like
dependency
injection, and it is often branded as the Angular for backhand. So now that you have an idea of
what NES GS is, why would you use it? Well, structure, modularity, native TypeScript support
Why using NestJs
a lot of functionality out of the box easily integrates with Graph QL micro services REST API,
and has very good documentation on security. It also tremendously grows in popularity, which
is a
very important factor to consider. In fact, right now, NES GS is the most popular back end
framework
below express on the same level as Koa, Gs, and above, Adonis, loopback, feather Gs, and
even
Meteor. Plus, employers really love it because it brings architecture structure. And it's easier to
hire developers because they don't need to learn a new custom architecture every time they are
on boarded on a Express GS project. So after that long explanation, let's jump into our project
and
code our NSGs application. And in this project, we are going to build a API application
programming
What we are building
interface. This will be a CRUD API Lucia's very common terms for API's and the resources
that
we're going to either update, read, delete, or create will be bookmarks. So without further ado,
let's get started. Before we start, we need to have no GS installed. I'm using version 16. But
NestJs project setup
any future version with a long term support should work. You also need to have Ness GS
installed.
So you need to install their CLI globally and yes, yes CLI. Then we can drop into our terminal
and create our new project with Nest new. And let's name it nest GS API tutorial.
The CLI will ask you to choose a package manager. I usually like to go ahead with yarn. Perfect.
Now
our product is installed, we can navigate into it. I will open it with code VS code. But you can
of
course use any editor of your choice. And let me make the code bigger. This is the VS code
Visual
Studio Code. The theme I'm using here is material theme. And the icons you see here is
material
icons. You can download them and install it here via their extension menu. So NES GS has
created a
starter project for us. It has a source folder where our files are, it has a test folder,
where it keeps the end to end tests. And it has some configuration files here. Let's first
clean it up, we don't need the app controllers back. And we don't need the app dot service.
And nor do we need the app dot controller. And since all those files were imported into the app
module, we need to go and delete them here.
So for NES modules are very important. You see that this file, the global file is called
app.module.ts. This is similar to something that we have in React, where we have an app.gs
or app.ts file. So this will be the main module or the app that will import other modules. So I
have
talked about modules, but I haven't introduced what is a module. So for any concept related to
Modules
nest, yes, you can just go into the nest Yes, documentation, and it's on the nest years.com.
And you click on documentation, and you'll have this page where you will have a lot of docs
on
any concept that you want. And for instance, here module, it's written that it's a class
annotated with the module decorator. And in fact, we have a class called app module that has
been
annotated with the module decorator, if it's not annotated with the module decorator for net,
yes,
it's not a module. And if you don't know what a decorator is a decorator is just a function that
adds some metadata to the to the current class or function that it is
kind of decorating. So just adds more property to to that class. So if we go back to the modules,
documentation, we see that a module can import other modules. And it is the case for the
application module. So the main module, the root module, it can, for instance, import User
Module,
or there's model chat module, any module that you want. And every module can itself import,
also controllers, and providers, we are going to talk about those a bit later. But for now
know that a module can import other modules. And usually I like to organize my modules into
feature modules. So if I have an application, where, for instance, we have bookmarks, well,
what would be the part of the like the application, right, you will have the authentication logic
where a user can create its profile login sign up. So for me to be ot module,
then you will have maybe user module that will handle the logic related to the user will have
also the bookmarks module, and maybe the database module. So you see, you organize your app
into
those features, right, and you break it down into modules. And the result is that it makes your
app
much more easier to handle. And to reason about, let me show you a another project I'm
working on,
just to illustrate the structure. So I have a source folder. And you see I have an old module,
category, common post Prisma, which you see all around for the database user. And if I click
here,
you see that you have some data, you have the module as as, as we expect, and we have some
providers, those we are going to talk about it a bit later. But this is an example of a kind of
production ready, project. So you guys can see the organization and the cost structure in a
nutshell
of of next year's projects. Okay, so we now know that modules organize your app, right? And
they
are declared with a class annotated by a decorator module. So let's go ahead and declare
another
class another module to break down our application into those small sub modules. So we know
that we
are creating a bookmark application, what would there be in a bookmark application, while I
have
already touched upon the authentication. So in our CRUD API, I want to be able to create users
and
to allow them to log in, right, so we will have an authentication module. But let's go ahead and
create a auth module. And usually the convention is that you need to put the modules to create
in a folder, and the only time that you can not respect this convention is when you're using the
main app module component that will import all the other kind of modules or components. So
let's go ahead and create the folder here and let's name it out. And at first, I'm going to show
you how to create it manually when you're using a framework
for the first time. It's Better to actually write everything on your own and not use any
generators.
Because nest yes has a generator has a CLI command that allows you to kind of automate that
creation.
And we're going to see that in a moment. But at first just write it by hand. So we get a bit of a
muscle memory going on. So we create a folder called auth. Right? What do we know about
modules,
we know that it should be it should be a class that should be annotated by decorator module.
So let's create a file. And we just respect that convention that if we have a file, we try to add
the its kind of function to its extension. So this is an extension right.gs is an extension.ts is an
extension dot, CPP for C++ would be an extension as well. So here, it's since it's module, it
will be.out.module.ts. And we're using TypeScript for all the products. So it's Ts, and we need
to
create a class. So it will be od module, right. And we need to decorate it with a decorator,
called module. And that decorator comes from the Nash Yes, common
module, and we just need to open it provide an object. And that's it. That's the minimum
requirements to create a module. So all we need to do is to save. And that's it, we have created
a
module, and we have forgotten something important, we have forgotten to export that class. So
if we
don't type export, this class will be available only inside that module only inside that file,
when you export it to allow other files of our application to use it. So we go back to app
module. And now we can just import our module. And we can use TypeScript intelligence to
help
us to find our modules and files. So here, it knows that the auth module exists inside out. So
all we need to do is to click here. If it doesn't appear, you can always do command.or control
dot,
and it will propose you an option to import it from the odd module file. And here it is to verify
that our logic works, let's go ahead and launch our NES GS application. So the bootstrap logic
is inside the main.ts. It looks a bit like what we could expect from a Express GS application,
right? So we have the app that is being instantiated here. And then we launch a server
on port 3000, I will use Port 3000 333, because usually, the port 3000 is reserved for react in
my development pipeline. So I prefer to use 3333. I save it. And now I open the terminal
in the project. Let me clean that out. And all I need to do is I need to do yarn, start Dev. And
all those scripts are actually defined here inside the package that Jason, we have start. And we
have
start Dev. So the reason why I'm using start Dev and not start is because start dev What are
files,
and recompile the code when needed. So if we do yarn, start Dev, it will launch NES Gs,
and we don't have any errors, which means that it's very good. Everything has compiled
correctly,
NES GS has generated a dist file, so a kind of output compiled JavaScript file from TypeScript,
we don't need to really worry about it. It's just how naseous works. And that's it, we have our
API working. So let's clean that up and, and come back here. So modules allow us to break our
app
down into smaller components that we can easily manage. Let's go ahead and create the user
module
and the bookmark module. In this case, I'm going to use the nest CLI to create those modules.
So
you have an idea about how it works. Just open the terminal, and we'll going to open a new
terminal
session. And we execute nest G for generate module. And we name it user and nest will
generate
the module for us and automatically imported into the app dot module for us. So we have a
folder
user, with the user, that module class here. And we do the same thing with a bookmark.
Right. And it also generates a bookmark module for us. And if we check our terminal,
let's kill this one. We see that the application is still logging. If I for instance, press Ctrl
save, you see that it is recompiled so everything is working correctly. Now let's add more logic
to
our application. So let's add the login logic. So when we build a nest GS application will
usually
separate our logic into controllers and service, we can refer to the documentation. And
basically
it says that controllers are responsible for handling incoming requests and returning responses
for the clients and providers or services are responsible for executing the business logic.
So we separate our logic into controllers and service providers to be able to, to simplify
what we do, right. So let's go ahead and implement it. We go now into ot module, and we create
a
Auth module
service and a controller. So again, the same can be done with the NES GS CLI, and we're going
to
do it by hand the first time. So let's create a controller, dot Ts, and ot.service.ts. If we
go to the controller, we need to create a class class or controller, we need to annotate it with
the decorator controller. So nest years knows that it's a controller. And we need to do the same
for
that service class service. For the service, we need to annotate it with the decorator injectable.
And that just means that it's going to be able to use the dependency injection that NES GS
uses under the hood. But more on that a bit later. And we need to of course,
export those otherwise, we will not be able to import those classes in our application.
And since we have added that, we need to add that to the module as well. So controllers, or
controller, and providers, or that service, let's check our application if it's running correctly.
Okay, no errors. Cool, running good. So I've introduced the dependency injection, but I haven't
really said what it is, you can of course Google it online. But let's go ahead and introduce it
with a concrete example to see how it works. So we have a auth service class and we have a
controller
Christ, right? What is happening usually is that the controller will need to call the service. So
the controller will receive a request from the internet, for instance, a post request
asking to login a user, right. And then it's going to call a function from the audit service
class and return its result back to to the client back to the browser. But to do so, ot controller
will have to instantiate a auth service class, right, because in the end, it is JavaScript.
So if you want to create that instance, for instance, it will be service is equal,
new or service, right? To avoid doing that. To avoid having to manage where it's created, and
who
manages it all, we use dependency injection, that means that instead of our controller to
actually
have to declare it, for instance, to actually have to do something, something like that,
give me the service here, give me that, an instance of that class. And I don't care how to
instantiate it, just give me an instance. And this is how you do it in SCS. Private or service.
Service. And that's it. Nice, yes, we'll handle on itself, How to instantiate the auth service,
and how to pass it to you in that old controller file. So you don't need to really worry about and
private here, if you have never seen that notation, it just means it just means that
and then have to do something like this, or services equal or service. Instead of doing that, you
just write private, and you scrap off this. That's that just a shorthand.
And if we check our code, we see that it is being executed. So everything is okay,
everything is compiled. And now if we want, like if we put a function into the
odd service called test, this function is not going to return anything. If we want, we can call
this function directly like that. Right? So that's dependency injection in a nutshell,
if you want more information, just Google it. There's a plethora of explanations online,
Dependency injection
how it really works. This is how Nigeria's use it and basically it allows us to not handle
dependency management. And it's just easier to work with when we have dependency
injection.
So let's come back to the auth service and create two functions because our auth module will
manage
to functionality, login and signup. So let's go ahead and create a function, login and sign up.
Right. And that's all is going to do for now, the auth service will store the business logic
Auth controller
right now there is none. So let's come back to the controller and create two endpoints,
an endpoint for login and an endpoint for signup. So the way to do it again, is using decorators,
it just simplifies how you write your logic. So we expect a post request on signup and a post
request on login. So we create a function, let's name it sign up, right, and sign in.
And, and to make it a route, we just need to annotate it with the post decorator that comes
from Ness GS common, and let's call it signup. And sign in. And since we are in the odd
controller,
it's usually a good practice to put a global prefix route called odd. So when we are going
to call that route, we're going to do a post request, have to out signup, right. And if we call
that route, it's going to be that that request. Let's just return a string for now. I am sign up.
And here I am signed in. Let's verify that the product
is compiling all good. And let's go ahead and test our API with insomnia. So insomnia is a
HTTP client, similar to postman, you can also use postman if you have if you're used to it
in some way, just a bit more minimalistic and easier to use. So let's go ahead and test our
endpoint. And if you remember, our old controller was on the endpoint, odds sign up, and
right. And we do the Send Request. And we see that we have response, right, I am sign up.
And that is basically what we have written here, right. And if we launch and free request sign
in,
it's going to print us the other response. One interesting thing, though, is that if we look
at the headers, we see that it is powered by Express. So as I said, In the beginning, an S
Gs use Express under the hood, you can replace this by another framework called fast Defy.
But
for most use cases, you will probably use Express, and that's going to be just fine. That also
means that everything that you know about Express, you can access it. So this is the
nice thing about NES GS, if you like Express Gs, and you need more structure in your app,
you can use NES Gs, and still have access to that express ecosystem if you need. One other
thing
that is very interesting is that Express has sent us a content type of text HTML, right? So
this is a text. So NES GS will automatically convert the data type based on the return. So
here, it's a string, if I send a object, for instance, message, hi, or Hello.
And we send it to sign up. We now have a object and the header is application that Jason So
that's
a very handy, you don't need to worry about sending the right data type, ness, GS will
do it for you. Let's go back to our app. And let's create more logic. So as I said, In the
beginning,
the controllers will handle the requests. So it will fetch the body of the request if needed.
It might check some headers or any work related to the request. Regarding the business logic,
the
actual execution, we offload that to the service. Usually the pattern is that we are going to
create
the same function on the service side. So let's go ahead and do it. We have a function now sign
up
and sign in. Right? And instead of returning something from here,
we can actually call the service and we can put that message here. Sign up.
Hello. And that's right. I have signed up. And here I have signed in right there things
correct and let's call those functions from the service, these odd service that sign up and this
out service that sign in. So what it does is that it allows to keep our controller clean,
and only busy with a logic with to the requests, while our while our service will be busy with
the
business logic like connecting to the database, editing the fields, etc. So let's go ahead here
and do it again. And we see that it works as well. And if we launch sign in here, it also works.
Now we know how a controller works, we know that it is responsible for handling requests.
We know how a service works or a provider, it is responsible for handling the business logic.
But what would be a business logic without connecting to the database, right. So we need
to somehow set up a database and connect to it. And by that time, I guess you have noticed it,
my style of teaching is to introduce concepts bit by bit. So now we need the database.
Let's go ahead and set up a database. For that we are going to use Docker,
Docker is an amazing tool that will allow us to run our database directly on our computer,
but so we don't have to install it. So it will run in a containerized environment I have Docker
already installed, you can go ahead and install it. And one quick way to check if it works. You
just write docker ps and if it inputs something that means that it's working, my version is
is version 20. With that being said, let's go ahead and and create our database. So when I work
with databases, I prefer to use Docker Compose, Docker compose will automatically allow me
to
Setting up postgres in docker
spawn the Docker containers and to destroy them. So it's very handy. Let's go ahead and
create Docker hyphen, composite Yamo. So here's the configuration of our Docker Compose, I
use
version 3.8. In the services we define our Docker container that we want to run is going to be
dev
dB, this is just the name I give it to, to it. The image I'm using is Postgres 13. So we are going
to use the relational database Postgres version 13 is going to be exported on the port 5334. So if
we
connect to Postgres 5334 on our computer, we'll be able to access it. And the environment
variables
I'm going to use is the user who is going to be Postgres, the password 123, as you have seen,
very secure, and the database name will be nest, and it's going to be on the network Free Code
Camp
that is defined here. That's about it. So to to launch our database, we go back to our terminal,
we open a new one. And we do Docker compose up, we choose the DB that we want to run. So
it's dev dB,
the DB, and we will run it in background. So with Duke and that's it. And we if we go to docker
ps,
we see that we have a container now that is running. And we can even access a lock if we
want.
So Docker logs. And it's written that the database system is ready to accept connection,
it has deployed, it has compiled everything is running correctly. Cool. Let's go back to our app.
So we have the database running in Docker. But how do we access that there are libraries like
SQLite,
typo RAM mongoose, that allow you to connect to the database. In this example,
I'm going to use a new IRM that I really love and I have been using for over a year now. It's
called
Prisma. So you can access the website on Prisma. That IO, what Prisma is, in a nutshell, is that
it's kind of a query Query Builder, but it's so easy to use. So basically, you define a model,
Setting up prisma
for instance, that will be a post with ID title and all the fields that you want in a database.
And you can get them from your JavaScript code or your TypeScript with that syntax. And
we're
going to explore it straightaway. So with Prisma, we are going to create the database
connection
logic the database module in a way. So that means that we need to create another module
since the database module will be able to use to be used by auth module because you need to
login sign up. It should be used by bookmark module and even by user module. So it's a new
feature of our app for Prisma. We need to have two libraries installed. The first one is Prisma
CLI,
it will allow us to create Our schema and run migrations deploy the migrations into the
database. So it's more of a maintenance library. And the other one is the Prisma. Client. So
Prisma
has different clients, the one we're going to use is for JavaScript. So let's install Prisma CLI
yarn add, and we're going to add it as a development package Prisma and yarn add
Prisma client for the client. Okay, let's clear that out. With that being installed,
we now have access to the Prisma. CLI. Let's run it and px Prisma in it. And it's going to
generate
several files for us. First of all, it generated this dot env file for the environment variables.
And it just created a Postgres connection string by default, that we're going to change
a bit later. And it also generated that Prisma folder in the root directory. So what it has,
it just has its schema. And this is where we're going to declare our models. See, when you use
the
library like type or M, you will create entities and you will place them inside your logic for
Prisma. There's only one place that you need to handle your your models a bit like with Graph
QL,
actually. So you would declare those structures that will describe the shape of the data that
will be in your database right here, it says that it's going to use the Prisma client GS library,
which we have installed, the provider will be Postgres, but you can have any other library
Prisma supports my sequel, and even MongoDB. And the URL is the URL of the connection to
the database. So it's going to grab it from the first environment, a file that it finds.
So if you place one inside the prison folder, it will grab this one. For that tutorial,
we're just going to leave it here in the global root folder. And let's go ahead and create our
models. So the way to declare the models is just to type model, and you name it was a singular
word. So if it's users, it's going to be user. And if it's bookmark, it's going to be bookmark,
User & bookmark models
right. Model bookmark, bookmark. And what do we have in our app, we have users and
basically
bookmarks, right, so we only have two entities in our app. So the user will have an ID field.
It's an integer, we need to tell the Prisma that it's an ID. And we need to set a default as auto
increment. Or that can be found in the Prisma documentation,
of course, and same goes for bookmark, we also have to add the created Add Field. Otherwise,
we
will not know when it has been created, it's going to be a date time. And the default would be
now
which means when the record is created in the database, a default value of now means the time
at the creation is assigned to that variable. And we also have to add updated add, I'd like to add
that as well. Date Time and prisoner has a special command here updated. And we can copy that
to the
bookmarks as well. Right. So for the user, what we will have, well usually we store the email
of
the user is going to be a string. And it's going to store also the password, at least the hash of
the password, so we're going to put hash shrink. By the way, if one of the fields needs to be
optional, you can just put the, so let's create two optional fields. First name is going to be
an optional string and last name, optional string as well, right, we also have a bookmark. So the
bookmark that we're going to save is going to have a title is going to have a description,
which could be optional. And we are going to have a link which will be also a string but will
not
be optional, because when you set a bookmark to a certain link, well, a link needs to be there.
Here
we go. We have created our two models, we save the file, and now you see auto formats it
nicely.
Then we need to add the database connection string to our to our database URL variable here.
So let's check it here. So the user is Postgres password is 123. And the database name is nest.
So
let's go ahead and change that. User is Postgres. password is 123. The port is at 54345434.
The database is nest and scheme not public, we can leave it out like that. So now when we run
Prisma
commands, prisma will be able to access our local database that is running here. docker ps. By
the
way, if you have the Docker app installed, like I have on Mac here, you can have, you can
access
the dashboard for Docker and see your container in action here, next year's API tutorial. And
you can
even see the logs, right, so let's run our Prisma command. So the way to do it is to run MPX
Prisma.
And let's press help for good to get some help. So we have several commands, all that is
explained
into the Prisma documentation. Of course, it's quite nice, by the way, you can check it out.
So we have several commands that will be useful for us, we have the Generate, we have the
migrate.
Running prisma migrations
And we have the studio Studio will allow to create a kind of a online client so we can explore
our
database through the browser, we're going to see that soon. But the one that we're interested at
that point is Prisma migrate devs, what it's going to do is that it's going to read that schema,
and it's going to generate the migrations in that folder. So let's go ahead and run that terminal,
clear MPX Prisma and migrate data and basically says that we you have added tables,
bookmarks
and users for those migrations to be applied, we need to visit the database. And it's usually
common when you rise my when you run, migrate Dev is only for development. If you need to
push your
migration to production, there's another command that will not delete the data. But for now, we
can just delete it. And we can press yes. And it's going to ask us the name of the migration,
we can just say in it. Now we see that it has generated a migrations folder.
And basically it just generated some SQL. And when we run Prisma, migrate Dev,
it actually does two other things. First of all, it pushes automatically, that schema,
that SQL to the database. So the database if we go there, and we check the logs, here, we see
that
there is some command that has been run insert into public users created an email hash. So
Prisma has already pushed some migrations onto the database. So the database has the tables,
the tables of users, and bookmarks. And one other thing is that Prisma has also automatically
run the Generate command. So when you use Prisma, migrate, Devon Prisma, also does and pm
Prisma.
generator, and what generate does, and it's actually a very good, cool thing is that it
takes your schema here. And it creates TypeScript types for your schema. So it creates the user
interfaces or classes. And same for model, the bookmark interfaces or classes. So you we can
directly use those fields in our code. So we can go. And now we see that we have a user here,
the user and the bookmarks as well, that are that are exportable as TypeScript types
from the Prisma client. So we can use those types directly in our application, which is
awesome. They're just very awesome. We don't need to code that by hand, we can use it
directly with Prisma. Right. So let's come back to the schema and inspect our database with
Prisma
studio. So Prisma gives us a very handy tool called Prisma. Studio, we can execute it by typing
MPX Prisma. Studio, and it's going to connect to the database at the URL in the end v file.
Here we go at Port 5555. We are now at the Prisma. Client and we see that we have a user and
a
bookmark models, right. And we can go here and inspect our database, when we'll have some
some
stuff there. And we can even add records if we want. So it's totally compatible with the Prisma
schema. It's actually quite cool. Let's discard the changes for now and come back to our code.
We
can leave that running that a that Prisma studio running, and we can come back to our code here.
So
far, we have created the Prisma kinda migrations folder, and we have created the Prisma
schema.
But we don't have any way from our code to actually connect to the database. And for that,
we're going to create a module to do it easily, right? We're going to encapsulate all our logic
regarding the database in the module and only export from the module, the stuff that we need
to
be accessed by the application. Alright, let's go ahead and create A module I'm going to
generate,
I'm going to use the NES CLI, because I'll be quite lazy here, module Prisma. And I'll just
call it the same name as, as this folder. But it's, of course, two different things here,
Prisma module
this Prisma folder in the root, stores the schema and migrations, while this one will be our
module,
following the NES, GS modular structure, right. So we will create an SG module Prisma.
And now it's here. And we will also create the service, we have created the service manually
here, let's go ahead and create the service
with the CLI. So instead of module, we have Prisma, or sorry, we have service. And by default,
it is going to create the spec files. So the test files, we don't want that here. So we say no spec.
And now we have the service that is important, as provided in the module, right.
And in the service, we are going to create our logic that connects to the database. So the way I
like to do it is that I like to have the Prisma service extending the Prisma client.
And Prisma client is a class that allows to connect basically to the database, right,
it has a constructor constructor, it has connect, disconnect, and execute SQL and everything.
So
what I'm what I want to do is I want to configure, I want to instantiate it with its configuration,
so I need to constructor and I need to call super. So super, will call the constructor of the class
I'm extending, and the constructor of Prisma client needs to have data sources needs to have
DB and URL and the URL will point to that trink. In the moment, we're going to see how to
use
config variables, so dot n and everything in SGS. But for now, let's just put that hard coded
into our Prisma. Service. Right. And I think that from now, we're going to need a bit of more
real
estate to run our scripts. So instead of running every time, the terminal here, I'm going to kill
it actually prisoners to you as well. And I'm going to run my scripts in a dedicated terminal.
Let me make it a bit bigger. So I'm going to do yarn start demo. And I'm going to split it
and do yarn MPX Prisma. Studio. And this is item two, of course, is nice terminal I use on
Mac.
So if you have any questions regarding the terminal, that's the that's the one. Let's go back
to our code. We have the prisoner service declared here. And we had it imported into the
modules. So
technically how it works in SGS. So let's say we want our module to have access to the
prisoner
module, because it's all modular, right? Well, you could, you could do something like import
Prisma module, right. So let's see if that works, or works. And normally,
you would have access to the providers that are inside that module, right. So let's go ahead and
try it out. So in order to service that is part of the auth module, and that has access to the
Prisma module now, because auth module imported in auth service, we can use dependency
injection,
to get reference to that service to Prisma service from our auth service code. So constructor,
private Prisma. And we can just reference it by Prisma. Service. Let's press save. And let's see
what we have, oh, we have an error. Well, why do we have an error? It says that nest can
resolve
dependencies of auth service, please make sure that the argument Prisma service at index zero
is
available. So what it does is that it says hey, you're trying to import a a service here. And
I don't know what is that? I don't I don't have access to that. And even though we have
imported
the Prisma module from the in the art module, we have not allowed the Prisma module to export
it to
export the prisoner service to other providers. So if we want to do that, we just need to do
exports.
Prisma service, right. And now the error should be resolved and we don't have we don't have
any
errors. right now. So basically, that means that if you have a module here, you will need to
import
it in auth module to make it work. But that's a bit tedious. Because we have our Prisma module
where the user will need to have access to the database, the bookmark will need to have access
to the database. And if we add to other modules, they will all need to have access to that
prison model. So do we need to really import that every time like every time we need to
create an import and create a model. Now, what is possible in in next year's is to create a global
module. So instead of importing it like that, what we can do, we can go into Prisma module
and add another decorator called global. And just by adding that and making sure that the
stuff that we want to be exported, it also is also in the exports array, just by writing
that this prisoner service will be available to all the modules in our app. And if we check our
logging, everything works correctly, just make sure that your global module is also imported
into the app module into the root module. And in fact, it is important here. Now we can freely
go
and and do what we want. And here comes the interesting part. Because this is where we
really start to write business logic. This is where we really start to do programming and not
configuration. And if it was a bit tedious to set all this up, well, if you do something like that
properly, with Express alone, it will take you much more time. And you need to be really
good with architecture to be able to make everything work flawlessly here we're in SGS,
we have everything working out of the box. And of course, it takes a bit of time to get used to
the
way of SGS. But in the long term is going to save you so much time. And here we don't
actually need the object so we can delete it. The injectable can accept other objects. But for
now,
it's not important in the scope of this of this tutorial. So without further ado, let's go ahead
and start writing our logic. So for this signup, what do we need? The signup is the creation of
a user right, we need to create a user based on something that we pass in the body usually,
so usually it's going to be as post route to all sign up. We are going to save Send the body to
that route. And the body will contain the email and the password and is going to receive
something
back about something back we're going to discuss more into details because we are going to
touch to
authentication and especially DWT JSON web tokens. We're going to talk about that a bit
later. Right now just let's focus on the process between the request and what is happening in
the
in the logic. So net GS has a lot of decorators. So this is a decorator.
This is a decorator, right? It has a lot of decorators that that have different functionalities.
Remember I said that under the hood is express GS? Well, if you want,
you can access that express GS under the hood, all you need to do is write a decorator called
Rec.
And it comes from Nash yes common. And let's call it Rec. And we give it the type of request,
which comes from Express actually, it's going to be a bit careful about that,
where things come from. And if we now press and if we now console log rec, we will see
that rec will have a lot of properties. And those properties are actually coming from the
Express request object. So let's just log it for for the loads and CO the odd sign up.
I have signed up amazing. Let's go back to our logs here and we see that we have logged the
request object. And that request object is basically the same as C Express request object.
And you can get stuff like Heather's like body, etc. Right. So if we want to log the body,
it will be something like request body and then if we want, we can pass that body to the signup
function. So let's go ahead and request body and provide a body of course it's going to be
form URL encoded. Here we have an email and a password let's send it over and let's log
that into the console and we see that the body has been locked. Now usually how it will work
is that we will pass the request body inside our function and of course here it complains
because
it the sign up doesn't doesn't have any declared parameters. But what happens if the email is
not
defined if the password is too weak, or even, there's no password? Well, this process of
validating, that is called the validation. And SGS has a lot of tools for that to make it really
easy
to do. But before even going to to that direction, I want to point out that this method is not very
clean every time we need to get a request. And then we don't have any, any information of what
is
inside the body. And SGS uses DT O's data transfer objects in a nutshell is just an object where
you
push your data from, let's say a request. And you can run validation on it if you want. And you
can
even have the shape of those details because we use TypeScript. So we can say that the body
should
have an email, and it should have a password, right? So let's go ahead and implement that when
working with NES GS, you should never really use the request object of the underlying
underlying
library. Because what if you switch to fast defy what if you switch to another framework that
they
might add in the future, you will use that kind of independence that NES GS has, you will not
be able to reuse the same code pretty much. So what we're going to do is that we're going to
use another decorators called body. And it just allows us to get the body of the request, right,
let's call a DTO. And let's declare it as any, because right now, we don't know the shape of it.
And let's console log that detail. And I'm going to use the My Favorite logging
pattern. So basically, is just going to create an object and put the DTO inside it. And that's
the shorthand for that. Let's go ahead and do the request again. And you see that it also
works. So the advantage of using decorators like that is that express will get the right body,
depending of the framework for you. So you don't need to really worry where the body is
express or
fast defy or any other framework. Message. Gs does it for you, right. But here, it only brings us
Using auth dtos
half of the answer, because we don't know the shape of it. So we need to create a interface
for that shape. Let's go ahead and create it. So usually, I like to create a folder called DTO.
And inside the DTO, I use the Baron export pattern. So I have an index.ts that is going
to export all the fields. So here we are going to do what while it's going, let's call out.dto.ts.
And there's going to be interface called odd DTO, it will have an email, which will be a string,
and it will have a password, which will be a string, and we need to export that.
Right when to close that. And we're going to index and we need to export that detail from the
index.
And if you have never seen that pattern, the Baron export pattern. The main advantage is that
now I can just do odd DTO.
And it's going to import all the details from that folder, instead of doing something like
odd detailed Ts. And if we have something else like sign up the detail that is instead of doing
that is going to import everything from the same folder, all the imports will basically be there
in those brackets instead of being spread out in your in your code, right. So that's a nice
pattern that I love using it brings a bit of more code. But honestly, it's worth it. It
makes your code much readable. And now we have access to this parameters, email and a
password
in the DTO. Right. And let's let's clean it a bit. Delete the requests. Recall, let's try it again.
And it works again. But here, it's only TypeScript. So what if we don't pass
the email? What if we, we forget to pass the value to the email? Well,
if we log that, we don't have any errors, we, we have email that is void and we have a
password.
So that's a problem because now we need to write codes like that if DTO dot email,
if not then throw error and is this is very verbose. We can simplify that a bit by using the
class transformer and class validator libraries. So let's go ahead and implement them. And just
before we implement them, let me introduce pipes. What are pipes in SGS pipes are just a
function
that transform your data. So if you have a string that comes from the request, because usually
apart
from if you send the JSON If you send a string in the in the in a query something like sign up,
for instance, user ID is equal one or user one, that one will be a string. And if in your code,
you need to use a number, that could be a problem. So one way of doing that is to use the built
in types. So what we can do is that we say email is going to be a string. And we
can isolate the email field directly from the body like that. And the password would, will be,
will be created like that, right. And let's,
let's log goals email password. And for some reason, my pitch here is not working. See
this line is a bit big. So I'm going to go into preach here, and reduce a bit the, the print with
and you try that out. Okay, 50 works well. And let's also look the type of type
of email, which will type of email. And same for type of password, type of password. So again,
I go here, and we say that the email is a string, the password is a string. So what if we want to
transform the password into a number? Well, it could be done simply like that by using
inline pipes, parse int, type, and there are other pipes. But usually, it's the this one that you're
going to use. And here, since it's not a number, the password will throw an error. So it's going
to stop the execution of our code. And if you check here, there is no console log is going to
stop the execution of our code before even we run the business logics. So this is amazing. But
if
the password is a number, well, it's going to have is going to work right now. And we see that
here,
the password has been converted by the pipe to the number, obviously, this process is a bit
verbose
because you need to create a pipe for every data field, what we can do visit DTO is just
is just apply those transformation and validation directly on the on the detail.
Right. So let's go ahead and continue what we're doing with that small explanation about what
are
pipes. But I think it's quite important to understand the pipes before we move into the detail of
validation. So there's more explanations about pipes in on the NES GS website.
NestJs pipes
And you see that we have different types, both pipe float pipe, uu ID, which stands for unique,
unique identifier, but there's something called Class validator. So basically, this is what we
need, we need to install those two packages, class validator and class
transformer. So let's go ahead and add them. Yarn Add Class validator and class transformer.
Here we go. And now what we can do is that we can go into the DT O. And to apply the
transformation and the validations, we need to use a class not an interface. Basically,
it is the same for us, it doesn't change anything, it just that instead of interface, it's a class.
And we need to add something here is email, for instance, from the class validator
package, and is not empty. And is going to be and this is going to be a string.
And it's going to be erased, not empty. And let's go ahead and throw the request again. And we
see
that it does not work right. Why doesn't it work? Well simply because we don't tell nest yes to
use
the pipe logic to use the validation pipe globally everywhere. So we need to go into the main Ts.
And
NestJs global pipe
just before the abduct, listen, write app, use Global pipes. And here we just write validation
type. And validation pipe is a built in pipe by NSCs for that same purpose, right? And we need
to instantiate it like that new validation pipe. So now that we have added that validation pipe,
let's go back to the insomnia and run the request. And here we see that we have two errors. The
first
one is email should not be empty an email must be an email, right? So that's cool. So if we
provide
something like test, well, it's still going to complain that it must be an email. So flat,
Gmail dot Come. And now everything works. This is validation in a nutshell. Thanks to DT
O's,
the validation pipe can do other stuff such as transform automatically your data. One thing that
could be interesting for you guys is to see that use case. So if I'm going to my controller, and
I'm going to console log, the DTO. Right. And if I'm going to execute that logic, we log that.
But
what if I try to cheat? What if I have identified a maybe a vulnerability in your server and I
want
to inject a variable, for instance, ID is equal one. Or any other variable? Well, if I console
log, that ID will be passed to our DTO. And maybe that's not something that we want, right?
One of
the things that the pipe validator can do is that it can strip out the fields that you don't need,
what we can do is just set whiteleys to true, and it's going to do, right, we send the same
request.
And now we see that the ID is not here, what it is doing is that it's stripping out the elements
that are not defined into our DTO. So now we can be sure that rd to has an email and a
password,
and know all the fields that we have defined. So this is amazing.
Let's go back to our controller now. And write the rest of our logic. Now we know
that the DTO is validated, and it has an email and a password. Let's pass it to the signup
function.
To make it all clear. Clean that and let's open the audit service. And in the audit service, here,
the signup function will receive a detail of the deal, right. And now we can run our business
logic
with self assurance that the data that we receive from the client from the browser is
actually correct. So the first thing that we're going to do is that we need to generate a hash
based on the password, I like to use Arcanum. So a lot of people use B crypt for password
hashing.
And that's a fine solution. But I also had the refresh token in my in my application. And there's
a problem with B crypt because its verification algorithm is only limited to the first 72 bytes
are going is considered to be a better solution overall. So I'm going to go ahead and use argon.
For that we need to install argon, two. And we can import it into our oath service.
Hashing user password with argon
So all as argon from argon two. And the first thing is that we need to generate the password.
Then save the new user in the DB, right. And we need to return the user, the saved user. We're
going to do those three things first. So let's generate the password hash is SQL Argan. Hash.
The
Sign up logic
first will come the plain text that we want our hash. So it's going to be detailed or password.
And that's it. Then we're going to say the user in the database. So const user is equal await.
And
it will be in a sync function, of course, because it's called Prisma. A synchronously this
prisoner
user. And since we have defined user in our prisoner model, create
data. And this is the data word that we're going to use to create the the user with.
And what we have here we have email, which can be detailed email, and we have hash, which
will be
that hash. Right, we can save it. And it's not happy because it says promise string is
not assignable to type string, and of course, are going to hash is in a sync function. So our wait
and the user is created. And then the user is created. We just returned to the user. Let's go
ahead and, and create our user. So we can go back to insomnia and delete that ID and create a
user.
And our user has been created. Obviously, we see that we have returned a hash,
which you should not return because that's basically the hash password of the user. Not very
secure. And let's let's try it out again. So we go to Prisma studio.
We gonna reload, and we see that the user has appeared here, we can delete it, delete record,
here we go. And go back to our code and just say that, listen, I don't want to return the hash.
Well, how do I do it with Prisma? Prisma has a lot of possibilities. Either you, you create a
select,
and you select only the fields that you want. For instance, if we want only the ID, we say
Id true email true. And, for instance, created add to true. So it will only return those fields
like that. And let's go ahead and delete that. But obviously, it's not very handy, because there's
a
lot of logic to, to write, right. So what I prefer to do is to write transformers. So we're going to
cover that topic a bit later. But for now, an easy and dirty solution will be to do delete user
hash,
there is going to just strip out the hash out of that user object and return that.
So let's go ahead and create one. And we have the user object without the hash. Amazing, right?
So
one other thing that will be interesting, what happens if that I, if I send the user again, well,
if I do it, again, it's going to be created again, and another user and another user. That's because
email field is not set as unique in the prisoner. So let's go ahead and run the migrations to
set it unique. And we can delete that. Otherwise, we'll have some problems. And let's go ahead
and get back to the Prisma model. And this is how migrations of Prisma works. So those
migrations
have created, the bookmark and the user. Now we're going to modify our model. And to make
it all compatible with our database. So we can simply include the new updates to our database,
well we need to do is we need to modify our schema. So for instance, email should be unique.
We might want to rename the the table user to users, because right now it is great user
is good for Prisma, because Prisma Prisma loves those kinds of things. But we're going to just
map these names to another name. So map users, and map.
book marks, everything seems to be correct. Amazing. One thing that we might add as well
is the relation between bookmarks and user. So bookmarks will belong to the user. The way to
do it with Prisma is to create a link to the user in the bookmark because many to one connection,
so
many Bookmarks can belong to one to the same user. In other words, this user can have many
bookmarks,
but any given bookmark at any given time belongs only to one user and not to several users, so
many
to one, let's create a user ID variable is shall be an int. And user, it shall refer to the user
model, we need to tell it it's a relationship is going to use fields. So we use fields to indicate
a prisoner which fields are used for primary keys. And we use reference to indicate to Prisma
to which variable this primary key references to so it's going to be to the ID, which is an
integer, or fuser. So we just name ID, right. Amazing, right. So we have made some changes,
and
Prisma automatically included an array bookmark here, we just need to change it book marks.
And is going to be an array of bookmark module. All good. So now we have made our
changes,
we need to run the migrations, how to do it, we come back to the terminal. And we do NPM
Prisma. Migrate, Deb is going to connect to the database, enter the name for the new
migrations.
Let's just name update models. It has run the generate. So it has generates the new
variables in TypeScript for bookmarks and for user ID. And it has made the email unique and it
also
has pushed the new migrations here into the database. And we might need to restart studio.
Sometimes it's a bit buggy when you run the migrations,
and it works correctly. So now we have the user and with the new fields, email should be I
believe
it should be unique somewhere it should be ready Unique. But anyway, we have bookmarks
here. And
the bookmark has been updated with user ID and the user object. So which is, which is really
nice. So
go back to our code and write continue writing our logic. So we go back to the audit service,
let's see if our logic against the duplicate user works. So we create one user ID one,
because we have visited database. And if we create the same user with the same email, again,
what we have is a five or not very cool, right? Because 500 errors don't tell us anything about
what happened. It's better that we provide to the user a kind of phase specified error saying,
Look,
in that case, it is a forbidden exception, because credentials is already taken, right? So we need
to
create that error in SGS, it's actually quite easy to do. And if you see here in in the logs,
we see that the there's an error with the unique constraint failed on fields email.
So prismas tells us that email is unique, and we cannot really use it. So what we can
do is that we can say, we can add try catch, block. So we put all that into the try catch
error. Here, we can just isolate if the error comes from Prisma or not. So if error instance
of prison Prisma known client request error, and all that is detailed in the docs,
right? The only time where you would write those detailed error exceptions,
is basically when you create a unique field with unique properties. Basically, you don't need to
cache all the possible Prisma errors here, I just want to make sure that if I'm catching the
Prisma, duplicate, unique duplicate error, so if the error is is Prisma error, and not something
else is that happens if error dot code is equal to p 202. Which stands for duplicate
field like Prisma has error codes defined and this specific code stands for you try to create a
new
record with the unique fields that has been violated. So if that happens, we throw a naseous
exception, throw new, forbidden exception. Credentials taken, right. And that new forbidden
exception actually comes from from SGS. It is well documented in the docs, and is the error is
not
does not come from the Prisma what we do is that we just throw the error, right. And let's go
back
to our insomnia send request. And here we see that the status code is for free. And we have
a descriptive message error message telling to the client, hey, the credentials are taken,
you cannot use that. Amazing, right. So we have created the signup service function.
Sign in logic
So let's go ahead and create the sign in. So what happens when we sign in while the user
will provide the password and the email? The first thing is that we need to find the user by
email.
If user does not exist, throw exception, then compare passwords. And if password
incorrect, we throw an exception. And if everything goes well, we just send back the user.
Okay, so the sign in function will also receive the same DTO as signup, we don't really have a
difference between the signing and signup process in some application, they do in some
application,
the signup object, the signup detail will be more complex, but in our case, it's going to be the
same. So the first thing is to find the user. So we go and find the user const user is equal await
this prisoner user find. So the way you can find elements in Prisma either you find them by
find
unique or find first. So if you want to find a single element from the database using Prisma
you can either find it with unique or first find first will allow you to get the element by
any field and find unique will allow you to get the element by unique fields. So in our case,
either a ID or a fields with unique property. So let's, let's use find unique.
Where email is DTO that email, right? So the first thing that can happen is that someone tries to
log
in with an email that does not exist in our database. So to handle that use case, we're going to
create what's called a guard conditions. So if not user, we're going to stop the code at
that point and throw an exception, throw new, forbidden exception, credentials incorrect.
And if the user has been found, we can just continue our logic and compare the passwords.
For the password comparison, we're going to use the compare function of the argument. So for
the
compare password, P W, mattress, physical await Argan. Verify the first argument will be the
hash. So the hash password, it is in the user, of course, so user dot hash. And the second
argument
is the password in plain text, and it comes from the detail DTO dot password here. And again,
we put a guard condition if not PW matches, if password does not match, we fire an exception,
the same exception as above, here we go. And if everything goes according to plan, we can
send
back the user. And before sending back the user, we can delete the hash field on the user
object.
That looks good. Now before we go ahead and try it out, let's create some handy scripts for us.
Automate postgres restart & prisma migrations
So first of all, let's just write a script that will allow us to respond our database instead of
typing it manually here in the terminal. Let's create a script in the package Jason. So we can
just run it and it will rebuild our container for us. So our Postgres database. And the second
thing is that, let's also write a script that will allow to apply those migrations to the database.
So let's start by the database. So let's, let's call, let's call this clip D dB. Dev restarts.
So that means restart the database with Dev development environment. So by definition,
we only are in development. But if you have a project, and that has several environments, such
as testing environment, maybe staging or something else, well, you can create several scripts
with
that syntax. So for now, let's go ahead and first try it out our script here. So Docker Compose.
So to remove a container, and here in our Docker compose our container or service is called dev
dB.
To remove it, we use RM. And let's go ahead and see what what functionalities we have here.
So we have forced to, to remove them without asking us for confirmation, we also have
stopped
to try to stop the containers before removing them, it could be nice, it's a nicer way of stopping
them instead of just killing them. And we also had this v dash
v to remove the volumes attached to that containers. And that's a good thing to do. So to
for our cleaning script is going to be Docker Compose, remove dev DB as
f v. So let's go ahead and put that into our maybe let's do something like that,
let's put our logic into several sub scripts. So it's just easier to manage them the B dev RM,
right. And the same would be dB dev up. But instead of removing is going to,
to push it to create it and with is going to be done in background. And for the DB
dev restart script is going to do yarn dB, the RM and yarn DB Dev.
Up. Let's try our our newly created script in our terminal and see if everything works correctly.
Yarn DB dev restart. So we see that it runs the first one and then it runs the second one.
So we had the Remove thing here. And we have the creation of a new Docker container here.
And if we do docker ps, we see that we have one created here for us The problem, though,
is that even though we have created the database, it doesn't have migrations applied
to it. So it does not have tables of users and bookmarks. So we also need to create a script
to automatically apply the migrations. So remember, in the beginning, we have run the
command Prisma, migrate Dev, what is going to do is that is going to generate a new migration
based on the current migrations. And we don't really want to do that, what we want to do is that
we want to apply those existing migrations to to the database. So that process is much more
safer
than regenerating the whole migrations every time. Additionally, when you do prism, migrate
Dev,
it asks you for confirmation. And it asks you for name of that migration, for instance, we have
in it here, and we have update models. So it's very hard to automate. However, the other script
that Prisma has is called MPX, prisma. Actually, let's go ahead and check the Migrate Doc's
help.
And we have Maghreb data, that's the one that we have used in the beginning to generate the
initial
schema and to run the migrations. But if we just need to apply the migrations to a database,
we can just run Prisma migrate, deploy. So let's go ahead and create that script. So Prisma,
Devil deploy. So this is going to deploy the migration to our
dev dB, which is here at that, at that at that link. So in so Prisma, migrate, deploy,
right. So and what we're going to do is that as soon as the database is up, we're going
to run that script as well. So it's going to also deploy our current migrations to the database that
we're working with. And yarn Prisma dev dB, what might happen as well is that if you have a
slow
computer, the database might take a bit of time to start. So it's usually nice to put this bit of
sleep here, I'll put sleep one second. So we are sure that before trying to apply those
migrations,
our database is running, and everything is set up correctly. So we don't try to apply
the migrations on a database that is in Bootstrap mode. So let's go ahead and try that out.
Again,
yarn DB restart, is killing the database. It's restarting it is waiting a bit, and it applies the
migrations here. And we we see that it's written that we have, we have found two migrations,
and
the following migrations have been applied here. Amazing. Now we can go ahead and start our
server.
And thanks to TypeScript, we now know that we have an error amazing, we don't need to run
the script at runtime to notice that we have an error,
we have it here even before starting the server. So we have what we have in the OD controller,
you have not given a DTO while in the service, you require the to, let's change that.
So in the surface, the sign in function, it requires the DTO while in the controller, you
don't pass it. So let's copy the body here, the body logic and pass it to to the sign in function.
That should resolve the issue. Amazing. Now we can come back to insomnia. By the way, I
noticed how
to make the font bigger. And I just didn't think about it. But yeah, basically, there's an option
in the in the in the journal options where you can resize the font. So sorry, for the smaller fonts
in the beginning, I'm learning as well. Let's go ahead then and do the signup requests. So
there's
Senate and we had the user, if we try it again, we receive an error. And if we do sign in and
provide
the same credentials, we have the user again, if we do a mistake with a password, we have an
error,
which is nice. And if we do a mistake with the email, we have an error as well. Amazing.
We basically have implemented the sign in function and everything works correctly. One thing
though,
in the real applications, when you sign in, of course, this information can be sent
to the browser. So it can it can, for instance, render information about the user. But it is not
enough to help the API to help the server to actually identify the user connecting to it,
we see that in our case, we send the email and the password. And it's called Basic
Authentication.
But we cannot do that every time. Right? So there are some API's where you need to send the
email
and the password. And basically anything required by the basic authentication at every API
request,
encoded, maybe in the base 64 format. That might be a bit technical, but that exists. But in our
case, for the user experience, we want the user to login only once in a while, right.
So to allow the server to track the user to know who the user is, there are two techniques.
The first is sessions, you probably have heard about them already. And the second one is G wt,
Jason Web Tokens. And that's another way of, of tracking the user on the website. So know
who
the user is and allow or forbid, requests based on user identity. Because if the user sends that
back, it's not enough, of course, anyone can fake that. This whole process is called
authentication,
and authorization, right? So we identify the user. So the user provides some credentials.
We know who that is, we identify then the user, but then we need to give something to the
user.
So we can authorize that user through subsequent requests, for instance, login, signup,
everyone can call that route, right. But there might be something private, for instance, user,
slash me, and that only attentive FIDE users can call it. So let's go ahead and implement that
functionality. I said before, we are going to use DWT, for that we have already prepared
modules for
NES GS. Now. So far, we only have seen custom made modules. So it's module that we have
made
ourselves. But Ness, GS also has modules that you can use in your app, like any NPM library,
so let's go ahead and implement some of them. The first module that we're going to implement
is
the Config Module. So for now, we have used hard coded environment variables. So this
database URL,
we have actually hard coded in Prisma, right. So instead of doing something like that, which is
not secure at all, because that will be available in your GitHub repository. And also, it is error
prone, because what happens if you do an error here, right, it's very hard to notice. And it can
NestJs config module
be only detected at runtime efficiently. So that's something that we're going to fix with the
Config
Module. So let's go ahead and install it, we can kill the server and install yarn, add
nest GS config. And we can start the server back again. And let's go ahead and implement
that Config Module. So the Config Module is usually implemented on the root module. So
here,
or you can put it in a in a custom module and implemented there, if you need to use something
like validation on that. So for instance, you can check that, oh, this variable should be a string,
this variable should be a number, this can also be checked. But it's out of the scope
of this project. So let's just leave it out. But know that you can actually put the Config Module
inside a custom module called config if you want. But for now, we're going to just import it
here.
We have just installed the Config Module, let's go ahead and implement that Config Module.
And,
and you see it comes from Nash TS config, I actually like to move all the node modules
packages on top. Now Config Module requires some configuration. And if we add it requires a
four
route option. And that's all we need to do to load that dot n file into our application.
Under the hood. The Config Module uses dot and for library, I actually have a nice video about
dot n
on my channel. So if you are curious how it works, check it out. Like any module that we have
here,
it also have a service and also uses dependency injection. So we can import that Config
Service inside any of our modules of our app. So where do we need it? Well, at first,
we're going to need it in Prisma. So let's go ahead and use it in prisoner to use dependency
injection. Don't forget to annotate a class with the injectable decorator. So if it has injectable
that would be able to use dependency injection. You don't need to put inject inject trouble if
you
don't have any dependency injection requirements. So if you don't use something like Prisma
Prisma
service, for instance, if you use that Ness, GS will complain somewhere, for instance, prisma.
User, find many, if we do that it should complain, because it's going to try to get that object
and
that will be undefined. So it will not know what the user is. And in fact, it says Cannot read
property of undefined, right. So for any class in net, she has to use dependency injection.
And that class, of course, needs to be inside the module, we need to annotate it with injectable.
decorator. So now it will work. But of course, we don't want to use Prisma. That was an
example. What we want to use, however,
is the Config Module. So let's go ahead and name it config Config Service. Config Service is a
service declared in the Config Module that we have imported here, I'm just repeating it so you
guys
can absorb the new, the new concept easier, right? Repetition is key. And what I'm going to do
here
is that this string, now, I'm not going to hardcore it, I'm going to import it from the dot
and file. And the way the Config Module exposes that dot and file contents, is by
having getters in on the config object. So this has a config object, we can do config.
dot get. And we just named the the name of the variable and database URL, let's put it in
quotes,
clean that and fire it up. And now we have an error saying basically that, okay, we know what
a Config Module is, but the Config Service is not exposed. Well, that happens, because, by
default,
all the modules are kinda contained. So this Config Module would be only available in the app
module, but will not be available into the Prisma module. So it's very similar to what we have
here.
With a global, you can expose our Prisma module to the whole app. Well, any NES GS
compatible module also have this functionality. Of course, we cannot use the decorator
here. But inside this object, there's an option called is global. So we can just enable it,
and that basically does the same thing. Let's come back in now, it should work to make sure
that
our prisoner Service is working correctly. With config, we can go ahead and print the config.
And we see that we get the right URL. So everything is working. And let's test it out. Let's
create a new user
have led to amazing the new user has been created. And that proves that our logic works.
Now let's come back to the GW T, the subject of authentication seems to be quite easy, right?
Like
how hard can it be just login and sign up? Come on, every website has that. But in fact, under
the hood, it's very, very complex. And there are companies, multi million multi billion
companies,
if I'm not mistaken, making hundreds of millions of revenues per year, providing that service
to corporate and, and and companies. So that's a quite edgy subject to get right is also very
hard.
So in this example, I'm going to provide a very simplified functionality,
very simplified authentication. If you need to get more information about that. I have a two
hours video on that subject alone on my YouTube channel. It's about net CFG WT
authentication.
If you're interested, check it out. But without further ado, let's get started and dive in and
try to add authentication to our CRUD app. Now SGS has some decent documentation on
authentication
and authorization. And behind the hood, it uses passport passport is a authentication framework
Using passport js & jwt module with nestJs
for Express GS with a lot of strategies. So you can log in with Facebook login with Google
auth
zero Twitter govt and has a lot of functionality that you can use in our app. In our example we
are
just going to use G wt. So basically the user will give the email password pair, and it will
receive
a G wt, what is it readable T, I have a video on that on my channel, but basically is just a string
with a signature, some data and some description about the kind of string it is and which
algorithm
it is using. And basically inside it to say, Jason that is encoded in base 64. And this is
the information that the server could pass to to the client. So for instance, in our case,
we could pass that the sub which refers to the ID will be a an ID or the user. The name could
be
Vladimir, in my case, and the email could be Vlad G Gmail. com, and you can add as many
claims as you want, though, all those fields are called claims. There see expiration,
you can put an expiration we can put a creation date. Basically, you can put any data you want.
And you pass that the server creates that when the user logs in or signs up, and it
passes it back to the browser. So every time the browser goes somewhere on the application,
it sends it over to the server and the server says okay, yes or no you have access to that or not.
This behavior is quite similar to sessions, except that sessions are being passed automatically,
with each request while govt needs to be passed, basically with code, but apart from that,
they solve pretty much the same problems, or at least people use them to solve the
same problems. Of course, some might argue that G diabetes are bad for authentication,
but I don't necessarily subscribe to that point of view. If people are using DWT for
authentication,
we should at least show them how to do it correctly. So let's go ahead and install
the needed packages, we need to install quite some packages if you have any problems with
following the steps. Of course, all the code will be uploaded on GitHub in the description
below this video. But without further ado, let's let's proceed and install all the needed packages.
So what we need to have we need to have Ness GS passport module, we need to have
passport and passport to local we don't need passport local is a library to handle local
authentication. So we don't need passport local nor do we need the types we only need passport
and
NES GS passport. This is basically the passport library. And this is the porting to net GS kind
of a, a module for Nash Yes, for passport. So we need to install those two. Let's kill it and yarn
add those. We also need to install g WT specific libraries. So here NES GS govt model, and pass
for
govt package of passport. So it's a bit a lot of libraries to install. But don't worry, just follow
the steps everything is going to be okay. And we also need to install the types for those paths
for
G WT thing because no library and they don't have TypeScript inside the repository, it's in
another
repository. And we are going to save it as a development dependency. Here we go yarn, add D
for development dependency in types password TWG. Now we will have all those added here
somewhere.
Right. And let's see how we can use them in our application. First of all, everything
regarding authentication, of course goes into authentication module. And that's nice thing
because everything will be organized and clean in our project. The first thing
that we need to import is the G WT module. So remember what we have installed. This NES
GSG
WT is basically used to sign in the code tokens. This under the hood uses JSON Web Tokens
library
so it just kind of a niche yes modularization of that library, while the other one in the
beginning that uses passport. This is a true kind of module that pours passport to to the next
GS.
So the reason why I'm saying that is that this we are going to use it in our service while the
other
library, we are going to kind of put it in another folder called strategy because the
first time you see that kind of pattern, it's a bit weird. So we're going to import it here,
G WT module. So this is to sign in decode the the JSON Web Token and we just need To
register it.
And there are several ways to do it, you can provide some the secret because the DWT needs
to sign with a secret, you can provide it here, an expiration date and stuff like that. I usually
like to leave it blank, because in most of the use cases, here, we're not going to use
a refresh token. But most of the time when you deal with Govt, you also need to use a
refresh token. And the refresh token will have a different secret and expiration date from the
from the access token, and more about those in a minute. But that's why I kind of like to
declare the module here and customize it further into the auth service provider. Right. So we
this is to sign in decode JSON web tokens. So let's, let's do it. Now let's do it. First,
we go here. And since it's a module, because everything in Nigeria are kind of modules. It
also has a what a service of course, well, we need to import it again with dependency injection.
And
this DWT models is registered inside this module. So it will be accessible here. We don't need
to
make it accessible there. And the use case of that module is just to sign some tokens. So let's go
ahead and do it. We need to import it here as the dependency injections of private govt G WT
service. Right. And this is imported from DWT from the same package. And if you have any
doubt,
you can also just check the package and see what's what's there. There's a god service God
module
and some interfaces. And I think it's actually a good practice to always kind of inspect a bit the
the libraries that you use, it will allow you to get a better understanding of what
what is inside instead of just blindly following documentations and see things just appearing
there
out of nowhere. So we have signup and sign in? Well, we said that we don't need to return a
user,
we actually need to return tokens. But to return that we need to sign that information, though,
like this user information and transform it into the token, obviously, like here,
so we need to generate something like that, right? Based on for instance, data like email
and ID. So let's go ahead and create a function for that. This is going to be signed token.
And I like to put it in a sink as well. And this sign token will take the fields that we need to
sign, it's going to be user ID, which is going to be a number and a email, which is going to
be a string, so we're going to send the user ID and an email. So later on, when he passes back,
something like that, we can get those two information out to that token, and run validations
or assume that all this is the user ID one. So whatever action that he does on our platform
check
first is that he has authorization to pass that specific action and then perform that
action. So this is the kind of information that we're going to extract from the govt
and kind of trust what's inside. So we're going to construct an object that we're going to sign,
let's call it data user. Let's code sub user ID sub is just a standard of
of the GW T's kind of convention that that you need to use a unique identifier for
a sub field. And let's add any field that we want for in our case is going to be an email.
And what we're going to do is that we're going to return this G WT sign and all those functions
come from Jason Web Token library that you can find on NPM if you need sign a sync.
And the first thing is going to be a payload and the second, the second argument is going to be
a options for the signature. And this is where we can set our
secret secret information that we're going to sign the data with. So only us know that
information.
So if a user comes back with a token ad with token, we know that it comes from us because
we have signed it with our secret password. And actually, we can name it payload because it's
going to be more descriptive payload. And the second option will be an object. First of all we
want to set when the token will be expiring. So let's set it at fixed 15 minutes. 15 m
So that means that once we give that token to the user, the user can do some actions on our
platform
for 15 minutes after that, the token will be rejected and the user needs to sign in again. And
we also want to add the secret, of course, secret is mandatory. Otherwise, we can't really sign
up for the secret. Since it's a secret, we should definitely not commit it to GitHub, let's put it
into the MV file as well, as called GBTC. Correct. And let's name it whatever like super secret.
And let's copy that and use the same process that we have using to Prisma
Prisma service, let's import the Config Module, private config,
sorry, Config Service. And let's get that the GW t here. Cons secretly SQL these config get
govt secret. Here we go. And since we're using TypeScript, we can actually provide some
interesting function descriptions, like the return type is going to return a string.
Right. And here. And for signing, for instance, instead of returning the user,
what we do is that we return this sign token user.id and user dot email. Perfect.
Since we return in a synchronous function, we don't need to put a sink here. This is only useful
if we do some asynchronous operations, when with await here, since we're returning the
promise,
we don't need to put it here. The code will know that it's a promise. And same thing happens
here.
And we don't even need to do it, the user here, we can clean that out. And let's also return
the user here. Awesome. Let's get back to our insomnia, and pass the URL request. So now if
we do sign in, is going to return us. Of course, we forgot to start the server.
Silly me. The server is running no errors, let's do the request again.
And now we have a text, which is not very nice, though I didn't really want
a text. But it gets us the the JSON web token. And by the way, if we copy that,
and paste it here into govt.io website, we see that we have the sub, which has the user ID
and our email. So this information that we have signed is correctly included into the body.
Now
into the next part, one thing that we might want to add is that instead of returning a string,
because here is going to return a string. And of course, next js is going to convert the headers
to
text HTML, I'd like to return a object. So instead of doing that, we can just change the logic a
bit.
Here in our sign token, we can return an object with access token,
I have noticed that usually, the convention for access token is to have this snake case
convention. And we can just return the access token here. Like that. And of
course, is going to complain because our type now is incorrect. Our type is promise string. So
it
should be promise of object access token, which is a string. Now everything is working
correctly,
and we execute the sign in again, we get a object with the access token key. Amazing. Now on
to the
next part, the strategy part, we have generated a access token. And basically token a user can
use
to pass requests, protected requests to our application, the next step is to actually write
the logic that will be able to intercept that token. So that token that we the user passes to us
validated, make sure that it didn't expire and make sure that the signature was which we
created, the token is correct. And then only allow the actions that the user wants to do
on our platform. So to summarize, we have written the logic that allows us to create a token,
right?
It returns a access token to the user. Then what would happen? Well, the user
will call another route, for instance, users slash me to get information about
the current user and He would provide in the headers authorization token, something like
that bearer space. And that would be the access token. So the logic that will verify that this
bearer token is correct is called a strategy. And this is what we're going to implement now.
For that we create a folder in our module called strategy. We'll create a baron expert.
And we'll create the strategy which is basically just a class strategy, the Ts, the
strategy, the G, the Ts, export class, G devotee strategy. We have an example in the NES
she has documentations. Let's check it out. Here. The strategy should extend the passport
strategy,
which comes from the NES GS passport module. And it implements the G WT strategy, which
comes from
this passport, DWT library. And what it does is that it configures it in the constructor
calling super. So it calls this module this service. It says that the govt should be extracted
from the headers as a bearer token. So it just means that it should be it should have this kind
of format bearer space token, which is a standard for a bearer token. This property is set to
false
by default. So we don't need to set it to false. It just allows us to ignore the expiration date
of the access token which can be useful when you were testing but we don't need it at that
point. And it takes the secret key as a as a parameter. And the secret is basically
this thing that we have defined here, we have signed our token with a specific secret.
Well, we need to allow the strategy to decode that same token with that kind of secret, not really
to
the code, but more to verify that the signature of the token has been really signed by that secret.
So we know that the token comes from this server from our API. So let's go ahead and
implement it.
The jeido which is strategy should extend passport strategy that comes from Ness GS module
passport,
and it should take the strategy that come from passper jadibooti. Right.
And it has a constructor because we are extending a class we need to call its its constructor,
the constructor of the parent class like that. And we need to provide the configuration of
that of that password strategy class. So we can just go ahead and copy what is there.
And delete that. Because it's false by default, and import extract g, oo, t and x. And this is
basically the secret, right? So this class is also a provider like the auth service class,
right, so it can use injectable. The reason why we separate it in a different folder is
just to make sure that we don't we know that this class has a specific use case. And it's for
validating the D token, the access token. So that's why it's inside a folder. But
it can also use dependency injection under the hood. So that means that it can inject Prisma
inject Djibouti and inject the config and this is what we need because we need to get that
that secret from the environment variable so let's go ahead and do it injectable
and here is going to be config Config Service. config dot get and it's going to be
I think we call the govt secret everytime I forget how we call things. G WT secret. Amazing.
Now, now
everything's okay. The only thing that we need to do is that we need to export that class from
our
Baron export, which is a good pattern. And we then need to import that strategy as a provider.
So NES GS is aware of that of the strategy being part of our application. So G to bootstrap the
G.
And that's all we need. Let's go ahead and start the server. Nice. Everything seems to be
correctly
instantiated, and we now have the DWT strategy configured. Now we can protect
some of our routes with that strategy. That means that you can access a route only if you have
the
valid strategy here we are using CWT but if you want you can also have strategies like login
with
Facebook login with Google. And they have their own configuration options and stuff like that.
So again, we generate the token with the auth service. And then we have a strategy that we can
Get current user with access token
decorate other routes with. So only people with a valid token can access
it. But we don't have any other routes, any other controllers are part of the odd controllers. So
let's go ahead and create one in the user module, I'm going to use the CLI nest G controller
user.
I would name my controller users based on the REST API format. And let's go ahead and
implement a simple get endpoint is going to be get me if you leave get decorator blank, it will,
it
will catch any any requests. So it will catch any requests like that. If you put something there,
it will actually catch it with that pattern. So if you leave it blank, it will just use
the pattern of the controller to catch it. Obviously, if the controller has nothing, it will try to
catch it at root, which is not a very good idea because it can have conflicts
and undesired behavior. But in this case, we are going to use me. So we are going to
call that endpoint with users slash me. And in me, we are going to return something return user
info.
It's just a drink, right? Right now we are not doing anything. And if we go back to insomnia,
and CO users me, and let's actually delete that, and it's not the post request, it's a get request
is going to return us user info because we have just mapped that route on to our application,
NestJs Guards
but we haven't protected it right. So there see. So to protect a endpoint to kind of have a
condition that will say, Okay, if you meet certain conditions, we are going to execute that, that
logic, else, we are not going to execute that. To do that in Ness. Yes, we use what we call
guards.
And again, you can use the nest GS documentation to read about guards. A guard is basically
a function that will stand in front of a route handler. So in front of a endpoint, and to allow
or not allow the execution of that endpoint. In our case, our guard will check for the strategy.
And if the strategy digital which strategy is correct, is going to allow the execution of the
route if not, is going to block it, you can of course, create custom guards. But in our case,
we can use something that has already been pre made. And that comes from the NES GS
passport module. And it's not called. So to use guards on a given route or on a given controller,
you can use pretty much everything at a global level at the controller level, or you can use
it at the route level. So in our case, we want to block the GET ME route if you don't have a
valid
token. So let's go ahead and create that we need to use a decorator called Youth guards.
And in the youth guards, we can provide the guard that we want to use. And Neji has passport,
the module that we have here somewhere, actually here.
NES GS passport, it already has a guard for to work with that strategy, right? So
if we inspect the code here, we see that will do what does it have? It has a note card,
it has some interfaces. It has passport module, but this is what we are interested in. It's not
guard and basically. So it's basically a guard that is compatible with
Nash's that we can use. And that works very well with the strategy. So let's come back.
Let's go back to the user controller and import it here into the youth guard. decorator. So old
guard. And here in the parenthesis we provide what kind of guard it is what kind of strategy it
is
guarding for. So it's G W team. And when we create our strategy with with that was that strategy
with
that strategy object that comes from passport, Govt it by default assigns DG WT kind of key to
it so so you can assign any key that you want. For us, for instance, GT 32 But by default, it just
leaves it like that. So it will be identified by the odd guy with that keyword. So if you
have a refresh token, you can do something like that. You can name it really the way you want,
you can give it any name that you want. But by default is G wt, you can leave it blank, or you
can leave it here Djibouti, I will prefer to leave it like that, because it's going to be clear, is
going to be a bit less of magic, because the first time I encountered the old guard, I was
wondering,
how does it know that this job jadibooti is linked to that strategy. And that's how, so
we are going to say that, Oh, this route should be protected by that strategy. Now, let's go into
insomnia and try to use the same request again. And see we have a 401 status unauthorized?
Well,
it's because the strategy is being executed. And it does not receive the bearer token. So it
throws
an error. So let's recreate a bit the flow, I cleaned up the database, I have now three routes,
get me route, a signup and sign in route. So let's go ahead and sign up and create the body email
if
lad@gmail.com passport, sorry, password 123. And this create the account, we have an access
token,
that access token, we can use it in our bearer tokens. So if we, if we throw the request in
right now if we use a request, right now, we're going to get a 401. Because we haven't
required any we haven't given any bearer token. Let's go ahead and implemented authorization.
Bearer. And then we paste the token. And now we have another error, which was interesting.
Let's see what is going on. This validate is not a function, okay, we forgot one function
in the in the GW strategy, and it's validly date. So let's go ahead and write it.
And it's going to take a payload of any, let's log that payload.
Cool. One important thing that we forgot to implement is that we forgot to have valid function.
So the token is going to be transformed into that object and put into payload.
And then we need to return it. So here we can perform any validation that we want. And then
we can return the user. In our case, we're just returning the Jason. So what it's going to do
by returning the payload is going to append the payload to the user object of the request object.
It sounds a bit confusing, but under the hood of Nigeria's, we use Express, right? And we can
get the request and the response of express with the right decorators, which I have shown in the
in the beginning of the course, right? What the WG strategy does, is going to attend that a user
object to the request object, so we can use it in our route. So the user is going to be that
payload. So it's going to be that object with the sub email expiration date, etc. But let's
get back to the strategy and just run it again. It is passing correctly, right? So we have the
the console log the payload here, as I said, it's an object with a sub of the user ID email,
our email and other information that was contained in the token, right. So let's clean that.
And let's see, why is it useful for us? Well, if we go back to the controller,
I said that the strategy will put that decoded payload or whatever value we pass here, whatever
value we pass in, the validate function is going to append it to the user object on the request
object of the request of the or the or the API. So we can get that object here.
Request. Actually the Ric Rec. And Rec object is from nest GS and request interface is from
Express. And we can do console log user is equal to rich dot user. Record user. And you see
that
even expressed now that a user is possible because because when you validate the identity of a
user,
its identity can be appended to the request object. So here we are going to
log that is going to log the payload. So the object that we have returned from
here. So if we run the requests, again, we'll come back to the thing. And we see that we now
log,
the same payload, but from the controller, because we have written user here. So if we pass any
other
value here, let's say hi, Well, hi is going to be logged.
Here, because we basically attach whatever we export from the validate function to the request
user object, right? Does that make sense? If that doesn't make sense, don't worry about that, we
are
going to build upon that concept. And I'm going to come back to it a couple of times. So
basically,
that means that we can get the information of the GW T from the token and do something with
it. Since we have an ID, we can get a user by the letter Z, since we have an email, we can
maybe
get the user by that email, everything is possible, we just know who that user is,
that requests the users me endpoint, let's clean the strategy and return the payload,
we can close the strategy file and come back to the user controller file. So what we're going to
do now is that we need to get the information of the current user based on the access token,
right.
So now we need to get information of the user and return it back. So that can be done in several
ways. In that example, we are going to get the user object from the database directly in the
validate function. So on top of having config, we also need to inject Prisma
Prisma service and here we can get the the object so it's not going to be any it's going to be,
it's going to have a subfield which will be a number, and it's going to have an email field,
which will be a string,
validate will be in a sync function can get the user const user is equal await the prisoner.
And I forgot to put private. The reason why I don't put private in front of the context
is because if we do something like that, I can't really
use this because super must be called before anything. And since I'm not using config in my,
in the rest of my application, there's no point to use private and remember when we use private
is
just means that we declare the variable here automatically, right? So we don't need to do that I
can leave it without private and of course, it's going to be
config dot get. But here we can use private because we use it elsewhere. So this prisoner
user, find a unique ID is going to be where ID is equal payload dot sub. Also,
very important thing to notice is we if we return Nope, it's going to throw an error.
Right? So the user will be now if the user will be not found. And a 401 error will be
returned. And if the user will be found, that user object is going to be appended to the user
request
object on the request. And the thing that we can do before doing so is to delete the hash. So
we don't inadvertently export some sensitive information, and then we return the user. And
in our user controller, what we can do is that we can return a rack dot user.
Now if we run the requests user, slash me, we're going to get back the user based on the
access token. And now we have our application working, we can sign in, sign up and get the
information of the user. So that's quite cool. But there are a couple of things that we can
enhance,
before I told you should not use the request object immediately because it is error prone.
And this is correct. So we can fix that. And we also can make that a bit more
clean. So this is easier to do. So let's start with that. Every time that you need to put
a string somewhere. It's called the magic drink. It can create errors, and we can make that
cleaner
by abstracting it in its own class. So let's go ahead and do that. This functionality will
be part of the odd. So let's go ahead and create a guard, a custom guard. So
new father guard, and again, the Baron export here, and we are going to create the duty
guard.ts. And what is going to do is going to
just be a class jadibooti guard. And all we need to do is to extend the old guard. And
we are extending a guard with GE WT strategy. And that's pretty much it, we might just run the
super
and, and we need to export that class so we can use it in our application. So instead of
writing out guard, like that, with those strings, we can just write that class. And let's export it
and import it in our user controller.
jadibooti guard. And if we run, if we check the runtime it is executing correctly. And we
can even get the information correctly. Oh 401. Unauthorized and we can we can. So so this is
similar to what will happen in our application, we get a access token for a specific amount of
time.
And once it is expired, you cannot use the protected route anymore. So we need to get a new
one. So let's go ahead and create a request email flat ARCHIE MILLER comm and pass word
123 We get the new access token. And then we come back to the to the header here and replace
it with
NestJs custom param decorator
a new one. And if we send the request, it works correctly. So now we have fixed that. And let's
go ahead and fix this. This is a decorator. So you know, all those things are decorators,
we have made a custom guard that extends the auth guard provided by naturally as passport.
But we can also make a custom decorator that will go in the request object and get that user
object
and return it back to us. So now we can go in the old folder because it's kind of part of the OT,
ot functionality, right, we can create another folder called decorator.
And again, export the burn with a burn export. And let's create a custom what we call a
puram decorator. And I don't remember the syntax by heart. And obviously that's why as
programmers,
we have documentation, so use it when you can you're not supposed to know everything by
heart.
And in the custom decorators documentation, we have params decorators, and that basically
mean
that you can put them in the parameters of your endpoints. And there are a lot of them already
defined, you see that you have the request, we have already seen their session as well.
You have IP headers query body that we have used, right. And they say that you can create
a custom decorator, for instance, to get the user object on the request object. And the way to do
it is like that. So they just copy it. And I'll explain in a minute what it does. So let's go and
create our decorator, let's call it get user. decorator, the Ts, and let's call it get user.
And that's it. It is creating the params decorator that we can use like that.
Get User right, and it in fact, it only knows it.
And what it does is it can take a data that you pass to it, for instance, that would be data.
But by default, it's unknown. So you don't you don't need to pass it. And the second argument
it's actually gets, it actually gets the execution context of the request. So it's important to
understand that NES GS is an abstraction right? It uses something under the hood. It can use
Express it can use any other protocols. Here, we say that the this execution context switch
to HTTP because we're using HTTP, but sometimes you might use for instance WebSockets,
right.
Or you can use RPC for something like like micro services. In our case, we use HTTP And then
we say
get requests, there are other things that you can do, you can get the response as well. But here,
we want to get the request. So it's going to get the request object of the Express
Library, right. And then we can do something with it. So this request will actually be the same
as
Express request. If that makes sense, right, this request is going to be that,
and then it's going to return whatever we want from it. So the decorator will put in a variable
here, whatever we return to it, and is going to be a user. And in our case,
since we have used the strategy, in our case, the user object will be the user object from
Prisma.
Right, that's all we need to do for the custom decorator, let's export it from the Baron export.
File, and in the user, we want to use the Baron export pattern is going to export it from the
decorator file. And here, we don't need to put any data for now we're going to leave it blank.
And here user is going to be of type user. And that will come from the Prisma client. So in
the beginning of the video, I said that Prisma generates TypeScript typings for us, so which is
very cool. So now, we can say that this variable is of type user that was defined in our prisoner
schema. And now we can just return the user and it makes it all clean, and that decorator, you
can reuse it pretty much everywhere. Now, we can also optimize that logic a bit by moving the
guard
on the controllers level. So that just means that everything that is in the user controller,
while it will require you to be to have a token, it will require you to provide an identity of who
you are, which makes sense. That way. We if we have another endpoint here, for instance,
patch.
Edit User, well, we want me to copy the guard here as well, we can just put it on the global
level.
So let's go ahead and run our requests again, and everything is working. So let's say that in that
in this case, you don't really want to pass the whole user object, you just want to pass the ID.
So you can say I just want the ID and then it's going to be user ID and it's going to be a
number.
So you can say something like that. And then in your logic, you can get the ID of the user,
if you want to do something like that, it's quite easy. You come back to the Get User decorator.
And you say, well, if if data if there's something in the data, then you return request user. data,
and data will be a string. And it could be optional. So we if we don't provide it is going to
return the whole user object, if we provided it's going to return the the a field from the from the
user objects. So we can we can actually try that out here.
And here, we say we want to get the email of the user is going to be a string. And we can
before returning the user console log that email.
Let's run the request. And if we come back here, or there's something that's not working a
required
parameter cannot follow an optional parameter. Let me go back to the get user. And here, okay,
I see.
So instead of doing like that, and this is true, a optional parameter can only be put at the end
here, the old order kind of metrics. So we will say String or undefined. So that way,
the data could be potentially undefined. Let's go back and throw the request. And we have
a log of our email and the same time we get our users so everything is working correctly. Let's
clean that up. And we don't need to get the email anymore. So thanks to those tools, decorators
and
guards. We have created quite a clean logic for our application. You see everything is
quite organized, and everything has its place. There are a lot of decorators that nest GS has
and it allows basically you to save Have some code and have something? Well, I would say
economic
from a from a visual point of view. For instance, there's another decorator that I use quite a lot.
And that I haven't showed yet. It's HTTP code. So usually, when you create a resource on the
server,
you send back a 201 HTTP code. So if we go ahead here and create a new user, Vlad to the
status
code will be 201. Right? So that just means that the server has created something in a database,
that's kind of a standard, you can of course, make it return to Oh, so for that is going to be
HTTP code. That's the decorator. And inside the decorator, we're going to return a status. So
for instance, it could be 200. Gnaeus, also has enums. So instead of returning numbers,
which is error prone, you can return enums, HTTP code, HTTP status. And you have
accepted found and it basically means different codes. So code 200 means okay.
So and if we put the mouse here, we see that 200 is equal, okay. And there's a lot of other
code that is that are used in the development. So you see, here's a whole list here not found,
for instance, that is very known for or for 401, authorize that is thrown by the strategy when
the
bearer token is not correct and forbidden, for instance, for free, when we throw a forbidden
exception. So we can do that. Obviously, when we create a user, now it's going to be
two Oh, but by default, a post request will return to r1. So we don't really need to
change that. However, when we sign in, we don't really create a new resource. So I like to
return
the code 200. So we can use it 200 Here. So instead of like signing in with a 201,
it can be used to sign in and get it to oh, that's quite cool. So post requests return usually 201,
GET request will return 200. So we don't need to specify a because decorator here, we have
implemented quite some features already. And you see that our application starts to grow, will
soon implement the user routes. For instance, the patch user route will also have bookmark
routes,
and application will start to grow, it's going to be very tedious to actually go in insomnia, and
run all those kind of tests manually. Imagine you have something like 100 endpoints, and you
need to
test several use cases, for instance, like, Oh, I've signed up with a profile of admin and the
admin should be on boarded. And then it should do that. And then I need to sign up with you,
oh, my God, it's pretty much impossible to test manually. So that's why we use automated
testing.
There are three kind of main levels of testing you have unit testing that you probably have
heard about, unit testing will usually take any any functions, for instance, is going to take
the sign up function is going to mock any dependency that the signup function is using,
for instance, argument or connection to the database. And it's going to try to make sure that this
function is executing and calls the right the right things and everything
is working correctly on that unit level. However, that takes a lot of time. And to make it right,
you need to actually practice a lot. There's a lot of methodologies, how to do unit testing. And
it can be really nice when you have a project where a lot of people work on and a product
that will respond through years and years of development. Unit tests are a good investment
in terms of your time. However, in most of the cases, what we want to have really is end to end
testing and integration testing. So we have unit testing just above, we have integration testing,
integration testing, will kind of take several modules. And that's why NES GS is so cool,
because, by default, it separates our application into modules. So for instance, I want to check
the
odd functionality on it. Well, I can load the odd module and the Prisma module, and maybe the
Config
Module. And I can test only all those three together. And when I'm happy with the result,
I can just test something else user user well for the user will need to have a user. So
it needs to use the auth module. So we're going to use the auth module user model prisoner
module, but not the bookmark module. And we can can test all those together, integration
testing,
you define some segments of your app and you test them together. I like to test it with the
test database, so on of having a dev DB that I use for like manual testing and manual feedback,
I will also have a test DB here in our Docker compose that I'm going to spawn and destroy
every time I do my tests. So you have integration testing, and then you have end to end testing
and
end to end testing kind of verifies a very high level user journey of your app. So okay, a user
signs up signs in, he requests his profile, he does that. So it mainly verifies how the user will
interact with your app. While integration testing, we can really test a lot of things, for instance,
well, is the same token working correctly, is it the same token with signing is working
correctly
are the exception throw correctly. So this is more for integration testing. Since testing takes a
lot
of time, usually, it's estimated that testing takes at least the same amount of time that you
have spent on creating your app. So if you have spent something like two weeks building your
app,
you'll probably spend at least two weeks testing it. There are various methodologies for testing,
there's test driven development where you write your app as you test. Obviously,
we have not done that here. And it can solve some problems as well. But in our example, here,
in our small CRUD application, what we can do is that we can use end to end testing. So end to
end testing will allow us to kind of showcase the use of our app, and prove that it kind of
works,
we don't need to go into details and make sure that everything works together. If you're
interested by that, you'll probably be using integration testing, I have a video about
that on my channel. So if you are interested about that, specifically, and how to do it with
Prisma,
well be my guest, there's a one hour video on the subject. But in our example, we are going to
use
end to end testing. And I'm going to use a library that has been suggested to me by one of the
core
developer of NES GS. And I have to say, it's quite cool. It allows you to save a lot of time. And
e2e tests with pactumJs
it's called Pakhtun. By default, NES GS uses super test for the end to end test. But In that
example,
we are going to use Pakhtun je s. So without further ado, let's get started and create our end
to end tests. So we don't have to jump between our code and insomnia over and over again.
Okay, so
let's start with that amazing endeavor. And first of all, let's install package on yarn, add pack.
So pack to me is an amazing library for testing your API's. It is really nice to work with.
And it works very well with Graph QL. And it has a lot of features that we're going to
use straight away. And to install it, all we need to do is to use
AES to install it with factum.
So before we start, our test will need to consider a couple of things. First of all, we will have to
set up a test database for our end to end tests. Because we want to keep the dev DB actually
separate from the test dB, so we don't delete our database every time. So that's one thing.
And the second thing is that we'll have to set up the database set up the Prisma service to be
cleaned up every time we run our tests. But before doing that, let's clean up our app. And to
inspect
file. We can just go here and delete everything. So how NES GS works is that it's going to
compile
a module. And we're going to take the whole global module, the app module is going to compile
that
and we can create a test module out of it. And on that test module, we can call requests like we
do
with insomnia. So let's go ahead and first, create a describe blocks. So it's going to be
app content.
And inside that app, and when we will just run a test, a to do test
should pass just to see that if our script is running, and we are going to kill our server
because we don't need the server anymore. We are going to kind of use what we call test driven
development. We are going to write tests and write logic and make sure that our tests are
passing.
So The way to run that file is through tests end to end here, there's a script already created
for us by nursery's. So yarn test end to end. And let's see if that at least works. And we see that
we have a past test, nothing really happened. But that's fine, because we haven't defined our
tests
yet. So let's go back to our test and create the Prisma module. So before all the tests,
what we're going to do, and it's going to be in a sync function, it's kind of a hook that is
provided by jest, we're using jest as our main testing framework,
both describes come from jest. And we're going to use Pakhtun for for the requests
to send the request to our server and to analyze the the answers to responses from the requests.
So before doing that, we need to create a module module ref await test, the test is going to
come from from NES testing. NES GS test think there will be a test.
Test create testing module. And here we import. And that's basically a, a, a module as we have
here in the app module. So we create a module that will import that app module inside it.
That's the easiest way to do it. app module for the app module come from the from from here.
And we are going to say compile. And that's pretty much it for end to end test,
we just import everything because everything needs to work together. And what we need to do,
then is to well, let's see that passes to begin with.
Right? Amazing. Now, what we can do is to avoid everytime that every time that we want to
run
the test. To avoid that we have to go here and do yarn test and 20, we can set the test to be
kind of automated. So the run automatically as soon as they detected change in our code.
So we go back to package json and say just watch. One important thing that we need to say as
well is
to disallow the cache of the test in case we don't run into problems where some resources
might be cached. So that's fixed as well, let's go ahead and run it and see if that works. So
now it should be running continuously. And if we save, nothing would happen. But if we run a
new test, it should automatically recompile it. So it will detect if we have any new tests in our
application. And if I press A is going to run all the tests again, that's pretty much just usual,
just I would say flow, you press W to get more options. But for now, that's all we need to do.
And let's delete that test and, and see if we can connect to our test database right now. And
let's add a couple of things. Basically, what we are doing here is that we are simulating the
the server. So anything that we have defined in the main Ts, we also need to define here.
In our testing module here we use Global pipes. If we don't include those, the detail validation
in our tests will not work. So we need to also include those just before here. So now that we
have the module compiled, we need to run a win to create a NES GS application testing
application.
So we can say app is equal module, dot ref. Create module ref, create nest application, right, so
he's going to try to emulate an app. Here, it just compiles a module that can be useful when you
run
integration testing. But since we really want to run an end to end testing, with that, also
app we can abstract it in name in the describe here, I nest application and app will be this.
And now we can include the pipes. Otherwise, the validation pipe will not work. It needs to
be as close as possible to our real server. And at the end we say await up in it. So this is where
we
kind of start the server and when We are done. When all the tests are done, what we do is that
we do app close. So we close the app. And we see that now it is still working, everything is
good.
Setting up test database
This is our starting logic. This is our teardown logic, we can close that. And now let's think
about the database. Well, by default, NES GS, in our case, will connect to our dev database,
we need to have a dedicated test database for our test and a dedicated database for everything
else.
So how are we going to do it? First of all, let's create the dev DB in our in our Docker
Compose.
So just copy here and called sorry, test dB. Test DB is going to run on port five,
and 5435. So it should not be running on the same ports or otherwise you will have a problem.
And the rest can pretty much stay the same. We can now go back to the package that Jason and
we see
Automate test database restart & migrations
that we have a we have the scripts for the dev dB, right? Well, we need to have the same scripts
for
test dB. So let's copy that and write it here test to be your third. So
and and just copy all the scripts here. And those will be for test for is not test deploy
dB. Test, remove and DB test up. So for the test DVD, it's going to be just test a B
test, the B prisoner deploy. And here in the to start is going to be test,
remove yarn DB test up, and er prisoner test, deploy. Okay. One important thing though,
Using dotenv cli with prisma
is that Prisma does not manage the the environment variables by default. So here is going to
just
load the environment, the database URL from the environment file that it detects. So I need to
be a bit more specific here. This means that prisoner will try to find that variable from
the environment variables. And if it doesn't find it, it will try to get it from the NV file. So
one thing to work with Prisma with different environment variables, because you can just
create another.in the development that indata testing, that would not
work. Well with that the cleanest way I found to manage environment variables with Prisma is
to use
a NPM package called dough 10. CLI, so let's go ahead and implement it. Yarn add the 10th.
CLI.
So what is going to allow us to do is to inject the dot env file of our choice
in our kind of scripts. So by default, if we just stay on the environment variables is going to get
this environment variable. So if we don't do anything, everything is going to be loaded
immediately from this one, right. However, if we, if we want to use another environment
variable,
such as in the the test, it's a bit more tricky to force or all our code to use this one,
because Prisma will automatically only get the dot ends, it doesn't know about the end the test,
and it cannot know about it. The Config Module from the API tutorial can load that specific
one.
For instance, it has a variable called end file path, and you can specify where the environment
variable ease, but we will have to create a lot of filler code and it's going to be quite ugly. So
it's better to use the 10. CLI. It keeps our code clean. And we don't need to change anything
here,
we just need to include that library here. So let's go ahead and do that. So if we are using
the dot env development, we don't need to use the 10th CLI because by default is going to be
using this one and this one will override any any other environment variables file. However,
if we use the test ones, we need to inject this one first. So it's very easy to be honest,
you do you just do the n the E and you just provide the path to the end file environment file
and dot txt And then you just execute the rest. This is this is specific to Docker. So Docker
does need to be aware of the environment variables. However, when we run our test,
we also need to make sure that the NESs GS is loading the right, the right environment
variable,
not this one, but this one. And for that we also need to include here. So we include that end,
hyphen, e dot and the test on the test end to end. And on the deployment of our migrations,
right, and let's come back to a dot and if the test and just they just clean that
here, and implement that new string, but instead of the Port 5434, we're going to have the Port
5435, which is the right port. Now to test it all, and this is the moment of truth. Let's go ahead
and kill it and do yarn test, end to end. Also, an important thing is that we need to create a hook
for test and twin. So when we run the end to end test, it needs to run the DB test, restart.
So it's going to clean the database, the test database, and it's going to push the migrations
on that test database. And it's indicated in the dot n dot test. And to define a hook in
package that Jason scripts, so we just say pre. And we put the name of the script here. So
if we execute that, this is the hook that's going to be executed before. And we do yarn,
DB test, restart. And just to make sure that everything is correct, the B
test restart. Okay, let's try that out and see if that works.
So we see that the hook is working. And everything seems to be correctly working now to make
sure
that the migrations have been pushed into our test dB. So let me just open a new terminal
session, and I just do docker ps. And we have now two databases, one that has been created
just just now. And it is the database on port 5435. It is our test database.
And this is the old database on port 5434. So what we want to do is to make sure that
this database has the migration, all we need to do is to use Prisma. To do but remember that
Prisma
is also using this the 10th file. So what we can just do is to force Prisma studio to use
this one with the same technique that we have used here. So that N E F test, let's copy that.
And it's MDX because we are running it from the terminal. And we do Prisma studio.
And that should automatically connect us to to the right database. Now we see that the
studio is blank. However, if we connect to the to the other one with dot length,
will be connecting to the dev database. And here we have some fields that we have been testing
with
insomnia. So everything is working correctly, we can clean Prisma studio, we won't need it
for our tests. And let's come back to our testing logic. If you have any questions regarding that
process, feel free to drop them in the comment section below. I will try to answer as soon as
possible. But all the code here will be available in the GitHub repository in the description of
this video. So if you are stuck somewhere, just check there. And I'm going to clean here
because
everything works correctly. And yeah, let's start our testing process. So now we're connected to
the
test DB via this environment file. Everything is okay. Let's go back in our in our testing
module. So what do we need to do? First of all, first of all, before running our tests,
we need to make sure that the database has been cleaned. And of course, every time we run our
end to end tests, the whole database is actually restarted, so cleaned and restarted. But when we
Prisma database teardown logic
run our tests in the same kinda just session, we also need to clean the database. We don't need
to restart the Docker container because that takes time that takes like three, four or five
seconds. And we don't want to wait all that time. Right? So what we want to do is that we want
to
tell Prisma to clean everything that is in our database every time we run the tests, right?
So because we have a just end to session that is watching. So that is keep going, it doesn't stop.
We need to do it manually. So it's quite easy to be honest to do it.
But let's first examine our schema to make sure that we are doing the things right.
So what should happen here? Well, every time we do we run our tests, we need to delete the
users,
and the bookmarks. And sometimes there could be a problem where for instance, of the
bookmark
is linked to a user, right? But what happens if you delete the user first? Well, if you delete
the user, first, the bookmarks suddenly might be without a user. And that could be that could
be problematic, because that can create errors, to make sure that we don't have any errors. And
that elements are deleted in the right order, we can either tell Prisma to delete first bookmarks,
and then the users. But that can be a bit daunting, because imagine you have something like
20 models, so your teardown logic of your database can be quite long. Or you can do
something like that you can say, on Delete. And we just say cascade, that means when the
element that
is it is the kind of the parent of that when the parent model or that bookmark is deleted,
the bookmark should be deleted as well. To implement that we need to run migrations,
I'm not going to do that, I'm going to go with the other option, where I'm going to make sure
that the bookmark is deleted before the user, but know that it is possible. And in some cases,
it is really good to have a on delete cascade, and it's unless you really want to conserve
the data in your database. So for instance, you can in an app have categories and posts. Let's
say that the user decides to delete a category, well, you can say what happens with the posts
in that category? Are they kind of set to are they just do we keep them?
Or do we delete them as well, in our case, if a user has been deleted, the bookmarks should
kind
of be deleted as well, I don't see why would we keep the bookmark so undelete is set on
cascade.
But in some application, you might want to keep the user data, you know, you never know. But
know
that it is possible to have those kind of hooks on prisoner, but I'm not going to go with it.
Because I don't want to run the migrations, I'm going to just show the other way. The other
way is to go into Prisma. Service. And we need to implement a new function that's called
Clean dB. And what does clean dB? Well, we need to tell it what it does. And we need to
delete
the bookmarks and the users. So we can do return this prisoner. Sorry, actually, user, it works
like that, because this is actually prisoner. So this is the Prisma, client, user, delete many.
So what do we need to have here, we need to delete all the users. And we need to delete all the
bookmarks. And of course, since we need to do it in order, it needs to be first bookmarking.
And
you're right, except that Prisma kind of optimizes our, our request and could delete the user
before
the bookmark. So to avoid that we can use transaction a transaction is when we tell Prisma,
to make sure that the things are done in the specific order. So this
transaction, and we just provide an array and elements will be executed one by one. So we first
need to delete the bookmarks, and then we delete the user. So we don't have a case where a
bookmark
can be without a user. And we just need to return that. And that's it. That's all we need to do.
This is our teardown logic that we need to run before our end to end test. And let's come back
to the end to end test in before all hook, we need to make sure that in that logic, the
database is reset. So how do we get the database? Well, it's actually quite simple in Nash, yes,
because it uses dependency injection. So you can do app prisoner. Let's just declare our
Prisma service here. And now we can say, we can say prisoner is equal to app get,
and we can get any provider that we want. It's called Prisma. Service. And it's going to just get
that Prisma service and we can put that into the variable and we can call that variable like that
await Prisma clean dB. So it's quite cool to do tests with Nest Yes, because yeah, it's just a
nice experience. And we can come back here. And we see that property, clean DB does not exist
on type
prisoner service. That can happen sometimes when you write code, and you didn't allow
Nigeria to recompile the files. So in our case, we, I think we need to just
restart the server. And that should work.
So if you are doing major changes to the underlying logic to the services,
you might actually need to restart the end to end test because it needs to compile the files into
into JavaScript, right. And that's it. And let's go ahead and now create our end to end logic,
we can close that collapse this, we don't need to touch it anymore. And we don't need to touch
it
either. Because as soon as we close the app, the prisoner connection to the database is also
close.
So we are quite happy. Now let's create our tests of describe what do we need else we will have
user
and we will have bookmarks.
So you notice that I actually write the tests first, before we even have a for instance,
bookmark logic is because that will allow us to kind of have a structure already and think about
our application before we even build our code. So what should be in the oath? Well, it should
be
sign up. And there should be signing
and that's pretty much all the race, then we should be able to get the user get current user
or more like get me then we should be able to edit user. And for the bookmarks, we should be
able to
get bookmarks create bookmark we should be able to get bookmarks.
We should be able to get bookmark by ID maybe edit bookmark and delete bookmark because
that way
we will have our CRUD application now next year's complaints because we haven't created a
single
test. Let's go ahead and just create a to do I think it will be happy with that
should sign up. Let's see that works. Okay, that works. So
we have should sign up we also have should sign in.
And let's start by that. So this is kind of our testing structure. Now we're going to
Auth e2e tests
run our tests right our tests actually and for that we are going to use Pakhtun so to run the tests
was packed on we first need to import it with import all as Pakhtun from factum.
So after that we have imported Pakhtun. We can now use it in our application. But factum is
a request making library, it needs an API, it needs the server to make requests. And so far,
our servers, our NSGs app, it just has initiated the nest application context, we also need to
start a server by providing a weight dot listen. And we can provide the port at which we want
the
app to listen. And now we can do the request so we can go into our first route a to do and
we can provide a callback function to that test. And what do we call we call return Pakhtun
spec.
And then we provide the the request type is a post request to OD two to HTTP localhost 3333
Odd sign
up and then we need to provide the body as well with body and the body will be the to the odd
DTO
and it will take an email which will be flat@gmail.com and the password which will
be one To free. And we will provide that email here. And we shall expect a status
of 201 Because we're creating a resources. So let's get back and see, okay, perfect. That test
took 42 milliseconds. And that has succeeded. If we want to see what's inside the request body,
we can just call inspect, at the end of the promise chain. And the body here has been
locked, along with the code and the headers. So you see that we have the headers that is
powered by Express, the status code is 201. And we get our access token. Now,
this is a bit verbose because every time we're going to call the the API, we need to write that
this is called also the base URL. So what we can do is that we can abstract that away from this
post request. So we don't need to repeat it in a subsequent request. So we can do Pakhtoon
request,
said base URL, and we can just provide that base URL. And instead of writing the localhost
here,
we can just write out, sign up, that is much cleaner, much shorter. And that does the same
effect. Let's get rid of the inspect to see if our test is passing. And everything is nice.
So it just took a bit more time, but it just independent of our of our logic.
Okay, now, it's amazing DTO, we are going to reuse it in our whole Old application, kind of
odd
flow. So we can just move it above. And let's also test if the sign in route is working. So
we are going to delete it to do here, provide a callback to be to test and return out sign in.
Since it's a end to end test, it will first execute this code. So we will have a user
and then it's going to execute that code. And obviously it should be too low. And let's see
how it works. Amazing, everything works together. So we have tested that we can create a user
and that we can sign in usually when you write a test, you also need to test the use cases where
your test will will fail. Alright. So for instance, before writing should sign up,
we should actually write it should throw, throw throw an exception, if email empty. So we can
also write all that to test if that logic will, will flow. So we can just copy that. Put it here
and say that we expect a a code of 400. So we're going to write 400 status code to be
expected because we expect the error to be thrown by the validation pipe, which returns a 400
error,
bad user input. And we also need to provide a DTO without the email. So what we're going to
do is that we're just going to provide just the password 123 Actually detail that password. So
we
reuse that, that detail. And let's see if our test is passing and we see that the test is passing
next we can also check the other way around. So should throw if password password tempting.
So instead of having the password here, it's going to be email and the to that email and should
also throw 400. Right. And we can also test both just in case, throw
should throw if not that you nobody provided. And I'm just going to do it without the body.
And we can do pretty much the same with the with the sign in. So we can copy that
and test it with a sign in here. Except that it's going to be signing everywhere.
Let's go ahead and check our tests. And everything is passing. And that's pretty much the
process
of kind of end to end testing. It doesn't really test how our modules how our functions interact
with. So we might return the right status, but maybe some something in the database is not
is not saved in the correct format and that we are not going to be able to verify that with end
to end testing. end to end testing is not the is not designed for that end to end testing is just
there to make sure if the app is working end to end. So if kind of all the functionalities
are working more or less as expected, if you need to go in more details, you're going to
actually create integration testing. And that's out of the scope of this tutorial. Now, a very
interesting part is that we need to check the user information, right. But for that,
we need to send the bearer token. So Pakhtun provides a very neat functionality that allows
us to store a variable in the packet and execution context. So we don't need to
create a variable somewhere, for instance, we don't need to do something like that
access token, and then assign it somewhere else based on the of what our API returns,
what we can do is that we can use the store API of pakhtuns. So here in should sign in,
let's inspect the body response that's going to be returned. So we have the body and we have
the
access token. So we can actually save that value in the memory of Pakistan. So let's go ahead
and use that functionality. And it's basically a stores function stores back request and response
data. And we can choose where what we want to store. So for instance, is going to be user at
user access token is kind of like assigning a variable. And the path to that variable will be
access token. So it's going to get the access token from the body, the access token variable
from the body, which is in the Jason and is going to put that into that variable. And later on,
we can reuse that variable in our requests. So let's, let's do it here. Get me it should
User e2e tests
get current user
return packet on the just copy that here. And we are going to put users
need and it should not have a body. However, it should have a
a an authentication headers. And if we check the code, right now, let's run that. We see that
get current user, we have a status of 404. less interesting. Why is it? What is going on with it?
So users? Has that controller and me, users me, so why is it not working? That's very
interesting.
404 cannot post it's a post request, of course, it expects a get request. So get.
And now it says that it's it's 401 unauthorized because we don't have the
access token. Right. So let's get go ahead and pass the access token. In the same way we
have passed them in insomnia. Remember here in headers we have. We have authorization
bearer space and the access token. Let's go ahead and do that here. Headers, with headers,
and we can provide the object key value. So the key is authorization. And the value is bearer.
And
to inject that variable that we have saved here, there's a special syntax in Pakistan. And it is
this thing a bit like what we have in in template strings with JavaScript, except that you have an
S
here for store. And you can put the name of the variable here. And normally, we should get a
a correct answer. And we can even inspect that to make sure that we get the correct one.
And you see that we got the user object that was expected. In a nutshell, that's all integration
testing is most of the time you will just test if the status code is correct. And at that point,
we have tested all our routes. And what is left is to actually write the logic for edit user
and the write the logic for create bookmarks, get bookmarks, get bookmarks by ID, edit
bookmark,
actually by ID, and delete bookmark by ID. So this is what we're going to do
in the next part. In the next section, we are almost there, we have almost finished
our CRUD application. So what we need to do now is to create the logic for the patch request,
and the crud requests in the bookmark module. Let's first start with the patch request.
And we are going to need the service here, we can leave our end to end in the background.
And we can go ahead and create our, our service. So nest G service, user know spec, it's going
to
create a service for us that we can now use, let's put there and we need to create a function here
Edit User in the same fashion that we have done with the auth service. So edit user is going to
be
probably in a sync function is going to take several fields is going to take a user ID.
And that's pretty much it. And it also needs to inject prisoner praise
Max service. And that's yeah, that's about it. So once it receives the user ID,
it also needs to receive a DTO. And it's going to be Edit User DTO. Let's create that DTO.
So Edit User DTO, the Ts. And it's a class, Edit User DTO. And what can we edit? Well, we
could
edit the email, we could edit the first name and the last name, so email, drink, first name,
string, and last name, think and all those fields can be optional, because we can just
edit the email without editing the first name, or the first name without the email, etc. Right.
So we can also provide the class validator with email. This is going to be a string,
string is string. And all those are going to be is optional.
It's optional is optional and mean to export that, Edit User DTO and exported here through the
parent
export pattern from Edit User DTO, let's clean those files and come back to user controller.
The patch request, edit user will receive a body of type edit user detail. And it's
going to also get the user and just the user ID. So Id, user ID and it's going to be a number.
And he's going to call the function in the User Service edit user. And it also needs to
have video edit user DTO. And let's just call that so on that side on the controller side,
this is done return this. We have of course not imported the the user service here,
we also need to import it through dependency injection, private user service, user service.
And now we can use that user service here return this user service added user user ID and detail
on
on the control side, it is now done. Let's go back to the user that service and write our logic
here.
So what we need to do is to update the user with the DTO. We know that the DTO will either
have
an email first name or last name. So we can trust the DTO. Because remember, we have this
function,
we have this pipe with a widely that's true. So we are sure that we are going to receive at least
something at least something in in in our in our API request. And we can say user is equal
await this Prisma user update where ID is user ID and the data that we're going to update, we
just
can just destructure the DTO. So if the video has something is going to be structured here,
and before returning the user, we can delete the user dot hash and return the user. That's our
edit
logic. And before continuing any further, let's go back to our tests and run the test for that edit,
edit functionality. So we come back to our tests. We have one Is that we have user get me. And
we
have edit user, right? So we go here and just copy the test here and right should edit user
is going to be users. And it's going to be a patch request.
And the headers are correct. And we need also the body, the body will have it to the to edit
user DTO. And let's just edit the first name of Lada mirror, and
just the email. Lat at code with blood, that and let's push that detail to the body with
embody the T O and let's see if we can get something interesting. So first,
let's see this test executes. So here it goes. should edit user, it is executed. Interesting. And
let's maybe inspect that. Where's the answer here is this. This is the response. So we get the
user
with the updated email and with the updated first name, which is quite cool. So that works,
we can also make our tests a bit more defined. For instance, we can say that, Oh, we expect
that
the body contains a field where we expected the body contains the first name Vladimir,
and an email of that, well, we can just say expect body contains. And we can provide a value. So
it's
going to be detailed that first name. And we can do the same with the to that email.
And let's see if our code executes. Cool. Our code is now working obviously, you can test it
out,
you can say expect body contains let's say I know false value. And that should throw an error
because false value is not part of the body value false value not found in response body.
So that's a handy way of checking if some fields are there, without going too much into details.
Okay, now let's go ahead and create the routes for the bookmark. So for the bookmark, we
haven't done
anything yet. We will need to have a controller and a service. Let's go ahead and create those.
So nest G controller, bookmark, no spec. And same for the service.
Miracle we can clean that out. And here what do we need? We need to have a couple of routes.
Okay, bookmarks, maybe put it here as bookmarks. We also need to use some guards. So govt
guards
need to have an access token in order to use those routes. So that is done at all missing imports.
Have we forgotten something else? No, I think we have adult we can close user controller and
Bookmarks e2e test
just work with bookmarks controller right now. So we have a route to get all all the
all the bookmarks, so it's going to be get get bookmarks. We also have get bookmark by ID.
We have edit bookmark by ID.
And maybe delete bookmark by ID. Let's check our test. If we haven't forgotten something,
create all create bookmarks as well. So we get bookmarks. Get bookmark by ID, edit
bookmark
by ID and delete bookmark by it and we also need to have a route for creation. So create
bookmark
that's it we need to have a positive decorator for create bookmark, we need to have a
get decorator for get bookmark by ID by ID. Let's move it above so gets are in one place.
We also have added bookmark by ID so it's going to be patch and delete is going to be deleted.
So we have the whole crud
crud thing. Let's also import the service because all our business logic will be in the service.
Private book mark So bookmark service, book mark, service. And let's copy all those functions
in,
in the bookmark service. And just clean the decorators here
here we go very well, now all of them will have to use get user decorator, get user.
And it will have to use user ID. Number. Sorry, it's actually goes into here.
And we need to fetch the ID. Same goes for here.
Same goes for here. And same goes for there. And the reason why I have to have these ready is
that
I can check if a bookmark belong belongs to a certain user or not. So when we are deleting a
bookmark, we need to make sure that we are not deleting the bookmark of someone else. And
when we get the bookmarks, we need to make sure that we are getting the bookmark of that
user. That's why we in that, so the Git bookmark will not need to have a DTO. This will not
need
to have a DTO, either, but it will have to have a parameter. So if we want to get a bookmarks or
get
book marks by ID forces, two, or five, well, this ID is called the params, we can get it with
another decorator. Here around that also comes from less common, and it's going to get the ID
and the Name of that variable will be equal to the variable that we defined here. And we need
to put two dots like that. And similar to what Express does express also have those parameters.
And there's going to be book mark ID is going to be a number. And
by default, this is going to be a string, but we can convert it to a number with parse,
and pipe, the Create bookmark will have to have a detail. So let's go ahead and create that
DTO.
Index Ts and create book mark that detail the TS export class create a book mark DTO.
And what will the Create bookmarklet to have? Well, let's check out our schema. So the
bookmark
has a title description, which can be empty, no link, okay, title, description and link. Let's
go ahead. And let's add that title is a string description is a string and it can be optional.
And link a string as well. And we can use this drink validation
is drink is drink. And this can be is optional. And this is is not empty.
Here we go. And we can export that create video from the folder.
Now we can close it all open the bookmarks and add the DTO here as body
the DTO will map to create bookmark
DTO and edit bookmark DTO will also have something similar. And I'm going to show you a
cool,
neat way to reuse our code. And here we have made an error it should be capital. And it
should be capitalized here because it's a class. And that should be fixed. Above. That's nice.
And the next thing we said that we want to have edit bookmark by ID by ID Okay, edit
bookmark.
Detail the Ts. So what we can do is to just copy what we have here added bookmark the deal.
And everything could be optional. That's the only difference here and here.
This is this is for TypeScript. No this is for the validation to know so you don't get confused.
We can change title description and link and we also need to export that detail from our
DTO folder. And then go back to here and the patch request will be edit. Bookmark DT Oh,
nice. And we
just need to have the one last route, delete bookmark by ID. Well, it also will take the,
the ID here. And we can just reuse the same logic that we have in get bookmark by ID. Here.
Here we go, we have everything that we need on the controller side. Now let's jump into
the service side. Let's jump into bookmark service. So what do we need to get to bookmark,
we need to have user ID, which is a number and that's about it, right? We don't need to have
anything else yet just to user ID, then we need to get the bookmark by ID, well,
we still need to get the user ID. But we also need to have a bookmark, it should be a number
and create bookmark, we need to have the user ID. But we need to have a DTO of create
bookmark DTO,
since it's going to be an object, same for edit, bookmark DTO dot
edit, bookmark DTO. And delete bookmark by ID is going to be user ID as well. And bookmark
it.
Like, like here, and that's pretty much all our parameters. Now we can pass
our R values here to the service, return this bookmark service, get bookmarks, user ID,
we also need to do the same here, get bookmark by ID, so just need to
change it here. And you also have the bookmark ID that goes there. And for create bookmark,
purchase similar except that the second argument will be d t Oh.
And here as well, it's going to be the same DTO but it's going to be
create bookmark edit bookmark by ID. And here we we actually have forgotten something.
Since it's added a bookmark by ID, you say the idea of the bookmark at which you want
to be edited and you provide the body so we have forgotten also to put a ID here.
And let's just copy it here and provide the bookmark, bookmark ID
and then the DTO. And of course, we need to go and modify the signature of that function.
And delete the bookmark by ID is going to be your turn this bookmark service delete
bookmark by that by ID user ID and bookmark it. Whew, that's, that was a bit tedious. But we
have
all the logic pretty much programmed from the from the controller's point of view. Now let's go
ahead
and jump into the service to to see how we can make that work. And we're going to do it
one by one. So when you write code, it's also nice to actually test it straightaway.
So you get an immediate feedback on how your code performs here is going to be very wide
because we
are running entwine tests. But usually you would first go with integration tests. But in our case,
since the application is quite simple, that's going to do it. We rerun the test
database to make sure that everything is updated correctly, all the files are generated as they
should be. We have our first tests that are being run. And let's go ahead and add the
logic for the second one. So the first thing is to create bookmarks. Okay, maybe we can,
we can first get the bookmarks, get empty bookmarks first, get empty bookmarks. So because
we want to kind of simulate the user experience on our app, if we imagine what the user would
be
with end to end tests, let's actually close that. Close this To make it a bit more readable.
So we have the signup functionality, the user signs up, then he signs in, then the user will
get himself. And then he can edit himself. So we can simulate what the user can do on the
platform,
then the user will go on a dashboard or something, and he will get empty bookmarks. Because
he
haven't created. He hasn't created any bookmarks yet. So let's go ahead and simulate that.
Let's copy this code and simulate it here. It should get bookmarks.
Return Pakhtoon is going to be bookmarks, it's going to be get requests,
the headers can stay the body, we don't need any, the status should be 200. And let's inspect
the result. And we see that the body is actually empty, nothing is there. And this is normal,
because we haven't written any any logic yet. So we can come back to our bookmark service
and write our logic. So let's import Prisma. Because it's not important yet.
Prisma Prisma service. So get bookmarks. We need to return this Krisna. Sorry, I forgot
private,
this. This Prisma. Bookmark, return file, find many where, where what where user ID
is equal to user ID, we can just leave it like that. So it's going to return all bookmarks
that are linked to us to the user that is doing the request. Let's see what it does return.
And here, now we have an empty array. And because by default, when Prisma will run, find
many, if
it's not going to find anything is going to return an array, because you are expecting to find a
lot
of elements, right? So what we could say is that inspect status, expect body. And here we can
say
what the body will expect. And it should be an array, an empty array. Let's see if that works.
Then we can go ahead and continue with our logic. And we can write the code for create a
bookmark. So same thing return, it should create a bookmark
return Pakhtoon spec post bookmarks with headers correct. And then we need to provide a
DTO.
So let's go ahead and provide a DTO here at that level.
Create bookmark the deal. Okay, the tie the title should be
it should be first bookmark. And the link will be Let's actually put a link to one of the videos of
Free Code Camp. I personally liked the video, that guy who teaches keep your needs are really
well done, man. And I'm going to save this URL in my bookmarks.
Here we go. And we put the DTO into our with body.
It should expect the status or two or one because it's a post request. And let's see what it does.
Well, obviously it will do nothing, because because we have not programmed the logic yet.
So let's go ahead and program the logic here this create bookmark. So in the same fashion, we
can
make it a sink and create our bookmark const bookmark is equal await this prisoner
bookmark create data. And what do we put here? First of all, the bookmark is created by
a user. So we can say user ID is equal user ID. And then we can just destructor the detail. And
then we can return the bookmark to see what it does. And let's see if our code executes. I'm
going to check here and inspect the, the, the response, let's inspect the response.
And we see that we have, we have created the first bookmark, it is linked to user ID nine,
which is the current ID and that is the second bookmark. So if we do it again,
we see that the ID is three is because while we're cleaning the database, we're not cleaning
everything, we just clean the users table and the bookmarks table,
there's another table that keeps track of the indexes. And that is not clean. And this is
normal, that is going to be incremented. But that's okay. It's not a problem in our case. And
anyway, you should not in your test relate to hard coded IDs, like here. So now we have
created a bookmark. And we can go here into get bookmark and re execute the same code. So
let's go ahead here should get bookmarks. And we can just copy it here.
And let's see that code executes. Okay, cool.
We had to get bookmarks and wish we had that. And we can inspect that.
And we'll probably see that we have an array. And in that array, there is our newly created
bookmark. So now we should have the get bookmarks and expect the status 200. Yes. But now
we know
that the body should be an array, and it should be one element inside it. So what we can do
to make a very simple test is that expect Jason length to be one. So we can expect the array of
bookmarks should have at least one element. So that's that we can then proceed and write the
logic for getting the bookmark by ID. So the way to do it is to simply should get bookmark by
ID,
which will copy the tests that we have already. So if we want to get a specific bookmark by ID,
we can write it like that. But we don't know the it right? We weren't sure about it. Because
as I've just said, we don't reset the ID is the that has been sequentially generated by Postgres,
for elements. So we need to keep track of the bookmark ID that we have created somewhere.
So let's go ahead and where do we create it should create bookmark, this is where we need
to use the stores API, like we have used for user access token. So stores is going to be
book mark ID is going to put the ID field of the bookmark inside it. So to use parameters
with Pakhtun, when to use their own kind of way of doing so we need to the ad here
and with params with path params. And it's going to be the key is going to be ID and the value
is
going to be the value that we have saved in our in our store. So let's see if that works.
Very well. But now let's write the logic. Because we haven't written the logic yet. And
we have the function here, get bookmark by ID is going to be pretty much the same. Apart
from
we are going to use Find first. And we need to get the bookmarklet that belongs to a certain ID.
And which ID is also a a bookmark ID provided here. And let's go ahead here. Let's execute
our,
our logic. And we see that now we have the right answer.
And we can go back here and do some basic assertions we can say expect
body contains and it's going to be what is it going to be the value, the value is going to be
this number which will which is which is going to be the bookmark ID.
Okay, let's see that works. Perfect. Let's now finish it with edit bookmark by ID and delete
bookmark by it. Before we go any further. Let's just examine if everything is correct, get
bookmarks. By user ID, that's fine. Get bookmark by ID. Yes, that's okay. Create bookmark.
We pass the user ID and the DTO that's fine. And edit bookmark by ID and this is where we
need to
do a bit more work. So first of all, we need to be get the bookmark by ID and then we need to
check
if user owns the bookmark. And then only we are going to do the modifications. So the
first part is quite simple bookmark is equal await this prisoner bookmark, find unique,
where ID is called bookmark it. And then if not bookmark,
or so the bookmark add specific ID does not exist or if the user ID at the bookmark
is not equal to the user ID. That means that is a bookmark that we try to edit
does not own to the current user, then we throw an exception, throw a new, forbidden
exception,
access to resource denied. That's it, and the function should be an async function.
And we can clean that out, we don't need those parentheses here, because we only have one
throw exception. And again, it's a god condition. So it's going to stop the execution of our code
below. So if the bookmark belongs to the user, then only we can modify the the user, then only
we can modify the bookmarks. So we will return the modified bookmark this Prisma bookmark
update,
where ID is called bookmark ID. And the data will be DT Oh, okay, just destructure the DTO
here,
and we can come back to our tests and just copy our test here. should edit bookmark by ID
should.
Edit bookmark is going to be a patch bookmarks by ID year. And we need to take a detail.
So the detail is going to be here and the to edit bookmark to and we're just going to
add the description. And the description was actually the we're going to add the title and
and the description. So let's just copy this
and we pass that DTO to the body with body.
Here, we expect the status of 200. And let's first execute it and inspect that to make sure
that we had the right things. Awesome, we had right things and we can just expect the title
and description to the to build those values. Let's I see that we have a whitespace here.
Let's expect body contains DTO dot title. And same for detailed description.
Let's see our test. Awesome. That works. And now let's run our last test. So usually when you
delete a resource status that is return is a
two or four. Let's see if we can we can actually do that. So first of all should should delete
delete book, delete bookmark. So it's going to bed delete with path params IDs with headers,
correct. We don't need any body. We don't. And let's see if that works.
Okay, one thing that I like to use when I delete resources, I like to have the code to have four.
So we're going to modify a bit HTTP code, like we have seen before HTTP status, no content
which is which stands for code to have four. And if we go back to the test and run them again,
we now have an error. We can of course go ahead and just modify that and we can return back
to our bookmark service and feel the last
logic here. And we don't want to return anything. So it's going to be a sync function. While
here,
we also need to check if the user owns the bookmark. So we can just copy, copy it here.
And if the user owns a bookmark, then we can go ahead and delete it. So await
this prisoner bookmark delete, where ID is equal bookmark ID.
And that's pretty much it that will delete our bookmark. Let's come back here.
And just after the Delete, let's make another API request should get empty bookmark.
And what we can do is that we can just copy the should get bookmarks here.
And and expect the Jason length to be to be zero. Let's come back and run our tests.
And yeah, amazing. We now have a entwine tested crud API with authentication. So I hope that
you
enjoyed this tutorial that you have learned something out of it. Of course, there's a lot of stuff
that we can still do. We can work on security. When we deploy our API,
we can run it in the cluster mode through pm two. And we can do a lot of amazing things
with that library called Ness GS. Furthermore, to improve the code coverage of this app, we can
add
integration testing and unit testing. But so far, my mission here is accomplished. I have
taught you how to use NES Yes, and how to make a solid use of its architecture. My name is
Vladimir, I hope you liked this tutorial, and see you for more ad code with flutter

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy