Industry Level REST API using .NET 6 – Tutorial for Beginners

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this.net 6 course amikai will teach you how to build a rest api amikai is a software engineer at microsoft and he is great at breaking down complex technical topics in a way that's easy for beginners to understand hi everyone today we're going to be building a crud based rest api completely from scratch i'm really excited to be collaborating with free code camp on this video so i really hope you're going to like it and i can't wait to hear what you think okay so what are we building today well today we're going to be building the back end for the fictional application uber breakfast kind of what is this breakfast application well it's basically a system in which you can create breakfast and as you can see every breakfast has a title a description a star and end time some savory items over here and also some sweet items and because what we're building is a crud based rest api then as you would expect the actions that we can do are creating a new breakfast reading breakfasts updating a breakfast and also let's imagine that somewhere in our system we have the option to delete a breakfast as well okay so if this sounds intriguing make sure to watch until the end because by the end of this video you should have a good understanding of all the topics that we're going to be covering okay moving on to the architecture of the project so as you can see by the end of this video we're going to have two projects now you don't need to remember what this looks like but what i do want you to notice is that we have two projects one containing the definition of our api so this models our api definition and another one with the actual logic of our application okay moving on to the technologies that we're going to use so we're going to be using dot net 6 and we're going to use the dot net cli and visual studio code for everything else as part of this video then you will get familiar with some new.net cli commands and some pretty cool visual studio code extensions which hopefully you'll be able to take and add to your toolkit okay moving on to the architecture choices so as we said we're building a crud rest api this means a system in which you can create read update and delete resources so not every back-end system needs to follow clean architecture and domain-driven design principles especially when the application is small or medium it can add a few layers of complexity which aren't always worth the effort so today we're going to be building something that is suitable for small to medium sized applications and hopefully you'll be able to take what we're going to be building today and use it in your applications even if yours is completely different okay now even though we're creating a quote simple application we want to make sure that our application is resilient and it will scale well as our application has more and more features and more error flows that's why we're going to incorporate some interesting concepts from functional programming and also some principles from domain-driven design which will make our application more resilient and better organized okay lastly who am i so my name is amica i'm a software engineer at microsoft i love creating all these visual engineering infographics on the various social media platforms and i actually just launched a new youtube channel a few weeks ago in which we write a lot of codes we cover design patterns and architectures that are a bit more advanced than what we're going to be seeing today so if you like what you see today then feel free to follow me on the various social media platforms you might enjoy my content hi everyone i'm mikhail here i do have a quick disclaimer so i am a microsoft employee and these are microsoft technologies but i'm not talking on behalf of microsoft these are my personal opinions okay so without further ado let's start writing some code so as we said we're going to have two projects one that models our api and another one which will contain the actual logic of our application okay now if you're asking yourself why would i want to split my application into two separate projects while having your api definition which is a bunch of c-sharp classes in a separate project allows you to define it as a class library and then publish it as a nougat package then if your client is written in dot net then they can simply consume this nougat package instead of remodeling the api themselves okay now this also works well with versioning so as your api evolves and you have new versions then when you want to publish the v2 of your api then all you need to do is publish a new nuget package and all the client needs to do is simply consume the new package okay so let's get started the first thing we want to do is create a new solution so let's go ahead and create our blooper breakfast solution so dot new sln and let's give it the name uber breakfast hey as you would expect this creates the boober breakfast folder which inside has the uber breakfast solution now let's go ahead and create our projects so dot new class lib and let's give it a name boober breakfast dot contracts right so this is a project that models our api great now let's create our actual web api so dotnet new web api and let's give it the name boober breakfast okay let's try building the project so dot net build and we get a warning that says unable to find a project to restore now this may seem a bit weird because we just created two projects but when working with the.net cli then it's not enough to just create the projects because if we look at the solution file then we can see it's still empty and there are actually no projects defined in it so let's go ahead and add both of our projects to the solution file so dot net sln add and we want to add the boober breakfast dot contracts and the booby breakfast project click okay this adds them okay now you can actually do this in a shorter way so you can do dotnet sl and and you can specify over here all the projects recursively like so so you can go ahead and choose whatever is more convenient for you okay now that we have all of that defined let's go ahead and open this in visual studio code okay so we open the project in visual studio code and as we expect we have the solution that we created and the two projects if we look at the solution then after we added the two projects we can see that they're defined over here if we look at the two projects then over here this is just a class library we're using.net 6 and we have nullable reference types enabled which basically means if we haven't defined that an object can be null then it can't be null okay let me just close this stuff over here okay and over here we have the web api so because we created the project from the.net cli using the web api template then we get here a few things out of the box okay so looking at the cs proj okay so as we said we're using the dotnet sdk dot net six and also here we have nullable reference types enabled okay now the first thing i want to do is actually to create your new folder it's called docs and over here let's create a file api.markdown what i'm going to do is paste here the definition of the api that we're going to be building and i'm going to open it using the markdown preview enhanced extension if you aren't familiar with visual studio code then over here you have a tab in which you can search and install extensions for visual studio code so this extension is called markdown preview enhanced and all you need to do is select it click install and then over here when you right click then you'll have the option to open it using this extension so let me put it over here so it catches the entire screen and i want to open it side by side with our client application okay starting with the c of chrono then the way you create a breakfast is you basically call the breakfast endpoint using the http verb post and as you would expect what you have over here is basically a breakfast right so we have the name which corresponds to this the description which corresponds to this the start and end date time which corresponds to this over here then we have a list of savior items which is this and suite items which is this the only thing that's missing from the schema is perhaps a list of images but let's imagine that we're just building the mpp and so in a future iteration we'll add this feature as well okay if we created a dinner successfully then we get the http status code 201 created and the location header which gives us details on how to fetch the breakfast that we just created if we'd like and the response content contains all the details that we sent including a server generated id and another server generated property a last modified date time moving on to the r of card so we call this endpoint over here and specify the id of a breakfast that we want to read and to indicate that we want to retrieve data then we're going to use http get if the breakfast exists then we'll get http status code 200 and then the response will get the breakfast that we requested okay moving on to update breakfast then to update breakfast you would specify the idea of the breakfast that you want to update and use the http verb put and in the request content you would specify the new details if the dinner was updated successfully then we get 204 no content but if the specified id doesn't exist then following the rfc specification for put will return 201 created and the response will be similar to the response of creating a resource so even though this is called update this is actually upsert so we'll update the resource if it already exists or it will create a new resource if it doesn't exist last but not least we have the delete breakfast basically you specify the id of the breakfast that you want to delete with the http verb delete and if it was deleted successfully then we'll get 204 no content so by the end of this video we'll create an api that behaves in the good flow like what we saw over here but don't worry we're also going to have error handling so if something goes wrong we'll return the appropriate response to the client okay so i actually want to open the api definition again on the right so we have it visible while we're modeling our api okay so let's start with create breakfast so over here in the contract project let's get rid of this auto-generated class let's create a new folder it's called breakfast and over here let's create a new file called create breakfast request and as we defined in our api so this will be public record create breakfast request so this will be string name spring description yes daytime start daytime yes and daytime then we have here a list of savory and a list of sweet items as well great moving on to the response so actually the response both of the create breakfast and the get breakfast are identical so let's just create a single file to model both of these responses let's call it a breakfast response so breakfast response and actually let's just copy paste this entire thing let's add the extra fields so this over here will be response we have here a guide and other than these two daytimes we also have here a less modified daytime right so as we said we already have this defined over here on the left now let's create the option request so absurd breakfast request and over here as well just copy paste this entire thing over here let's change it from create to upsert and that's it right right now that we have the api modeled in c-sharp classes let's go ahead and start implementing the actual logic okay now if you aren't familiar with web apis and i want you to have something to imagine as we were writing some of the code so i want you to imagine that when the user clicks the create new breakfast request with a new breakfast that he just defined then the following details are sent to the server to the create dinner endpoint and you can imagine that the request arrives over here at the breakfast controller and we'll be able to extract the details that were sent and manipulate them the way we want so in our case we want to store them in the database okay then in the response then whatever the breakfast controller returns then it'll simply be returned to the client and then the client can take the information that was returned and display it back to the user so back in our application because we created the web api using the command line interface template we get here some files which we don't care about so let's go ahead and get rid of everything that we aren't going to need so we can get rid of this weather forecast controller this weather forecast file and the program cs let's get rid of everything we aren't going to use in this video so we can get rid of this of this of all this and of this as well okay now if you're used to seeing both a program cs and a startup cs then what we have over here is the new template starting from dot net six where over here you have the builder variable which you can use for dependency injection and configuration and over here you have your app variable in which you can manage the request pipeline okay now a nice trick that i saw is creating here a scope and putting this inside like this and same over here just makes it seem a bit more organized okay last thing over here we can now get rid of this package reference since we aren't using it anymore and that's it okay so let's start defining our endpoints so over here let's create our breakfasts controller so this will be public class breakfasts controller which will inherit from the controller base this is coming from this namespace over here okay we want to add the api controller attribute this is an attribute that we get out of the box with asp.net and it does some heavy lifting for us okay so let's start defining our endpoints so let's start with a create breakfast request so http post and here let's specify the route so yes breakfast this will be public i action results create breakfast let's see what it's suggesting yes you know we can try to add the contract namespace but it's not recognized that's because we don't have any reference to the contracts project so let's go ahead and add this reference so dotnet add and then we need to tell which project so in our case we want the blue breakfast project to reference the contracts project okay this added the reference to the contracts project so now we should be able to add the namespace great and for now let's just return the request next we have our get endpoint so let's just copy this entire thing and paste it over here let's change this to http get over here let's add the yes the id and because we want this to be a guide then we can add here guide and the framework will enforce that this must be guided let's change this to get breakfast and over here we have our guide and for now let's simply return here the guide next we have our update breakfast request so again let's copy this method over here and paste it let's change http post to put this will be similar to what we have over here so let's simply copy and paste it and let's change this to observe breakfast and over here this won't be the create breakfast request but rather it will be the absurd breakfast request and of course we also want to extract the id from the route so id okay and lastly we have our delete endpoint so let's copy the get endpoint and paste it down here let's change http get to http delete let's change the name from get to delete and that's it for now let's close the api definition and make sure that this works as we expect so dotnet build create it build successfully let's go ahead and run the project so this will be.net run and here we need to specify which project so in our case it's the uber breakfast project let's click enter this will build and run our application right now to make requests i want to introduce another visual studio code extension so let's go back to the extensions tab and look over here for rest client okay this is the one you'll see it has two million downloads and a lot of love from the users simply click install and you'll be able to run http requests like we're going to demonstrate now so the way this looks is let's create here a new folder let's copy requests inside here let's create another poll that's called breakfast and in here let's create a great breakfast request okay so it's simply a file that ends with dot http okay and the way this extension works so you basically just write the request that you want to send and you can see that just writing the http verb already pops out this send request option then you simply click it and it sends the request for you so let's open our api definition again on the right and create our first request so we want the host slash breakfasts underneath we can define whatever headers that we want so in our case we want the content type application json and below our request body which is this thing over here okay let's try sending the request and making sure that the endpoint simply returns the request as we defined so we sent it and we got it as we expect great let's go ahead and create our other requests as well so over here we have also the get breakfast request the upsert breakfast request and lastly the delete breakfast request okay let's copy the create request to the upload request because they're very similar but instead of post this will be put and over here will have the id of the breakfast that we want to update so let's just take some id from over here and paste it in our request okay now something cool that you can do with this extension is create variables so you can say for example that the id is let's copy the id over here and paste it here and then we can simply replace this entire thing with id and you can see that there's even intellisense so you can autocomplete and over here you even have navigate to definition pretty cool okay so this is our update endpoint it corresponds to the definition of our api over here let's move on to the get breakfast so let's simply paste this over here let's paste the id as well actually and instead of put this will be get and the delete will be similar to this only we'll have here instead of get delete let's make sure our definition is correct so create we already tested let's make sure that delete works as well so right over here we just simply return the id forget also we return the ib and for upsert we should be returning the request great so now that we have everything wired together and we have all our requests defined let's go back to the controller and think what we want to do over here so let me just close all these windows on the right also let's stop the application you can do it by clicking ctrl c okay back in our controller one last thing that i want to do so because all our endpoints start with the word breakfast we can simply go over here and write real breakfasts and then we can remove this this this and this as well and because it's very common to have the route simply the name of the class without the word controller then the framework also allows us to do something like this where you simply write controller and then all the endpoints will be prefixed by the name of the class without the word controller okay so now let's think what we want to do so let's start with the create breakfast endpoint so as we said we have here the request now we don't want to store the request as it is in the database for a couple of reasons one it doesn't have all the properties that we want so for example the id property doesn't exist and the last modified property doesn't exist but also because we want the flexibility to be able to change things inside our system without having to change the contract that we have with our clients okay so what we want to do over here is take the request that we received converted to the internal service model and inside our application will only speak in the language of these models and not the language of the contracts then tomorrow morning we can create a new api version and all we need to do is convert the new api definition to our internal service language and then we can support multiple versions and our system is more flexible so for that let's create our internal breakfast representation so let's go ahead and create here a new folder it's called and in here let's create our breakfast object okay so this will be public class breakfast and over here let's have yes actually this seems right but because we want to make sure that this is always built with valid values and we don't want to give anyone holding the object the option to simply set new values to our properties then let's get rid of all the sets over here and let's create a constructor great let's just wrap the line here so it's visible okay and now not only that no one can change the id for example after it was set but also over here we can enforce whatever logic that we want so if we have some logic for example that the name of the description can be shorter than three characters then we can enforce it over here and then once it's created we know this value is always valid in whatever valid means inside our system okay so back in the controller let's map the request to this breakfast object so let's say over here breakfast equals and here let's say yes this thing's about right let's make sure that we have here everything that we need right so we take all the values and we generate a new id and for the last modified we simply pass utc now okay the next thing we want to do is is somehow yes saved to the database then we want to take the breakfast that we created and map it to our breakfast response and from here we want to return our response okay now this over here will return 200 okay right because we're using the base method okay but what we actually want to return is 201 created so let's go ahead and use the created apps and as you can see there are a few options over here we're going to use this one over here but there are other options okay now this method receives three things first of all it receives the action in which the client can retrieve the resource so in our case it's the get breakfast endpoint then because the get breakfast endpoint has a parameter called id then we need to pass over here an object that has the id property so something like this yes and lastly the response content let's just add the argument names though it's a bit more readable okay so what we have at the moment is very common to see in controllers the first step is mapping the data that we get in the request to the language that our application speaks the last step is taking the data from our system and converting it back to the api definition and returning the appropriate response and over here we have the actual logic of our application which we're going to implement now okay so let me just remove these redundant parentheses over here and back in our boober brackets folder let's create a new folder scott services and over here let's create another folder called breakfasts and over here let's create the i breakfast service and what we'll have over here is the public interface yes i breakfast service let's leave only the create and again we only want to work with our internal models so over here we want our breakfast model and the response let's have this void for now and let's change the name of this to breakfast great now back in our controller let's use this interface so private read-only yes let's include the namespace and inject this to the constructor okay if you aren't familiar with dependency injection then we'll look into it in a few minutes now down here let's use it to actually create our breakfast so breakfast service create breakfast and let's pass it to breakfast okay now let's go ahead and implement this breakfast service let's create here a new file let's call it breakfast service and over here let's have our public class practice service the ibreakfast service interface let's go ahead and implement the interface okay now as part of this video all we'll do is just store it in memory but over here what you would actually do is use some repository or entity framework to store it in the database okay so what we'll have over here is private read-only dictionary where we'll map an id to a breakfast let's just initialize it then over here all we need to do is breakfast dot add and the idea of the breakfast is the key and the value is the actual breakfast great let's just add a new line over here and back in our controller for us to be able to test this end-to-end and see that this actually works so in the get breakfast let's try to retrieve the breakfast with a given id so yes something like this let's generate this method right so this generated the get breakfast method which we'll implement over here so implement the interface and all we need to do is simply return the breakfast that corresponds to the id that we just received okay now that we have this method implemented back in our controller let's map our breakfast to the breakfast response so yes and let's return the response from here okay let's run the project and make the create breakfast request and see what happens okay now some of you might have expected this failure so the failure we're getting is that the framework doesn't know what to do when someone requests the i breakfast service interface right so back in the controller we're trying to use the i breakfast service interface okay now what actually happens behind the scenes is that every time we send a request to the service the framework creates a new breakfast controller object when it creates the new breakfast controller object it also tries to create a new i breakfast service object and the framework doesn't know how to instantiate the interface that the breakfast controller is requesting so to fix that let's go to the program cs and over here define how to create the eye breakfast service interface so builder dot services dot add singleton yes breakfast service okay what we're telling the framework is that every time it instantiates an object that object requests the eye breakfast service in the constructor then use the breakfast service object as the implementation of this interface and over here we have a few options for the lifetime of this breakfast service object so specifying here singleton tells the framework the first time that someone requests the ibreakfast service interface then create a new breakfast service object but from now on throughout the lifetime of the application every time someone requests this interface always use the same breakfast service object that you just created another option over here is to say scoped scope says within the lifetime of a single request the first time someone requests this interface then create a new object but from now until we finish processing this request every time we create an object and it requests this interface then return the same breakfast service object that you created before okay there's also transient which as you would expect basically says every time someone requests this interface create a new breakfast service object okay now looking at our breakfast service object so because we're persisting our breakfast inside this dictionary over here we don't want this dictionary to be recreated every request so let's change this to static okay and over here let's change this back to scoped great let's try running our application again and see if it works now so we make the create breakfast request as we can see we get here the http status code 201 created we get the location header and we get the breakfast response which contains the server generated id and less modified date time right let's take the id of the breakfast that was just generated and let's try retrieving it over here so let's replace this id with the idea of the breakfast and make the get request and we can see that this works as we expect so we get http status scoped 200 okay and over here again we have our breakfast response okay now one thing about the create breakfast so let's create another breakfast we said that the location header contains a route which we can use to retrieve the resource that we just created so let's try and using this to retrieve the breakfast so i'm copying it and pasting it back over here now if you want to send multiple requests from the same file then you can do it by typing three hashtags and you can see that now we can send another request from the same file let's use http get and make the request and we can see that it works back in our controller let's implement the methods that are still missing let's also stop our service so let's implement the update endpoint so over here let's create breakfast given the details that we have great so it's similar to the crate only that we're using the id that we got from the route then we want to use the breakfast service to observe the breakfast so yes let's generate this method and we want to return here no content now if you remember then in this endpoint we want to return no content only if the id is an id of a breakfast that exists in our database otherwise if this is a brand new id and the details here are valid then we want to create a new dinner and we want to return here a similar response as we're doing in the create endpoint so let's add here a to do return 201 if a new breakfast was created right last but not least let's delete the breakfast so breakfast service dot will eat breakfast and let's pass the id let's generate this method and return here no content and back in our services let's implement the two missing methods so implement interface delete will be simply remove the entry with the given id and for upsert they'll simply be breakfasts and add it to the dictionary let's make sure that this works as we expect so let's rerun the service so create created a new breakfast let's copy the id and paste it over here in the get breakfast let's try retrieving it and we retrieved it successfully let's copy the id to the after breakfast and over here let's change this let's make it i don't know vegan sunshine too let's make the request you can see we got here no content which means it was updated successfully let's try retrieving the breakfast again and we can see we got it with the updated value now let's copy the id and delete the breakfast and we get no content again which means that it deleted it successfully so now if we try to get the breakfast again what do you think will happen and an exception was thrown and we can see over here the exception that occurred so this is basically the exception to string here's the exception message and here's our stack trace can if we go back to the breakfast service implementation okay we can see that this happens because we're not accessing the dictionary safely but before we fix this we actually never want to return to the client the exceptions that were thrown it may contain sensitive information and also we don't want the client to see the stack trace and what happened inside our system and how it's implemented so let's add global error handling which basically means how we handle exceptions that are thrown in our system so back in the program cs we're going to add a middleware as following so use exception handler and over here let's specify some route so when i said that you can imagine the request simply arrives to the controller so basically what we have is a pipeline that our request goes through and if you aren't familiar with middleware then you can imagine it like a sandwich of code you can write code that runs before and after the next piece of middleware where somewhere down the road one of the middlewares actually invokes the controller what we did over here is we're using a built-in capability of the framework to add code that basically adds a try catch surrounding the following middlewares and then if an exception is thrown then it catches that exception it changes the request route to the route that we defined over here and it re-executes the request and what this means is that over here we can create a new controller and we can call it errors controller so public class here is controller yes let's just change this to controller base let's say that the route here is error and let's change this to problem and let's explain what we have over here so like we said it re-executes the request to the error route then over here we can have whatever error handling logic that we want currently all we're doing is using the problem method from the controller base which will return the http status code 500 internal server error so i want us to run the program and see what this looks like now so let's rerun it and make the get request again so we don't have anything in the database so this should throw an exception and we can see that we no longer get the exception as it was thrown but we get this next response with 500 internal server error and no sensitive information will return to our client okay so this brings us to the next part of our video which is error handling so there are many approaches for handling the error flow in our system what we're going to do today is use an approach from functional programming where basically what we'll do we'll say when you request a breakfast we'll either return the breakfast like you requested but if for some reason we can't do it for example the breakfast doesn't exist then we'll return an area that represents what happened okay so let's see what this looks like so first of all i want to add a new package to our application so dotnet add to the boober breakfast project the package okay now this package is called error or it's my personal favorite for handling this approach you know if you know me and you know this package then you know i'm not very objective and that's because well i wrote this package but it really gives you some functionality that doesn't exist in other packages so let's get familiar with it i actually made a video about the two main approaches for flow control so if you're curious to see exceptions for flow control against this kind of approach then make sure to check it out we look at multiple open source libraries and you can see what's right for your application okay so inside the blue breakfast folder let's create a new folder it's called service errors and over here let's create a new file let's call it errors.breakfast dot cs and what we'll have over here is a public static class errors and inside here a public style class breakfast and in here we'll define all the areas that we expect to have in our system that are related to the breakfast resource now over here an era that we're expecting to have is a breakfast that doesn't exist so let's go ahead and represent that error so public static error not found now if we look at this error object then we can see it comes from that error or package and what this is so it's a simple struct which has inside three things it has a code which is unique to the error that occurred it has a description which is a human readable description of what happened and it has an error type what is this error type it's a simple enum which has inside failure which is the default value and some other errors which are common in any system right so in our case we have something that wasn't found so let's go ahead and use this one so the way this works you can do error dot and over here you can use each one of these methods to create the corresponding error object plus you can also use some custom integer value if you prefer so in our case we want not sound and what you give it over here is the unique code so for example breakfast dot not found right so this is the code for the description let's say yeah something like this then back in our breakfast service this returns either an error or the breakfast okay now this arrow or object comes from the error or namespace yeah let's update our implementation as well and see how we'll use it so error or breakfast let's include the namespace and what we can simply do now try to retrieve the breakfast so try get value that corresponds to the id inside this variable if this succeeded then simply return the breakfast otherwise we want to return an error in our case yes exactly this let's just include the namespace and if you're asking yourself how these two become this arrow or object then inside the arrow or object you can see we have two implicit converters one that gets the value in our case the breakfast and converts it to an error or object and the other one gets an error and converts it to the error or object okay so that's how it works behind the scenes then back in our controller then when we're getting the breakfast this no longer returns breakfast but it returns an error or the breakfast right this is no longer a breakfast but it's the get breakfast result okay now this may contain the breakfast but it also may contain the error so over here we want to branch out depending on what we have so we can say if the get breakfast result is an error and yes similar to this let's accept it and understand why this doesn't work so back in the error or object this actually contains either the value or a list of errors so you're not limited to store only one error so back over here we can access it using the first error property then if the first error is not found then we want to return not found wise we know we have the breakfast in our hand so we can say breakfast equals yes the get breakfast result dot value so this will simply return the breakfast that we returned we can map it to the response and return it same as we did before so let's run this and make sure that it works okay so we're making a get request and since we don't have any object then we get not found as we want but if we first create it and we take the id and use it to retrieve then we see we get the response as we expect okay so back in the breakfast controller you may be looking at the method that we implemented and you're thinking to yourself that this is a much less readable than what we had before after all we can simply throw an exception from the breakfast service and catch it over here in the airs controller and then our controller can stay like it was before so you are right let's see how we can make this a bit more concise first of all let's extract this mapping logic to a method so extract method and let's give it the name map breakfast response and i want us to get familiar with the following capability of the error or object so for now let me comment out all these lines and see how we can use the match method so what we're able to do is say return get breakfast result dot match so as you can see the match method receives two functions one that will run if what we have is the value in our case the breakfast and another one that will run if what we have is an error so let's see how we can use it let's define the first function we have a breakfast if we got the breakfast then we simply want to map it using the method that we just defined so map breakfast response and that's it the breakfast and if we have an error so this is the list of errors what we can do is handle it over here for now let's just return here a problem and see how this behaves so let's run the application again and we try to make the get breakfast request then we can see that it correctly invoked the problem method okay so i want to go back to the breakfast controller so currently we're checking if the first error is specifically one of the service errors but actually what we can do is we can take the type of the error and according to the type return the appropriate response to the client so if you remember back over here the type of error error is not found so we don't need to do something that's very specific for this error but rather we can do something that's a bit more general so for that what we'll do i'll delete all the logic over here and i want us to implement our own problem method so for that i'm going to go over here and create here a new controller which will be the base controller of the other controllers that's called api controller so over here we'll have a public class api controller which yes we'll inherit from the controller base and as we said this will be the base controller for the other controllers so let's go ahead and start inheriting from the controller base over here instead let's inherit from the api controller and because these are attributes which we can expect on any one of our controllers let's move them to our base controller and over here like we said we'll have our own problem implementation which will receive a list of errors and return the appropriate response so the way this will look is protected by action problem which will receive the list of errors and for now let's have your something simple so let's say our first error equals the first error yes and over here we can say that the status quo depends on the type of the first error so we can say type which and over here yes something like this so we can say if it's not found then status codes dot not found if it's validation then we want 400 otherwise let's return here internal server error and we also have here conflict so let's go ahead and use that as well so conflict will be conflict exactly and over here let's return a problem where the status code is the status code and the title is the description of the first error right as so let me just close the terminal okay so all we have at the moment is our own base controller implementation which the other controllers will inherit from and what we'll do from now on is in the controllers when we end up with a list of errors then we'll call our own problem method which currently all we're doing is taking the first error using the type to compute the correct status code and then we're using the problem method from the controller base to return the status code and the description of the first error okay so now that we have this defined back in the get breakfast method then what we can do over here is pass the list of errors to the problem method so now this problem method is the one that we implemented in our base controller and that's it let's make sure that this works so start the program run it again and now when we try to do it for your breakfast that doesn't exist then we can see we get here the description of the first error and we successfully converted the error type to the correct http status code great so back in the controller again all we're doing is calling the breakfast service receiving an error or the breakfast if it's the breakfast then we're mapping it to the correct response and returning it to the client otherwise we're using here our custom problem method which converts the list of errors to the appropriate response great let's go ahead and do the same for the rest of the methods in the breakfast service all right so this will be error or created this created also comes from the arrow or library for the observed let's say here or updated and for the delete let's say air or deleted then back in the implementation let's update this so this is error or rated this simply returns result.created again this comes from the arrow or library in the delete then it's air or deleted and let's copy this paste it in the delete method and change this to delete it and lastly we have the observed so we have this here or updated and we can return here result dot updated okay now one of the reasons that i really like this approach is because as the application grows and we have more and more errors then all the errors are neatly organized over here and over here in each method it's very clear to see what happens in the good flow and what happens in the bad flow here let's update the controller as well so over here we get the create breakfast result which is error or created first of all we can get rid of this entire thing instead use the map method that we created right so this will be breakfast and then for now we can say if great breakfast result is error then we want to return problem and pass the errors to the base controller otherwise we want to return the same response and then let's just move this to the bottom so it's more organized then let's start with the delete so this returns the delete result which is error or deleted then over here we can say yes actually right so if it deleted then we want to return no content otherwise if there's an error then we want to return the appropriate response and lastly the upsert so over here if you remember we want to return 201 if a new breakfast was created okay so it would be nice if the upper breakfast method could somehow tell us if it created a new breakfast or not so to do this what we can do actually is go over here and instead of returning error or updated which comes from the arrow or package we can create our own response model let's say something like abserted breakfast result and this could be whatever we want so for example public record struct upstarted breakfast and here we can have whatever extra details that we want so for example is newly created which will indicate if we created a new breakfast or not and then over here we can say bar is newly created equals yes right so if the breakfast dictionary doesn't contain the given id then we're creating a new one then what we still have left to do is update the method definition so this will be absurd breakfast both here and here then we can return here a new upstarted breakfast and pass it the is newly created value then back in the controller this returns the absurd result and over here we want to return 201 if a new breakfast was created so because we have the same thing over here let's extract this twee method let's call it something like created at get breakfast let's fix the indentation and move it to the bottom okay back in the offset method what we can do now is basically absolute result dot match yes almost so if it upset it then what we want to do is check if it was newly created if yes we want to return created at let's fix the typo get breakfast otherwise we want to turn no content lastly let's just align the variable names so this is observed breakfast result and over here it's delete breakfast result okay so that seems about right we can actually make the create breakfast a bit more concise so we can replace this entire thing with create breakfast results and we can match on the result and say if it was created then we can return the created at get breakfast and pass it the breakfast otherwise we can use the problem method and pass it the errors okay so just to recap all the errors that we expect in our system sit over here and we have them well defined with a unique error code and a descriptive description then in our i breakfast service then we define for each one of the methods that they can either return the expected result or an error now because we aren't really expecting any error in some of these methods then we could have simply replaced it with void but i just wanted to demonstrate another capability that might be relevant in your application then lastly in our controller then we get the response and we return the appropriate response to the client based on whatever result we received great let's run the project and make sure that everything still works as we expect so we create a new dinner we get here the response that we expect let's take the id and use it to retrieve it great this also works as we expect let's try updating it so we get no content which is good it means a new resource wasn't created let's try deleting the breakfast and we get no content again which means that it was deleted successfully so now when we try to get it again then we get 404 not found and if we do upsert then because it doesn't exist we expect it to create a new dinner so let's try and we see we didn't get no content but rather we got 201 created with a new resource and the given id that we specified great let's make sure that this was stored in the database so let's try to retrieve it and we can see that it was stored in our in-memory database as we expect now let's move on to the final part of this video which is enforcing our business rules on our internal service models so if you remember over here when we create the breakfast then there are some rules that we might want to enforce on the data that we're receiving so at the moment the client can call us with whatever information they want and we'll simply store it and also the controller can put whatever value they want over here in the last modified date time and will simply store it as is it would be nice if we can simply take the data and say okay here's breakfast if everything was valid and it adheres to the rules that the business defines otherwise here's a list of well-defined errors okay so let's see how we can do that so over here let's create a static factory method and this will look as following so public static this will turn an error or a breakfast and let's call this create let's just include the arrow or namespace okay the way this will work is that we'll change the constructor to private but we're the only ones that can call it over here we'll receive the various details so yes this seems right let's just inline this and now instead of enforcing our logic over here we can go ahead and enforce it over here so the way this could look so let's say for example the name needs to be between 3 and 50 characters and the description needs to be between 50 and 150 characters okay now i'm giving this as an example but something you might actually want to do is make sure that the start and end time are actually values that make sense for a breakfast and the start date time is in the future okay so let's look at how something like this can be implemented so for example public constant min name length now let's have a three and let's have the max name length we said 50 and let's have the same thing for the description so let's change name to description let's change three to 50 and the maximum to 150. then over here on the create method then we can say okay if the length of the name is less than the minimum length or greater than the maximum length then over here we can return an error that represents that the name isn't valid so for example let me just close everything so it's more organized we can go to our service errors to the breakfast and create here some validation error let's call it invalid name dot invalid name and over here we can say that the breakfast name must be at least yes it seems right let's just update this too models not breakfast dot min name length both over here and over here and let's wrap the line great then over here we can simply return errors dot breakfast dot invalid name and we can have similar logic for that description so let's change name to description and here as well and let's add the appropriate error over here and here let's change everything to description as well and this should be also description and now this should work great and we can actually make this a bit better if you remember we said that internally we have here a list of errors so we don't actually have to return only one of these errors we can actually return all of them so what we can do we can create here a list of errors and over here simply edit like so and same thing over here so errors dot add and add the appropriate error and in the end we can yes if there are any errors then let's simply return the list of errors okay so similar to the other implicit converters there's also an implicit converter that takes a list of errors and returns an error or object okay back in the controller breakfast dot create and we no longer need to pass it this and this since it's generated inside we can call this request to breakfast result which is either the error or breakfast then we can say if this thing is an error then we want to return a problem otherwise we know we have a breakfast in our hand and we can simply extract the breakfast from the arrow or object and use it like we did before okay same thing over here in the upsort so here we would have breakfast dot create we would remove this but we do want the option to pass the id so let's create here an optional field with id and back over here let's just move the id to the end and same over here so this will be request to breakfast result and over here we can say if it's an error then return the errors otherwise let's take the breakfast and we continue like we did before then over here in the create method let's generate a new id only if an id wasn't specified and that's it for now so just to recap what we have we changed the constructor to be private so no one can create a breakfast other than using the create static factory method then over here we can enforce whatever business rules that we have that have to do with the breakfast object okay so we're encapsulating logic that has to do with a breakfast inside the breakfast object finally we're turning either the breakfast or a list of errors to the controller which it's the controller's responsibility to make sure that what he has is a valid value now back over here we would simply return the first error and that's a shame because we know we have here a list of errors with details that are very relevant to the client to know how to fix his request so instead let's check if everything that we have in this list of errors are validation errors so we can say if errors that all and the type of all of them is validation then what we want to do is we return a validation problem so the controller base has another method which is called validation problem which if we give it a model state dictionary we'll convert it to a beautiful error response which contains all the details so let's see what this looks like so we want to create here a model state dictionary and to this model state dictionary we want to add all the errors that occurred so the way we can do this is yes exactly right so we're simply iterating through the errors and adding a dictionary entry with the error code and the description and then we need to pass it to the validation problem method as so and that's it right so we're basically saying if all we have are validation problems then let's return bad requests with all the details and this will return a standard response otherwise let's take the first error and return the appropriate response you know we can actually make it a bit better so let's imagine that five errors occurred if even one of them is an unexpected error then we can't really trust any of the other errors so we can add some logic that says if there's any error where the error type is unexpected then we want to return 500 internal server error okay let's run our project and make sure that it behaves as we expect let's first look at the good flow so creating a new breakfast works as we expect let's try retrieving it works as we expect as well let's try updating something we get no content so let's try retrieving it again we can see that we got the updated value lastly let's try deleting it and we get no content so when we try getting it now then great we get not found if we try to upset it then we can see it was created as we expect now i want us to test the error flow let's put here some details that are invalid and try to make the request and we can see we got bed requests with descriptive errors of what happened let's just fix the space that we're missing so back in the errors let's add a space both here and here you know back in our breakfast controller we can actually add another static factory method which knows how to take the request and return the same result so let's see what something like this would look like so back in the breakfast object similar to the create gonna have here a public static error or breakfast which receives the create breakfast requests and simply calls the create method like we did in the controller and we can do the same thing in the observe method as well so over here let's do from let's replace this entire thing with the id and the request and let's add here another static factory method which knows how to take the id and the officer breakfast request and similarly you'll call it with the details and the id and will return an error or the breakfast right we can remove this comment you know if you're looking at this over here and you're thinking this is much less readable than what we had in the beginning and if we simply threw exceptions both for the breakfast object and for the breakfast service then you are right we are paying here a price of readability but i want you to remember that as the application grows over here we currently have one controller and one service but as the application grows we'll probably end up having many services many controllers and more logic down the road and throwing exceptions makes it hard to understand what the error flows in your application are and what errors can occur so personally even though this approach can take a few minutes to get used to what you gain is that you have your errors well-defined and as you're developing the application you're mindful of the scenarios that might occur okay so that's it for this video i really hope you had a good time and you have a better understanding of how to build a crud based rest api completely from scratch i cover topics that are a bit more advanced in my channel so if this wasn't freeing and you enjoyed it make sure to check it out and i'll see you in the next one
Info
Channel: freeCodeCamp.org
Views: 178,257
Rating: undefined out of 5
Keywords:
Id: PmDJIooZjBE
Channel Id: undefined
Length: 66min 5sec (3965 seconds)
Published: Mon Aug 01 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.