How to Build an API Server with Rust and MongoDB

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
So today we're going to build an API for dog walking um my girlfriend is currently starting a dog walking business and like any good boyfriend I'm going to of course create a rust API for her this is not really the performance intensive task uh that rust is necessary for but I hope that if we go through how you can build an API for something simpler it'll give you the foundations for building an API for something more complex this is aimed at beginners to rust but not beginner programmers so I'm not going to talk about what an HTTP server is um what a server is or go into much detail about if you think those topics would be interesting uh let me know in the comments but for now let's dive in and build our HTTP dog walking server in Rust if you're new to rust you can check that you've got it installed the rust C command and also we want to check cargo great so these are the versions that I'm working with and now I'm going to start a new project by writing cargo new rust server toal and we are going to code okay now we're in our rust project and the first thing that I'm going to do is add a bunch of dependencies um so we are going to write Fargo add um actic web is the framework we'll be using uh for our server Cho for time just youtil will help uh us with our asynchronous code mongodb for our database and SD Seri for uh serializing and deserializing data now we're inside our main RS function um and instead of bringing to something to the console we are going to return an HTTP server so to do that um first turn the type of result and then in here we're going to return an HTTP server from actic want a new one and we want new app I'm going to implement a service that we'll write in a second called hello right so then we want to bind this to uh Local Host and I'm going to choose bort 50001 um then we're going to run and lastly we are going to await shouting at us for couple of reasons we need to import app and now I'm going to write my Serv oh getting one thing um in order to get the HTTP server functionality we need to use an attribute that triggers a macro um that's going to make main uh behavior in the way that we want so that going to be hash um actic web okay and you also need this function to a okay and now we can write our first root this is also going to follow an attribute pattern so hash uh get and this will be the main index rout I sync function called hello this is Implement a responder and this returns and ITB response okay that's like a status 200 response and I will say hello is our rust server we have a route so we can now run it and I'm going to run it using cargo watch uh C flag for Shing the screen uh we're going to tell it where to watch uh Source folder and X run means that the watch command is only going to run when a change is detected great so now we can go over to our browser we can visit Local Host 51 and we see Hello YouTube cool this is the basis of our server um let's add some Roots we're going to be building an API for a dog walking business um so we're going to create some roots to make a booking add owners and dog and cancel the booking the file structure of our project I'm going to create three new folders models for the database models roots for our HTTP roots and services where we'll handle the database connection um so let's make those um and then we're going to start with the models and defining our data structures so if we go into the uh models folder going to now create models for bookings dogs and owners and we got booking model. RS RS and owner mod. RS and we're also going to create a mod RS for exports cool so if we go in there we now have all of our files okay we start with the booking model so um I'm going to create a new public data struct of that um called booking and this is the data structure of what we expect to end up in the database so we're going to be using mongodb data types in this one um we are going to have an ID which is type an object ID okay let's start by importing MB B Sun great okay we're going to have an owner that will be reference to another collection so we need another object ID uh start time is going to be a date time also import and then we'll have a duration of the Walk practice put unit so let's say in minutes um and this is unlikely to be more than a couple of hours so we can fit it in a u8 and we'll also give uh the functionality to C want be to Market as cancell cool okay last thing to do is just make sure that all of these fields exported and we will do exactly that H model okay and then I do want to go back to uh main file and just declare mod should link everything up nicely okay there's one thing I forgot to do and that's uh we want to give this data structure the ability to be serialized and deserialized else we want to debug it let drive uh debug serialize and serialize sweet Okay so this is a good data structure for our database but it's not going to be a great data structure for our API users um instead we're going to want to enable them to provide things like a string instead of a date time uh and we're not going to expect them to come with an underscore ID so how do we do that well the common pattern is to have a separate data structure and then to implement a way of converting uh from the request to the database structure so we will copy this but instead our new data structure will be booking request and then inside it we'll have just an owner start time and duration these top two are going to be strings so how do we convert from uh strings to the format that we want we are going to implement a tri from um you can also Implement a thing called a from um but a TR from can fail and as we're dealing with User submitted data which could be unexpected we need to account for the possibility of failure so we are going to implement trve from with a type of booking request and this is for the booking type before we go any further I'm just going to create a generic error type for whatever errors uh might get returned so type error going to be a box uh and then a dynamic standard error of type error any of those and then inside this we have a function dve from I want to call this item and probably the most tricky thing to convert is going to be our date time um it's going to do it in two steps we're going to use the Chrono crate which is a popular crate for time operations in Rust um so first we're going to create a Chrono dat time uh this is going to be of type system time we will import function pass from RFC and the one we want is this one one 3339 and in there is going to go borrowed version of our item this could error so we want to handle that give it errors I want to print a failed to pass stop time and whatever the error is then we'll specify our time zone oh PR Mark so that uh we can use the result of this with time zone and we'll say want to use UTC for that and then lastly we'll put into function that will convert this to a system time great now we have that we can probably go ahead and start returning our results um so okay the successful case is that we return self IE type booking this has an ID which will be a new object ID um next is the owner so this we can use uh the pass string function on the object ID and we'll pass in the item owner and I had some error handling as well failed now for the start time because we did the hard work above we simply have to use uh long dat time and we can get it from no and finally cancel we'll just put in a default value of Boss okay let's save that um it doesn't like self oh we're missing duration in minutes that one's the easiest don't have to do anything now we have a booking object that is going to represent the database type we have a request object that's a bit simpler and can be used by the end users of our API and we have a function convert from one to the other so now we know the formula we can do this for the other type so next I'm going to go to uh the owner type in fact let's copy this across right so this time instead of booking it's going to be owner uh and owners are going to have some different things they'll have an ID but everything else will be different so we want a name we want email phone and an address and we'll say they can all be strings so the only thing we need to worry about in our conversion function is this ID um let's copy across everything else we want to keep and then down here we don't need to worry about dates or times um this line can stay the same and then all these fields Simply Be item Dot and the name and it's going to be similar for the dog model um let's copy the onus and we'll rinse and repeat this time instead of owner we are dealing with dog and I think we'll put the reference to the owner on the dog so this is an object ID then the dogs can have a name maybe an age uh again dogs sadly don't live very long so we can use a u8 then a breed and I think that these pieces of information aren't going to be critical so what we can do is wrap them in an option type to signify that they might not be there so in our requests we're not expecting people to provide an object ID format so that's going to need to change to a scen and but everything else can be the same object ID becomes a string and then in our conversion function we're just doing two conversions so go back up and grab these most of these can just be taken directly but the owner is going to be object and I'll add an expect for good practice to pass oh I've noticed we've not getting intell sense because we need to export our new models dog and owner well now that I've done that getting some unhappy noises oops these should say dog and he should say oh no okay the next thing we're going to build is our database server so head over to the services folder and I'm going to create a new file called. RS and then as usual uh mods. RS Services RS as well okay so then if we go back into our database file the first thing that I want to do is create a data structure for our database will need to be exported so we'll say public structur database uh and that's going to contain a booking which is mongodb collection of type booking and then we'll add others dog is a collection of type dog and owner likewise okay great the next thing to do is implement this database and give it a way of uh connecting so we are going to implement our database and we're going to give it an a nit function public uh async of AIT which is going to return database itself itself so in this video we're going to presume that you know a little bit about mongod DB and how to connect to it I'm not going to run through how to set up a local instance but effectively when you have a mongodb database whether that's online in the cloud via something like Atlas or locally uh you have a URI string so I'm going to use a local string um and I'll also use the environment uh variables library that comes with rust so that if you wanted to you could also provide a URI that way so we're going to say let URI equal um match and we'll use the M library M so either it'll take an mvar called URI if it finds one that's okay and it will turn it into uh a string like that or if there's an error and it doesn't find what it's looking for it'll grab a local U string okay and to stop the red lines I will return Bas booking dog owner so next we need to create a client for our database so let client equal and we can import uh the MB client with your I String I uh I wait this and then I will we'll unwrap it then we're going to create our database uh it a name client. database uh and I'm going to call this dog and then lastly we just need to Define our collections so let's let booking be a collection type booking and all we have to do for this is call The Collection function and pass in again we will do this for log and owner just a little clean up so now we should be able to initialize our database uh and I'm going to go ahead and do that in uh our main. RS function so before we return the HTTP server let's define a new database uh with so we need to our own database we want to run in it and let's or we wait for that then we're going to use actic to create a data object SOB data equals data new and take our database and then we can pass this into our app so before we Define our service we we can specify want e data it and we also need to import data from actic where we're getting an error here and I will show it to you closure May outlive the current function but it borrows dvore data which is owned by the current function we have some suggestions of how to solve it both closure take ownership use the move keyword okay let's do that okay our work on the database layer isn't done because before we create our Roots we're going to want to write database functions to do all the things that we want our roots to do so to start with we're going to want to expose end points that allow somebody to create an owner dog and booking um so each of these functions will be pretty similar um of WR one and then it should be a case of copying so we want a public and a synchronous function called create owner it's going to reference database um and it will also accept an argument of type owner and we uh anticipate that we want to return and set one result or an error we want this in this function let's define a result s to reference the database and it's going to be owner the owner collection and we'll use the method inert one uh we insert an owner we're not passing in any special options uh we need to wait for this and then we will call okay and for good measure I will add a little error message creating and return our result this we can repeat twice for our two other data types so we'll do create dog this the dogital D there and similarly down here right the next thing that I want to do is create a function to cancel a booking this is going to be ever so slightly more complicated um but it's not the most complicated one that we're going to write so uh let's define public aing function of cancel booking and it's going to follow a similar format so we've got uh self and this time we'll just ask our users to provide a booking ID of the booking they want to cancel and this can be a borrowed string type and we'll use our expected uh type which is now an update result this time we're going to let our result equal cell booking and we're going to call update one okay to write more complex operations with we need to use the doc macros so for our query that's going to be a dock exclamation mark um and we are looking for the document that has an ID of whatever string has been provided turned into an object id cing id unexpect failed pass ID okay and then the update will also be a doc in here we are going to set a new field and for those familiar with we have to use the dollar set syntax and here we are going to Simply change cancelled to true there are no options that's none going to await okay and finally we can un expect okay result oops semicolon this is my bad habit from many years of typ script better cool right finally uh best to last we're going to write a function to fetch all the bookings um this is not so much about rust as about understanding the way mongod DB Works um we're going to write an aggregation which is basically a series of mongodb operations in one single database interaction um so don't worry too much about what this looks like if you're not experienced with I'm not going to go into a massive amount of detail um but essentially what it does is it's a bit similar to joins in SQL we're going to look up the dogs and the owners and attach those to the booking so we get an object with everything we need in one place let's call our function get bookings right now we're just going to get absolutely everything and we want to return a vector of booking type with everything else so we're going to need a new type for that let's call it full looking comp is going to shouted us but we'll come back to that right this time we going to have multiple results so let results s do cing and we are going to do an aggregation again I don't think option for this we want to use a vector type and then each of the aggregation steps is going to use the doc macro so let's say Doc um first we want to have a step and we're going to be matching against we want anything that isn't canell equals false and we probably want bookings that haven't started yet so we're going to say start time is uh greater than or equal to now so let's say we want a BB daytime going to get it from system time and we'll use UTC now oh we want to convert that so let's put that up here now a type of system time you see now and then we'll just use into to convert it that is the first step next we're going to look up the owner relevant to the booking so this is going to be a lookup from the owner collection local field that's whatever it's called in the booking collection is also owner forign [Music] Fields simply underscore ID and in the lookup we want to call it as owner that's going to give us an array with the owners in it uh but we don't want an array so the next step to use is an unwind doc unwind the path of owner and we're going to do something similar with dog so I'll copy this lookup from the dog table this time the local field we'll be using is the underscore ID of the owner we've just fetched foreign field is the owner and we're going to call this dogs because they could be more than one wait okay and expect error all boings let's save that oh oops okay we still don't have a full booking type um so maybe now is a good time to implement that so if we go back into our booking um model RS I'm going to add a new struct call full looking okay this is going to have all the same things as the booking plus some extras so let's copy those fields we know we have but we're also going to change the owner field that is now being looked up from another collection and we've got the entire owner so this will have type owner and we will also um Vector containing dogs so now we should be able to import this full booking one error is gone now we've got a new one so we go to the end of our aggregation we're going to have to do some work to get our bookings into the right format so let's start by creating a new Vector let mute bookings the vector of type for booking and new vector and actually maybe I can bookings now we're going to have to Loop through and push the bookings from the format they've just come back in into our vector format so I'll use a while loop we'll say while let some result um then we want to do results next that's from the Futures library that we uh we added at the beginning await what this is going to do is it's going to go through the bookings one by one um and then we can do a match on the result so we match result it's okay then we get a document out of it so let's let one booking be a full booking and it's going to be from the document doc we'll have an expect that says error converting document to full okay if it works we'll push it into our vectoring push missing an S okay and if something goes wrong then we have error error and we'll Panic error getting and there will be some text that we can add there cannot borrow results as mutable as it's not declared as mutable ah happy now brilliant okay at this point we have all the database functionality that we want for our MVP so all that's left is to create the roots creating the roots is going to follow a similar path to creating the models okay so we're going to CD into our Roots directory and then we are going to create a bunch of files similar to what we did for models so it's booking grout. RS uh dog RP RS Root RS and mod RS before I do anything else I'm just going to quickly Our Roots and we will add the mod I think this time we'll go in reverse order we'll start with dogs and this bit really is just the glue that stitches everything together we've already done the hard work creating the database interactions this is something that actic helps us a lot with we going to start with dogs and owners because all we want to do is surface the ability to create each of those with dogss we will use post attribute signal that it's post request and we'll call the rout dog all right and I'll follow similar naming conventions to the database so we're going to say public a synchronous function to create a dog okay we're going to want to pass in a database that will be type data uh database make sure to choose use database we've defined in our services and the other argument is going to be the request actic gives us a Json type for this so Json dog request this is going to return an HTTP response I think I'm going to do matches for all of these so we'll have a match BB um create dog and then we can now use our five from conversion function that we created when we were making the models so dog D from uh and we'll be request and this really is the simplest as we just need to clone everything so owner request. owner clone then we'll do that for name and for age for breed if there's a problem we will say error converting dog request to dog we wait it okay if that's fine then we should have a dog and we'll get HTTP response okay Jon and if there a problem we'll have an error and we can return an internal error so H internal Ser error P that and the body of this can be the error we've got that's a string we can copy this pretty closely for our owner is going to become owner everywhere owner and let me capitalize all the data structures and owners have a few different properties they have a name they also have and email a phone and an address okay I think that is everything so lastly we have the bookings we'll start with a create function and then we'll implement the cancellation and the fetch for all the bookings so the create is going to follow a very similar pattern all going to be looking okay bookings have an owner they have a start time and they have duration minutes two more points to go the fetch function which was quite complex to write on the database uh is going to be very simple because we've done all the hard work so this will just be a simple get request get attribute on actic we will call this root bookings public async function get bookings this accepts an argument of our database as before and nothing else and this is going to be much simpler so we're simply going to run our get bookings function on the database wait it and then follow format above lovely nice and easy finally we have our route to cancel our booking and I want to make this rout take Dynamic ID so I'll show you how to do that um let's use put those will be fine too and I want our route to be something like flash booking then we'll have the ID so we can do that with curly braces and then we'll say cancel right I'll copy the function above going to be similar just with a difference which is how we get access to this ID so the way we do that is by specifying a second argument of path and this is going to have a type path the type inside the path is a twoo type um so we just have one string and then to get access to that string so we Define a variable called ID we can run path dot into inner and again this this is going to return a vector so we just need to grab the first item and then instead of get bookings we can run database cancel booking have our ID and we just want to call this cancel okay oh we need to borrow this so we can use dot as string there's one final thing to do and that is actually to tell our HTTP server about all these roots if we go back into main RS here where we've called service and specifi hello just add all of our other services I think there are five create cooking create dog create owner we also want to get bookings and cancel now all that's left is to run our watch function again and see if we can try Our Roots so cargo watch then I'm going to open up Postman and I've prepared some requests for us to try so here's Postman here's an owner request insed something into our database here then let's use the ID of the new owner to add a dog to that particular owner so we'll add one here let's add two dogs that will be fun I'm using my grandparents dogs inspiration we'll say Barney the poodle age8 that okay now we have two dogs then the booking so we'll use the OWN I do again we'll say that yeah it starts in May it's half an hour walk great and I'm going to keep a hold of this ID so we can cancel that booking first let's fetch it and see what we get back so this has no body it's a simple get request and that is good news so we have the ID of the booking all the owner information here and two dogs one two and the remainder of the information that's great that's absolutely everything that we need uh we can see it all in one place um and finally our put update request we're going to ask for the booking with the ID we just got to be cancelled send this is sending uh what if I count of one so then what should happen if I run the Gap again is that this should disappear because the booking is now cancelled and it's gone we've got an empty W so hope you found this tutorial a useful introduction to building HTTP servers in Rust it's my first tutorial so I welcome any feedback filming a video was harder than I expected so I'm going to see if I can mix things up a bit for next time but I hope it was useful thanks for watching
Info
Channel: Bret Cameron
Views: 1,736
Rating: undefined out of 5
Keywords: actix, actix-web, api, backend, coding, http server, mongodb, programming, rust, rust server, server, software development, software engineering, web development
Id: EQfGDIyC7ng
Channel Id: undefined
Length: 43min 32sec (2612 seconds)
Published: Mon Apr 15 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.