Complete Backend API in Golang (JWT, MySQL & Tests)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everyone so this video is going to be more of an in-depth look in how to build restful API in go and I have made a video previously to this one where we have built a project manageri but I've got a lot of people asking for a more complex and production ready project so I've decided to create this one video/ course that will teach you just enough for you to be able to go on your own and not having to watch another video on how to build apis in go I've made some decisions in the this video to keep things basic and I want to keep things basic on purpose because I want to teach you the basics I see a lot of tutorials out there that teach you all these Frameworks but you gain no value at the end and when you want to switch from framework to framework you'll have to learn that framework again and if you base your knowledge on a specific tool you'll have to relearn all of that over again so that is why in this course that I've kept myself to the core Basics so that if you want to learn a new framework or a new package you can do it more confidently and on your own terms which is more fun in my opinion and there is a couple of ways you can watch this video to get the maximum value you can either C along with me and you can watch everything sequentially there is a lot of information that I will cover or if you're just looking to learn something specific you can quickly jump to that part in the video notations below I'll make sure to add them and at the end I have prepared a couple of exercises where if you want to put your knowledge into practice I have written some improvement exercises for you to try them out so with that out of the way basically what you're going to be building is an e-commerce API and in there we're going to create bunch of things such as we're going to addication we're going to create all these endpoints here we're going to test everything so we're going to create unit test for everything we're going to connect to an external myo database and at the end we're going to also doize everything so we can just ship this image to the cloud and everything is done and I'm going to show you how you can create all of these services and how they can communicate between them and we're going to also do that with dependency in injection so it's going to be very easy to test and we're going to create everything using migrations on the database so we can just reproduce everything here on any database you want and for example here on the uh checkout endpoint there is going to be a lot of calculations done here because we're going to have to do inventory management so we're going to check if the products that the user wants exist if they have enough stock in the database and then we have to calculate the total and sends an invo to the users so I think this is a very cool project for you to build and then again at the end of the video I will leave an improvements list so we can actually make this project a bit more of your own all right so let's actually get started and here I have created a directory called Ecom and I'm going to go mod in it and create the name of the project so I'm going to point to my uh GitHub repo and then the name of the projects so the very first thing that we're going to do is that we need to create an http server so for that we need to create some sort of a Handler so let me go here and actually start by creating a CMD folder so in go it's convention that we store entry points of the application in CMD usually when I create apis and in this case we're going to have two entry points the first one is going to be the API itself so let's create the folder for it and then the other one is going to be the migrates the migrations folder uh is going to be a separate program from the API so inside here say create an api. go and it's going to be inside the package API then here on the CMD I'm going to create a last uh file which going to be the main not go and this is going to be on the package Main and this is actually the entry point for the API so here I'm going to create a main function and this main function is going to create a new API instance so a server instance that we are going to create here so let's create cre actually a new struct called API servers and here we're going to hold the address so it's going to be a string and then we're going to also add the database so the SQL do DB then let's create a method called new API server so this function here is going to create a new inst of the API server so here it's very simple let's just return a pointer to API servers other DB and then and then the method that we needs to create here is the run because here on the main what you're going to do is that we're going to have a new server instance so we can actually just go here to API do um new server new API server pass in other we don't need don't have these informations yet so I just ped here a string with 8080 and then the DB which could be no for now and what you could do is here do a server. run and we check if there is an error and if there is an error we basically just log it to the console now the Run function will do a couple of things so the first thing is that it's going going to initialize a router register all the routes so register all the service routes and their dependencies and then at the end we're just going to listen and serve for the router so here we have the other from the instance and then we can pass in a router so for the router I usually go with Gorilla Max because it's simple enough and although I'm using the goang 1.22 and I have built projects without using any dependencies on this but but I know that most of you are not on this version of go so I'll keep using Gorilla maug for that reason so here let's create a new router now if you want to switch the routing for anything you'd like this is the place so just need to change on one place and this is going to be very uh modular and then I'm going to also create here a new sub router so basically this is going to be d/ API V1 perix uh first thing here we need to actually import G Max and then here we can actually use the API path prefix and then the prefix going to be API slv1 so the reason behind this prefix is so that if the API changes in the future we have a new version so we can just increment it to version two this is because clients that might be consuming our API should not have breaking changes so this is one of the specs that AP restful API should have then I'm just going to add here a log so now that we have the sub router we can actually go here and start creating our handers but this function would become pretty messy if we start creating our endpoints here so we are going to separate everything into services so why not create the routes in the service itself so the first service that we're going to start with is the user service so let's go ahead and create routes. go and each service is going to be of type handlers where the Handler can take any dependencies so this is where the dependency injection comes in and let's just create a new Constructor for the Handler new Handler and for now just going to return the Handler an empty one like so and as we wanted let's create here the register routes function and this function is going to take in the router so this is going to be the sub router and what we can do here is do router. handle Funk and here it's a signature so first is going to be the SL login for example and then here is going to be the Handler so here we're going to have a new method for the Handler let's just create an empty function here called handle login and put it here now this receives an HTTP handle fun so if you recall what that is it's going to be a response writer and then requests a pointer to http do request and you don't need to call it and the method for this endpoint is going to be slash poost let's duplicate this for the register as well and I'm going to just duplicate this function here call handle register and here we have an example of a service so of the service is going to be pretty similar following this structure so we can actually make them very modular so here on the API let's go here and create the user service or actually user Handler whatever you prefer to call it going to be users from my package and here I create a new Handler and then what we do is that we do user handlers. register routes and we pass in the sub router so they are prefixed with the V1 API and this is pretty much it to register the HTTP server so here on the main we should be having a server connected so let's go ahead and um do a go run CMD may. go and we have the server listening on Port 88 now I don't want to be typing go run every time so what I usually do is I create a make file and here we can create a couple of commands that then we can just reference them easily on the console and this is what I'm going to do so here I have a build command attest and the run so whenever I do make run it's going to be the same as doing the go run uh CMD that we just did so that is done so so here on the login there is it much that we need to do it's pretty much set up so this is how we're going to register all the routs across all the services so for this one just for the users uh but whenever we want to register routs we can just come here and add a new uh service so the next logical step would be to fill this with the database connection so let's go ahead and actually do that so here I'm going to create a new folder called database or DB and in DB I'm going to create a DB do go so this file all it does is going to create a new MySQL storage so let's just call that new MySQL storage and it's going to receive a configuration from the myql package so this is the driver package that we need so whatever database you're going to use you can just change the name of this function and import the driver that you want so whatever database you're going to use you need to import according L to whatever you want so I assume that you know what you're doing then uh here we have the database connection that we need to open and it's going to be my sqle of course and here the configuration let's just transform it to a format format DSN and then let's handle the error where here basically could just log. fatal if the database is not working I mean there's not much we can do and we return the database and na error so here that on the main what we can do is that we can get the database from db. new myl storage and here passing a configuration so let's go ahead and create one my sq. config now the first thing that we need is a user so I'm going to put this as roots then the password is going to be something then here we need the address now the address is going to be the combination of the host and the port so it's going to be something like um this is just a default Val you're going to come to this into bits like so and then we also need the db. name which I'm going to call this e-commerce or Ecom for short then the net is going to be TCP and I am going to set the alow native passwords to true and finally the par time to true as well now of course you don't want to have these variables here are coded because these might not be your variables that you have for your connection or if we're going to deploy this into the cloud it doesn't make sense to have uh the password and the user here art coded so the best way to handle this is with environment variables so let's actually go ahead and create a new folder code config so this is going to hold all the server configurations and the first thing that we need is the environment variables and if you're familiar with any of my videos this is what I usually do so here we have a type config which is going to be the variables itself so these are the variables that we have just written the only thing is that here I have the server port and the server host as well and here are the database variables and then what I like to do is create a function called in it config so this is going to return a configuration object and here we return the config the struct and here weate basally the match all of the variables and to get the variables um let's create actually a function called get M below here which takes um the key and a file back so in case the the key does not exist so so in case the value of the key does not exist I mean so we get the value from the I think it's OS do look app M and here we pass in the key if it is okay then we can just return the value else we just return the file back instead so here is the key I'm going to just call it's public host just like the name and the fileb is going to be HTTP or call host without the port because then the port here below it's going to be the same thing so port and the 8080 is what I usually use good default and then for the DB it's just the same Val that we have written so I'm just going to pass that forward here they are the only thing here that I have dynamically po populated the address from the host and the ports so then here on the main now we can actually change these values here but before that I don't want to be initializing this function every time so I'm going to create a Singleton called M which is going to hold the initial configuration so this is going to be just a global variable that we can access like so so we can do configs dot or config Dot Ms and then we access the user uh like so DB users and I'm going to replace for the other values as well like so so these are the new updated values I'm just going to pass here the database below and handle the error which we can just do a log do fatal again now there is just one thing missing uh in case you're using the M file so I know a lot of people like to have this do m files with the variables here usually what I use is a two called deer M that directly injects the environment variables into the runtime uh but I know a lot of people like to do this so what I'm going to do is I'm going to include the um go. M so if you go here to the browser this is the tool that we're going to use basically all we need to do is call the go. load and it's going to read all of the variables that you have here on M and load them into the environment's uh runtime so to do that let's just copy here the this import it and this is done so to that's set out let me do a mic run and see if everything is running on the same part so 8080 and we need to do some um yeah we need to initialize the database connection because we have open it but this does nothing uh this does not connect to the database for that we need to do um I'm going to call it storage and pass in the pointer to the SQL database and then here what you do is the DB doping and this is what actually starts the database connection so it checks if everything is all right and here we just blog. Sprint uh and say that ddb is successfully connected and we just call this after we have like here pass in the database now before we can test this we should actually create our database so here on my SQL auditor basically you can create your database whatever name you give it and then we can actually go ahead and test this and we should be connected to the database so if you get an error something is wrong with the credentials most likely so we that not on we have covered the environment variables and how we can configure our server very easily and we have also connected to the database so let's then start creating the register endpoint that we were here let's start with the register I actually need to change this to the correct one so the first thing that we need to do here is that we need to receive some sort of payload some Jason payload we need to pass it validate it and then we need to check if the user exists so let's start writing that so here we do the get Json payloads then we need to check if the users exists if it doesn't we create the new users so this is pretty much the flow now to get to the payload let's create here a new folder called types so this package types here is going to contain all of the application types so we can use them all over across the services the first type is going to be the register user payload which is going to have the first name so we're going to create the user's first name then we have the last name maybe the the email and of course the passwords so this is going to be the structure then the next thing I'm going to add is the Json marshalling here uh the first name like this and the same for the last name the email and then the passwords and here all we need to do is get the json. new decoder standard then we pass in the body and we decode it into a variable so I'm going to call this payad which is going to be the types. register user payloads so now we already Conn the payloads into this object and before this we should also validate if the body is actually nil not only that but if you think about this function this here could be moved into a reusable function that you could use across all of end points because this is something that we're going to use constantly over and over so let's do that let's go here and create an utils and create the u. go package U and here let's create a function called pars Json so it's going to receive the requests a pointer to the HTTP do requests and the variable that's going to receive to the coding so it's going to be the payload I'm going to call this it's going to be any could be anything it's going to return the error and let move this logic here we could just return instead we don't need this so this is going to stay here we return it and here we can return an error that's the missing request missing request body and here what we do is that we check if there is an error from this call and we can do utils par Json pass in the body and the payload and if there is an error we need to write to the user again let's take this opportunity to create a new reusable function here that basically is going to return Json so we have the pass Json and we can also do a write Json here it takes a response writer a status code and then anything that could be the output um it's going to return an error as well the first thing we do is that we set the header for the content type so this is going to be Json API so let's add the content type for application Json then let's write the status codes here as well and finally let's just return this into a new encoded this and then we encode the payload out like so so here we have a function to write Json to the user uh I'm going to take this opportunity to create as well just another one this is the last one I promise which which is the right error now there is a good reason to create this function now this function is just going to write Json and it's going to do pretty much this here but the payload the V is going to be of type error so the reason for this is that we can have a standard output to the user Whenever there is an error so I see a lot of apis out there that return errors in multiple ways a string an object and this way we can reuse this function every time we want to use an error in this case return an error so this is going to be the signature now the advantage of this is that we are consistent and any frontend any client that consumes us can expect an error in this signature here um so let's do this here uh right error we need to prefix it with u right error which in this case is going to be a b requests because it's an error from the [Music] user and here above we should also pass in the request not the body and this is pretty much it so we've added a lot of U functions that is going to speed up the development of the next end points and on step here we need to actually go to the database because we need to check if the user exists and to do this we need to go to our storage to our MySQL and check if there is a user with the same email as the one that is requesting the register so the way that we're going to handle this for the services is that we're going to have a storage layer here I usually call this store and if you are familiar with the repositories pattern this is what we're going to do so all of these store. goo files are going to be repositories so this here is pretty much a repository it's a type store which depends on the database and it uses this database ways to make queries so for example let's just create here a new store um like so and we turn a pointer to store and here we return store database database and for example this is going to be the user so let's have here a method called get user by email and it's going to receive the email and it's going to return a pointer to user or an error now we don't have yet this user nor these type so let's go ahead into the types and create our user and if you remember from the beginning this is how we have designed the user structure so it's going to have an ID first name blah blah blah and this is what we are translating into go here in the STS so now we can just import the types and it should be working awesome now as I've said in the beginning here you could use anything you could use an nor you could use SQL X which is very good solution here actually uh but I'm just going to use uh the raw SQL here the queries uh it's going to be very simple it's not going to be much if he wants to abstract this away you can easily do but at least you know what's happening here on the repository level um so we're going to search by email passing the email if there is an error we can just return nil and now we need to scan the user so let's do a pointer to an empty pointer to user and then we can just Loop over the rows that's next and if there is something here uh usually I like to do here is create a function called scan row into users because couple more functions are going to use this functionality basically it receives the rows which is going to be a pointer to the SQL do rows and then it just Returns the pointer to types do user error and here we scan it we need to create a new empty types. user and scan the rows like so so we use the scan syntax and here we basically pretty much just filling up this user with the columns that are from that are coming from the database so the user do first name last name email and password it can also help us here the created ads and at the end we check we just return user and nil should also handle this errors if errors like so so that here what we can do is we can do this so scan users into row pass in the rows handle the error and actually can just return here because if there is the user uh we have it and if the user do ID is actually equal to zero it means that it was not found as well so we need to handle this case and I'm going to throw an error calling users not found and at the end we just return the users and nil like so and here actually it's not a new declaration so we need to remove that here but is to work so with this we have our first repository now we need to inject this dependency into the routes Handler so to do that if you remember all we need to do just like the store repository we need to say that it depends on the store so it's going to be depending on the types dot user store and let's create this type as well which is going to be an interface now the trick here is that this is going to be an interface now the reason why is that we can actually easily test interfaces in go let's say for example that this is the function so this is the interface uh that we just created here uh and this is the implementation which actually matches so it means that any instance of this store here is going to be valid uh avable for an interface and if he wants to create a test to test these routes here all we need to do is create a mock interface of the user store it could be just a very simple uh function like this which has to have the same signature so it would need to return a user as well and here it could just return nil uh mock user store for example which could be a stct so this mock user store here that just created very quickly it's a valid dependency for the user routes here so for the the user service because it implements this interface so you can see how easily you can actually create tests using this approach now the user store is going to have a couple more methods we can just Define them here for example it's going to have the get user by ID and then maybe a create user uh so this is what we're going to call next on the register so let's go ahead and create the empty signatures for those methods and then we can Implement them like so so just returning nails and then we come back to these guys and Implement them and now let's actually see if the uh so here let me save this and then on the api. go let's create um now we need to change the new Handler actually because it's receiving a new dependency we need to initialize it so it's going to be the this here like here but actually this is not should not be a pointer and let's just set store equal store now in the api. go we need to update here to receive a new uh a new store so it's going to be the user store so let's create here user store user do new store and it's going to receive the database so it's going to be the S do DB and this is how we can easily do dependency injection in go lank now let's go back here to the register and do the H dos do get user by so now we can have access to the repository in any method that we want to create let's passing the payloads do uh email check that this returns a user and an error and here we check if the error is equal to nil and if it is it's because the user actually exists so we don't want to proceed so we just return and then if the user actually does not exist then we can easily create the like so with the method we have created and this is pretty much it for the first stage I'm just going to handle here the error very quickly as well and then we can do is return to the user with the right Json pass in the status created like so now you might have noticed there is a small problem here in the problem is that we are not encoding the password so whatever password the user sent is going to be stored on the table has plain text and of course we don't want to do that so first we need to Hash this password somehow so let's start that into varable here and for now U I'm just going to keep it as a string here and replace it and to Hash this password what I'm going to do is that I'm going to go into these services and I'm going to create a new o service so everything related to authentication encryption I'm going to store it here and I'm going to create a file called password. go in here I'm going to create a function called hash fun hash password and it's going to take the original passwords and convert it into a new string or return an error so when it comes to authentication uh encryption all of that stuff that makes your application secure I usually like to and I recommend uh relying on external tools that are bulletproof that are on the industry and so one of them is bcrypt like so so I can just import it and unload it and they have a function called generate from password where you pass a bytes so it's going to be the password has bytes and here you pass in the default cost or any cost I just like to use the default one like so I'm going to handle the error so if there is an error we can just return an empty string and uh error here else we can just return a string of the hashed and we don't have an error now let's go back to the routes and here we can do o do hash password pass in the pad. password and also handle the error like so now here we are passing the hashed password and not the point text password now this looks fine for the registering point I think we are pretty much done with it so there's two things that we need to do first one we can of course is test this but we do not have any tables created in our database so we need to create the migrations uh but first let's create some tests here let's create the routes. test. go and test these routes so here I'm going to create a function that is going to have all of the end points so I'm going to do everything here called test user service handlers and the first thing we need is a Handler so the user service Handler which is going to be just new Handler and here we need to pass the dependencies so we need to pass the user store now this is where um I've mentioned that we could easily mock because this here is an interface so as long as we have an interface that's obas this signature here we are good so let's do that and I'm going to show you how you can do this for all of the tests that you do so here let's make a user store and this is going to be of the type mock um user store for example and let's create this type below here hidden it's going to be an empty strike without anything and then the most important part is that we need to implement the um the methods from the interface right so let's go here and double check so the first one is I'm just going to copy this here open a new window actually like so so that we can see what we need this needs to be types. user and here we don't need to implement anything if we want to implement something you can actually Implement here any custom logic so you can test against that and I'm just going to do the same for the other ones so this should be it I've just added these two and if you take a look the error is gone here and if I just remove this function you can see there is an error which basically says that it's missing the this method for to be a right interface so let's start with our fast test which for example should fail if the user uh payload is invalid so all tests are going to be pretty similar so I'm going to focus and explain this first one and off of the subscri T that uh I'm going to skip ahead and just show you what it does so you can just learn once and then you don't have to uh hear the explanation again so we're going to make here a request to that endpoint so the method is going to be a post a post method and then here it's the URL it's going to be SL register and then here it's the body right now we need to pass in the payload so let's make one here and encode it because what we want is um types do register user payloads need to pass the first name uh like so something random here I'm going to keep the email empty I could pass for example some random passwords and now we needs to this accepts here a reader now we needs to First do uh Marshals into adjacent do Mar and here we pass in the payloads I'm going to ignore the errors and pass in here into Ayes dot new buffer and pass the marshals then I'm going to handle here the error very importantly and do a t. fital so we stop the test so the test fails now that we have a request what we need to do is actually make the request against the Handler so let's make a test recorder HTP do test do new recorder then the router which is going to be a ma. router or depending on what router you have then here what we do is you do router. handle fank so it's just like the registering and here it's the URL so it's going to be the register and here we pass in the Handler that we want to test which is the um register Handler and here we make to serve HTTP the request we just made like so and the recorder basically the request is made now all we need to do is check that the request or the response code is um if it's different from a bad request because because here on the register we are throwing a bad request if the user has an invalid payload so basically we need to file here and I'm going to say unexpected status code and it should be the correct one here so let's go ahead and run this test so the test passed so it seems to be everything all right so let's actually make it work so I'm going to pass in here in mail actually I'm going to keep like this just to show you that this is an invalid ml but it's going to work and that is going to be a problem that we going to fix and you can see that something is wrong here because the test is still passing even though we passing a valid U payload so this function here is not being CAU so actually the test is still working it's still passing although this here should of course fail but what's it's what's probably happening is that it's going past here and it's failing either here actually it's probably just failing here so um basically the error is going to be nil yeah it makes sense that this function here uh it's failing so how can we do and make this function actually not show no error so that's where we can come here into the get user by email and for example instead of throwing a new error let's for example show um new error say user not found so for example it's the first time the user is registering and if you run this test now it should fail now the test did not fail which shouldn't have and I think the reason is that here on the par Json uh there is two a couple things here so first is that we need to is returned that was missing and then the second one is that here we need to pass in the pointer uh so otherwise the pill is not being filled so that was why it was failing so if you try again we can see that it now fails so the status code is 21 and of course H it's not what we want so let's try to make this failing again it keeps failing and the reason why it's keep still failing even though the email here is empty if I even remove it from here I think it will still pass and it makes sense because the um here the par Jason is doing his job so if I actually did this so to actually make this fail for example just to show you uh let's go back here to this email I'm going to remove this you can remove this and here I'm going to pass in nil so basically wants to match this condition if we try again we can see that this passes because it failed so U if the user sends an empty payload it's going to catch here on the part Json so we can make sure that that part is working uh but what I want to test here is that really passing an empty email or passing something that is not an email it should fail and for that we need to validate not only we need to pass the payload but we need to validate the data on the payload now for that could easily come here and validate it so validate the payloads and we can do it has his simply has just having a function where we check the payload do email check the username all of that so that becomes a bit verbos if you want to scale this uh into more end points and all of that so there is a pretty cool tool that I like to use for this so it's a very small BL print package and the reason why it's so special it's because all we need to do is come here to the this signature here and add a new um field for example let's come here and add the validate and for example the first name needs to be required so we say that it's required I'm going to do the second uh the same and email needs to be of type email as well and for the password it needs to be required and it needs to be a minimum of three and a Max of uh let's say 130 and then here on the routes let's go ahead and check how we can use the the the validation signature so this is the package it's very well known and the way we do it is basically we can just importance and validate the Str like so uh but first i'm going to make a reusable um function here called validate where I'm going to call the validator like so from the version 10 and then I say new this basically is going to be a single 10 for this validator and we need to import it so every time we want to use the validator do it here reason I'm doing this it's because here they uh recommend us for doing caching so every time we need to use it there's no need to initiate a new instance we can just reference to this one and here all we need to do is Let's uh just check the error from the validation so u. validate do Str and here we pass in the stru which going to be the payload and if there is an error we're going to create an object called errors and we're going to say that the type of this error is going to be validator do validation errors and then we're just going to throw um to the user that he has errors in the validation so it's going to be a bad request as well and we're going to try to send multipliers to the user this time so it's going to be invalid payloads where we pass in the message here from the errors like so and we return so let's go back to the test and actually I'm going to keep the email has in valid so we are expecting this to work because it's going to throw a bad request so it's going to fail let's see if that is the case and we got some pretty bad eror so something is wrong I see so I'm made a typo here on the types so here yeah instead of being mix it should be Min does it make sense so let's go back and try again and here we got the Test passing so we got a badge request and what happens if we make a valid email basically the test should fail and it fails so we can make sure that this test is working and let's move to the next one and let's for example test the happy for so uh just to keep it short let's try to see should correctly register the user so it is the flow where we send all of the pill correct so let me just copy everything here and paste it down below and here let's make this valid email and here I'm just going to say invalid to make it obvious and this should throw a created like so and let's go ahead and test this so both tests passed awesome so as you can see it's pretty easy to test your end points here in go following this structure uh it's just functions calling functions uh there's no magic there's nothing anything here so of course we want to test this functionally and see if we make an HTTP request if everything works and we have something in database so to do that first we need to create the tables um we could go ahead here and create them manually but that is not reproducible in other developers Machin for example if you have a team of Engineers and they had to create the tables manually that would not be very pleasant for them so let's create something that they can just uh script that they can just run and create the tables um on their machines so to do that let's go here into the CMD migrates and let's create the folder called migrate so database migrations is a way for us to keep the history and the changes of the database so we could have some changes to the database all we do is create a new migration we plate the table play the column we create some new data there and all of that is going to be here on a log of migrations all we need to do is execute them now the advantage of this is like git so we can have an history of the changes we can have an history of all of that with a really good uh benefit from that that it's reproducible so if you run it once it should be the same output every time so to get started let's go here into migrate and let's create a new main. go this is going to be the entry point for the migrations and here we're going to use our last tool our last external tool is going to be this Migrate how it does it it handles off of that it takes for example our MySQL driver that we already have installed so we don't need to do much there but we need to write here a little function that uh actually executes and connects to the database so let's do that so this is going to be a new program so it's going to be a main here we can do that and we can reuse the code that we also have on our mind so this here uh where we have the connection to the database and the configuration we can copy this and then here below we can use the tools it's called migrate and it's going to be this one the goong migrate let's just check it's the V4 that is correct so let's import this after that is done let's use the new wi database instance here we pass in the source URL which is going to be where we have our migrations so it's going to be CMD migrate SL migrations it's the folder that we have just created here then we need the driver is going to be the myl one and we pass in the driver itself now for the driver what you need to do is initiate a new driver here so let me actually go to the database to the documentation and check the documentation for our database here to my SQL and let's see what do they recommend so here they are creating the driver like so so let me just copy paste and this my sle let me see if it's coming from yeah it's coming from here so this is the driver that they have let and this one here we actually need to rename this one is coming from so this is the one that we had let me just do my SQL config do like so and that should be it so here we have the driver passing the driver but I'm going to handle the error if the error is different than nil I want to know why it failed like so now here they also import the source file like so so let me just make sure that I have that and here to import the go SQL driver by we already have it because we are passing the configuration here so we are done with that here I'm going to also handle the error I'm going to copy paste and now all we need to do is either call m. up or m. down depending on the migration because when you create migrations there's two types there's the up and the down if you want to do the changes you call the app or the latest and if you want to revert you need to count it down so it's going to revert all those changes so here let's create let's see what command we wants to pass into our CLI and then here if the CMD is equal to up we do something if the CMD is equal to down we do another thing and let's just call the method that is the m. it throws an error so we need to handle it as well so if the error is different than nil and if the error is different than migr do no change here we log it and I'm going to do the same for down but change the commands to down like so so of course here this is an in our application so let's go to the make file and add a new command here I'm going to call the first one to create a migration so it's going to be the migration create so if this command fails on your machine all you probably need to do is install their CLI on your machine as well so here you probably have um instructions how to do that you can use on uh Docker as well but after that you can just execute this command so this command here I have it already I'm just copying it on another monitor here just like so here so we can create the migration let me just add the other two so it's going to be the migrate app it's going to be gun CMD and this is going to be the path and then I'm going to do the same one for the migrate down like so and this is how we can do it let's try and create our first migration so here let's do migration and I add for example the user table add the user table there we go it worked here we have two files as you can see so the first one is going to be the down and here we have the up so I need to do here is create SQL so we can just write SQL statements here these are SQL files that are going to be executed so what we want on the app is to create the table users if it doesn't exist for example create table if not exists users and then here is just going to populate the properties that we want for example it's going to have an ID which is going to be an anid integer if I can type it's going to be an auto increments I'm going to remove here the primary key it's going to be not knowable as well and I'm going to move the auto increments to the last here like to have this formatted like so and then here you're going to also have the first name we already know that which is going to be for example the last name and the email and I'm going to keep filling the fields like so so this is the same structure that we have on our types if you check the user the only thing here is that the def find the primary key and the unique key on email so you don't have duplicated users now here on or down what you need to do is drop table if exists users like so now to run our migrations again make sure that you have your database created called Ecom or the name that you give it and then we can run the command make migrations how it was the name it was Migration app like so and now now if you go here to the database if you refresh you can see here you have the users table and cool thing is that if you do the down you can see that the user table got deleted but here you still have the migrations uh empty so this is some metadata that the package creates now since we're here why don't we create all of the migrations to create all of the other tables so the products the orders and the order items so that is what I'm going to do now so here I have created the products table this is how I made it and I have also the the ordered items and the orders so if you want to follow along stop the video and copy along or you can get the repo from the description below now let's go ahead and run all of this uh with make migrate app and if you check here we got all of the tables now before we can test we need to actually Implement here the get user by the and create users on the repository for the user service so let's start with the create user so here it's pretty easy all we need to do is do an insert into the users table so let's do uh inserts into users and we're going to insert the first name last name the email and the passwords the hash passwords and the values are going to be one 2 three 4 and here we put the user. first name if the error is different than nil we can just return it otherwise just return nil we don't care about the response for now if we need it we can add it so that is done so let's go to the get user byid and here it is so I just copied from the get user by email and changed here the the we parameter so it's very similar to what we have done so let's go ahead and actually test this so let's do make run and now here I'm going to open my HTTP client which is called standard clients and here I can make an HTTP request you can use Postman or wherever you prefer so here I'm going to send this pilot the email valid password and the first and last name and if you hit send we had 2011 and if you check here the database we can see that we have an entry with the user ID one and here is the password hashed so uh that is working as well and if I try to send a new request we got to B request because the user with this email already exists so let me just try again here and invalid um payloads and here we got also validation error so let's quickly move out and implement the login so the login is going to be very similar to the register so here we can actually just uh copy this here and instead of this payload here we can copy and paste here to be the login user pilot and here I'm going to delete the first name and the last time we don't need this and here this validation it doesn't make sense as well both fields are required so here let's change now the next step is not that we have the pillot we need to find the user so let's go ahead and go to to the store find user by ID or by email in this case uh and we pass in the pad. Emil again we can copy from here the actually we cannot because this is a different validation so let's do it and don't be lazy and we just return not found invalid email or password so we're not going to say what it is so so the next part is we need to check if the passwords match so the password here on the database which is hased and the password which is plain text that we just send on the request so if you think about it uh you need to Hash the password at was sense and compare both passwords now we can do that with the help of the bcrypt package so here we can actually create a function code uh compare passwords which is going to take the hashed passwords has a parameter and then the plane one has a string has an slice of btes and returnable and here all we do is you call bcrypt do compare hash and password so it's going to do what we have explained we pass in the hash and the plane and we return the error is equal to nil and here we can actually just do this so if the o. compare passwords the user. password and here we can trans to bites the payloads do passwords so here just going to do the same as above so I'm not going to say that the password is wrong so it could be the password or the email not going to say which one and at the end we could just do. WR Json and say that it is a status okay and the response could be let's do something like this something different and pass in the token and you're going to pass in an token for now so if you think about it when you log in this because we're going to make a JWT session so whenever a user makes a request to an authenticated routes for example you're going to start working on the products after and the checkout when the user makes a request these routes it's going to be authenticated so we need some sort of token here and this is what we need to create so let's go to the O and let's create a new file here called J.O and in here we need to create a function called create U jdb which is going to receive a secret so this is going to be needed a user ID and it's going to return a string and an error now the way you can make the token is going to be with the JT uh package so JDT dots uh new with claims because we're going to put metadata inside it it's going to be the signature for the uh s h250 this one here then we need to do the claims map claims it's going to be an object and here you can pass in the user ID first we need to convert it to like so so here actually can we do this okay so this is you don't need to do that so the expire Z is going to be the expiration of the token uh here we need to add some time um let me just do Unix so the time here is going to be the expiration that we need to configure I like to make this a configuration of the server so expiration uh and here we can actually Define it so this is going to be time. seconds and here let's do time do duration and insides let's go to the configs our configs MS and let's create maybe JD expiration in seconds and inside here it's going to be a string and down below you can make this an environment variable and we can make it the default value for example be seven days so it's going to be * 24 * 7 so a week so the token is going to be valid for this time now this function that I have created does not accept uh integers so let's create here another function called get M has in it's going to receive a key a fback a key is going to be a string the fback could be an integer and it's going to return an integer I'm actually going to make this an in 64 and here the employment is going to be very similar to what we have above but here we're passing the integer so here um you can do get has int and here we need to make this an INT 64 as well and now the error should be gone so now that we have the token let's do the token has a string so here error uh let me just delete this import because it's not valids uh token do signed string it's going to be signed string and we pass in the secrets and here I'm just going to quickly add all the errors so it's going to return an empty string and here an error and we can just return the newly created token and a Neil error so here let's go back to our login and create the token so o do create it's going to return an error as well and now we need the secrets now of course the secret is going to come from the environment varibles but we need to transform it into a bite uh size so here let's go to the configs do Els and do JT secrets so again we need to create this inv variable very quickly here so here I have created the secrets as well on the config so we are returning it as well here we have the secrets passed into the function and the next argument is going to be the user ID so it's going to be your do ID we handle the error hopefully it doesn't fail here but I'm going to do an internal server error and here we can replace the token with the actual token save and let's actually test this Endo as well so here I'm on the login so let's try again with this user so I'm going to try with the wrong password first and we got 200 so this is is working which shouldn't be working right here I'm going to start the server again and here I'm going to make the request with the wrong password first and here we got the bad request so we don't know if the emo exists but if we try with the good one so we got the token so the user is going to be loging with this token and if we inspect the token so you can go to this application so the. and here if you P your token you can see the metadata that we have just added to our tokens here is his expired that and the user ID so we know that is a Valu token here so here I have quickly added a couple of tests for the password and here I have also added for the JWT so we can have the authentication testable now that we have authentication we can authenticate the uh services so let's go ahead and build the uh get products so for that we need to create a new service called products or product because I'm keeping this in the singular and here let's do again what we already learned so we create r. go and it's going to have a repository as well it's going to communicate with the database so here it's going to be the package product on both files so here I have just quickly set up the uh repository for the uh products and now we need to add some methods so let's first go here to the types and add below here the product store and it's going to have the get products which is going to return uh products types size two products and an error let's create the product as well here now here is the product definition from that diagram now there is a very important thing here that I want to mention this is not the best way to implement the quantity here because if you think about I am storing the quantity on the product itself so if you have here on a database multiple products the quantity is going to be an integer like uh 42 items you know uh and this is not the best way to handle this this is because this is not Atomic so in terms of the acids in in the database terms uh this is good enough for now as an example but the problem is that with multiple concurrent requests this quantity can be uh false so here on the Improvement exercises at the end of the course one of them is to implement a better structure to this so I have a suggestion that has well there that you can Implement okay okay so we that out of the way let's implement the get products uh interface here this is going to be from types products like so so very similar to the users I have just made here a very easy select and here I made scan RS into product as well this is how I did it very similar to what I have done on the users so now here on the routes let's create the Handler and here is the basic Syntax for the Handler that we have already seen and here we know that it is already going to have a dependency which is going to be the uh repository here so it's going to be the product store and here we can pass it as a parameter as well store store and here we just need to register our points like so so here I have just created an empty Handler and this is going to be the signature of the gets all products and instead here is going to be a get and now what we need to do is just call that method so it's going to be the h. store.get products we're going to have a list of products an error let's handle the error here so we have this just an internal server error and we write to the user um here here right Json we can pass in the status okay and the products so this is how you can easily do a get all for products so let's just go ahead and test this out uh actually first we need to go to the api. go and register our new service so it's going to be the brand new products service are actually calling this Handler so e products do new Handler we need a product store product. new store and here the database and here pass the store and then how we do is register the routes like we did and we pass in the sub router like so so now we can actually test make so here if I hit send we should get the list of products and it works because it's giving 200 but it is not returning any product because we don't have any product so let me just create here Demi product and this is how we just created a basic product and if you hits the Endo again we get here the data so we basically just Marsh out from the database and here we have it now I'm going to leave to you to create the post method so the post method is going to be very similar I just make here the posts and you need to create some sort of types for the payloads and then it's going to be the same thing as the uh the users so you can go here into the store create a new create product method and then here thereout all need to do is validated payload create on the database and return to the user so I'm going to leave that to you because I think you can do it now that you have the products Endo let's go to the actual fun end point is going to be the checkout so this is going to be a big one so let's go ahead and skip to the service here I'm going to call this card and just like the other ones it's going to have the routes. go it's going to have the start. go and I'm going to quickly fill this up with the empty power PLS so here is the power plate for the service Handler so you just have the same thing here the resistor routes here's the signature I have just filled up with the empty checkout function and here I have also created the card store which is going to be um like this and here is the order and the order item from the database like so and actually I am thinking now I'm going to change this to be the order store because the cart actually does not have a store so what I'm going to do is I'm going to create here a new service called order so if you want to create end points for the orders you can do it here because the cards actually it does not have uh States Associated to it per se it's going to be the orders so here we can make the orders store instead and here let's create the methods that we have here on the types which going to be the create order and the order items and these are the methods so very simple uh we have an insert so basically we inserting into the orders and here we get the last ID as well and return it and here the create other item we just insert and we forget what is the output so very simple so let's start by implementing our checkout here and before I forget I'm going to do that later because I think we're going to need more dependencies here uh we need to create we need to actually check if the products exist and I love that so we are probably going to need the um the product store here I'm just going to add it types do product store and here as well like so and I think this is going to be it for now so let's start by implementing the checkout now the first thing here is that we actually are going to receive some sort of card object from the front end so it's going to be let's think about it like a a slice or an array of items and their quantity so let me exemplify what I'm thinking so it's going to be types dots uh cards check out payloads let's create that here on types so it's going to be some sort of item slice so it's going to be items which is going to be a slize of card item and here the card item is going to be a product ID and a quantity like so so I think this is the signature and I have here quickly added the validation and the Json as well for the marshalling so this is going to be required so now that we have here the card we can actually pass this Json and then here below we need to validate it just like we did on the other end points and now here what we need to do is need to Loop over the card items and start doing some validations on the items because we might send some items that are out of stock that don't exist so we need to take care of that so for that let's go ahead here and create a service as service. go so on this service. go you're going to have a couple of business logic decisions so this is why I have created a new file for that so here now let's think about what we need to do so the first thing is that we can get the products so we could do something like h. store for the products and get products and here let's just get the list and get the errors now it would be handy that we could pass here a list of product IDs because if you think about it we don't care about getting the whole database here it would be nice if you could just get some uh size of IDs so that is what we're going to do and to do that we need to go here to the store and let's add a new method so I have just CED C products by ID IDs and here I'm passing in a slice of IDs so let's go to the store now and implement this method it's going to be very simple so this is the implementation so just copy here if you are following along because I'm going to move uh to the routes if you want to have the code I'll leave in the description the repository so you can follow along as well there so here we need a slice of IDs what you could do here is iterate over the card because on the card we have the items and each item has a product ID but we need to have the uh the products has a slice so let's make here um actually let's make here a function let's go to our service here and let's create our first function here which is going to receive the slice of card item so types do card item and it's going to be returning a slice of integers and maybe an error because we're going to do a simple validation here so the products so let's have here a name the product ID size with the length of the items now we need to Loop over the range items and the first thing we do is check if the item. quantity is actually less or equal to zero because if it is it means that the item does not uh I mean it doesn't make sense to request zero items so let me just return here an error invalid quantity for the products and here just say the product ID then I'm just going to here fill up the array on this product ID and at the end we just return the product IDs so I just call here the function and pass in the cards. items and handle the error as well where I'm just going to return a bad request I think it makes sense now that we have a list of products what we need to do is if you go here and think about it we have the products and we need to create an order for the user so the user made an order uh we have some status but then we need to associate some items to that order so for each order item there's going to be here a new row on the database so we need to take care of that uh it's going to be a couple of steps so let's go here into the service and create a function called create order and this function is going to do a lot of things so I'm going to also make this a method from the Handler because we're going to need to access to all of the repositories and this is going to return the order ID the total amount for the user to pay and then here the errors now from this function we need a couple of things it makes sense to have the products so let me just add here the products and add anything that I forget so we also need here the card items I think so cards. item and the user ID has well to make the table Association and I think that is all now here the algorithm is going to work something like this so we check if our products are actually in stock so if they are available then we calculate the total price and we also reduce the quantity of the products so we need to reduce the products stock in our database and then we need to create the orders and this includes to also create the orders items now this is going to be a complex function and in the beautiful world what you do is wrap everything on a SQL uh transaction or a database transaction and I'm actually not going to do that for Simplicity but this is a very good place to do so now just before I start working on here the algorithm I'm going to do a very quick optimization now we're going to do a lot of looping over the products and I want to create a map or an object where we can quickly access the product so it's going to be the product map just going to be an empty map of integers to protypes do products and this is how I'm going to populate it so it's going to be I'm going to range the products which I have actually codes like so and here what we do is we go to the product map we say the product. ID and here we say to the products like so now the first step let's here create a new small function calculate calculate or actually check if card is in stock something like this we pass in the card items and the products map so let me just create this function below so here is the signature of the function let me just here return uh 0 0 and nil just so we can actually it's still throwing the error that's because I need to fill this up really quick like so and here it is wrong its items okay so I have card item now the first thing we can do is actually check if the length of the card items so the cards. items is equal to zero so if we don't have anything in the card we can just return an error or we can even ignore it but I'm going to show an error then we need to Loop over the um the card items and let's just get the products from the map so we need to check if it exists as well so products and here is going to be the item. product ID if there is no products we just return an error and here we are going to do the stock check so if the product do quantity is actually less than what we are requesting we can return an error and here at the end we just return nil like so so here let's just handle the error and I'm going to just return zero Zer and nil now let's calculate the total price so it's going to be total price I'm going to also create a function called Cate total price and for the price we need the items again and the product map so we need the product there and this is how we calculate the price very simple we just Loop over again the items and we add their prices times their quantity so pretty simple and here we need to move to the next step let's go to reduce the quantity in our database so here I'm just going to Loop over the item item I'm going to get the product the gr product from the map so here we do item. product ID and then we say that the product do quantity is going to be minus the item do quantity so this is that part of the Cod that I mentioned that it could be problematic if multiple requests are coming in uh so a better solution would be to completely refactor this part here into a multiple table into a join table like the order item and now we need to call the store and update the stock so we need to do an update uh store for the product here and then to do an update product so I'm going to quickly Implement that here and pass in the new product like so so let's go to the types and Implement here the update product which going to receive a product and it could return uh let's just return an error let's go to the store for the products and Implement that function here and this is the implementation very simple just an update statement and here now we can actually update our product then the last steps we need to do is actually let me just here update the price because we already have it now we need the order ID so that is what I'm going to do order ID and error and here let's go to the order store which is the store like so create orders wa to types do new orders let's pass in the user ID then we also need the total this is going to be the total price the status the default is going to be pending for example and the address here going to be some uh some address I'm going to leave this hardcoded here now I'm going to keep this hardcoded here here because one of the Improvement exercises is to implement an address a user address table for example in Amazon you can have multiple addresses and you can here fetch the default one so for example this is one of the exercises so I'm keeping this like this and here we need to send the order ID and here very similar I'm creating uh all of the order items so of for all items I create a new order item in the database so let's go to the routes and actually call this function so it returns first the order ID then the total price an error and let's just call it create order we need to pass in the products the cards do items and the user ID we cly do not have it now I'm just going to Define it here which is could be like zero for now now this user ID is coming from the token from the GT token uh we're going to create a method in the Beats where we can actually we fetch this user ID from the authentication when we have authentication to this endpoint here because currently it is unauthenticated so so one step at a time so here let's just handle this error and finally return to the user the right Json with this data so it's going to be a status okay the map and here I'm going to return a map custom map with the total price which is going to be total price and then the order ID as well like so all right so the endpoint is pretty much done and before we can actually test let's first add authentication to this end point here so the way that I like to do this and it's a very nice way is to use a higher order function or a decorator pattern or whatever you want to call it uh the idea is that we're going to wrap this function here with for example with uh JT o and here we passing the function so before calling the Handler it's going to be calling this function here which going to do the validations so let's go to here to the authentication into the J te and let cre that function I'm going to be calling this with JT o and it's going to be taking the Handler Funk as we saw so it's going to be hb. Handler Funk and it's also going to receive the store so we need to access to the user store so we can actually fetch the user by his uh token ID so here let's do HTTP do Handler Funk we need to also return one so it's going to be a function that returns a function so let's let's do fun here use this option for the auto complet so the first thing we need to do is actually check if the user has a token in the headers or the cookies or whatever so first we need to get the token from the user requests then we validate the GT to see if it's all right if it is we need to patch the user ID from the database because we get the user ID from the token right and the last step is that we set the context user _ ID value for example or some something like uh user ID so we're going to change the request context to the user ID so we can have the user ID here on the uh the Handler so here I have just imported with the correct function and I'm going to pass in here the user store so it actually needs a new store here which is going to be the user store types dot user store we have already created it before user store like so so this method here is wrapped with the fun function so let's go ahead and implement it so I have here just created a very simple helper function so we can just get the token with a function call so get token from the requests and now you need to validate the token so here I'm going to create a function as well called validates token which is going to receive the token string and it's going to return a pointer to JT do token and an error now the only thing we need to do here is called the pars it handles all of that for us the first thing is that we need to pass in the token string and here we need to pass in a function and here we need to validate the token do method so we need to check uh like so so let's get the method and if it's not okay let's return nil and an error card like so and here we need to actually type this to the correct name here so we need to do a casting to the signing method which is this one and here let's just return at the end the bite slice with the config do Ms do J SEC that already created and nil now if we have an error here let's handle it I'm I'm going to first log for an error because it might be handy I'm going to return and we also need to return to the user that he has permission denied so what I like to do here is create a function called permission denied because it's going to come in handy because we're going to call this function multiple times here and this is what we're going to do so we're going to just call u. error and do that so you can easily do that and finally on this part of the algorithm we can check if the token not valid and if it's not valid we need to return permission uh denied now here let's fetch the user from the database but first we need to get the token ID from the the user ID from the token so this is how you can do it and then here let's get the user ID we need to convert it to a string and then we can actually call the uh user store or did I call this store. get user by ID and here you can pass in the user ID like so and if the user doesn't exist we hand all the error again just the same way and finally let's add the user to the context so let's do r. context let's get the context from the requests then we are going to do a context with value so let's initiate a new context with this value here we're going to pass in a key so it's going to be the user ID and the value is going to be the u. ID and here here we just modify the context of the request and we can actually call at the end the Handler Funk very importantly and we pass this parameters here now the compiler is going to warness that we should not do this as a string what you could do is create a new type so I'm going to call this user key and it's going to come in handy because we need to reference this key as well so this is going to be a constant so it doesn't change so I'm going to call this uh like so it needs to be context key and it's going to be equal to this one and the context key is just a string here like so so we have our own type for the context and now we can access that type here on the routes and just before we can actually do that uh let me create here a helper function for us to get the user from the context so get user ID from context and we're going to receive the context context. context and return the ID so the first thing we do is we check if the value for the user key exists if it doesn't we can just return for example minus one and here it's okay not an error and if it exists we just return the user ID now we needs to cast this value as well to an integer because we know that that is an integer and then here on the routes we can just call this user ID with the o. getet user ID from Context and here we pass in the request. context so we did let me just save here the files and here are we actually using this user ID we are we are sending to the function so everything is nice and tidy let's go ahead and actually test this so I'm going going to just restart the server uh mun the server doesn't start because we actually need to go to the api. go and I always forget this so we need to create our service and updates the services that we have change so the product is depending on the product store uh but I think we have broken the implementation of the interface so here I'm just going to delete this and hopefully it's out needed and apparently there is a typo here so let's just add here an S so it's compliance so let's just initialize the cards um actually the card store now we need to do is the card Handler so it's cards do new Handler and here we need to pass in a couple of stores so the first one is going to be the order store this one we need to initialize so order store it's going to be New Order dots new store pass in the database and we pass into here then we need the product store and the user store finally and here you're getting an error here that's because we are calling the the struct and not the Constructor so let's go card handler. register routes and pass in the sub router so this is the last time we actually need to do this and now if we try again the make run the server has boots and my sock with TS so this is the checkout here I have a product one and product two uh actually you don't have a product two yet so let's actually create it so here I have just created a new book product even if we try to hit our end points we have a permission denied because the endpoint requires artification and here I have on the headers a token and if I hit sends and here we got the order ID and the total price and if we check the database we have here a new order created and the order items also created and in the products we have the quantity reduced to the amount that we have purchased and when say that want to buy 200 of the product one and if you hit sends we get the error product t-shirt is not available so this quantity is not possible to buy and if you try with a product that does not exist we also get I mean we getting this error but if you try with this we get the product 22 is not available in Star and so this pretty much sums up everything and we have completed the project that we set to build to now again you can get the full codes on the description below and if you liked this video consider subscribing and leaving your like and consider also joining the free Discord Community where there are plenty of experienced Engineers that can help you and you can leave your questions there so if you have any question regarding this project you can leave on the Discord below as well so thank you for watching and see on the next one
Info
Channel: Tiago
Views: 50,932
Rating: undefined out of 5
Keywords: golang, rest api, jwt api, backend tutorial
Id: 7VLmLOiQ3ck
Channel Id: undefined
Length: 97min 57sec (5877 seconds)
Published: Thu Mar 21 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.