Coding

#8 Adding Bookings | Build a Complete App with GraphQL, Node.js, MongoDB and React.js

  • 00:00:01 welcome back to this series in the last
  • 00:00:04 video we made sure that we have dynamic
  • 00:00:07 relations between our events and our
  • 00:00:09 users and so on now in this video we'll
  • 00:00:12 add bookings because thus far the
  • 00:00:15 relation between events users is that an
  • 00:00:18 event is created by a user or that a
  • 00:00:20 user created a couple of events
  • 00:00:22 obviously users in the app will be
  • 00:00:25 building in the end should be able to
  • 00:00:27 book events as well and not just create
  • 00:00:29 them and therefore we'll have a look at
  • 00:00:31 how we could add a booking model and
  • 00:00:33 these relations in this video
  • 00:00:38 so let's dive in and first of all I will
  • 00:00:42 create a new model because a booking
  • 00:00:44 will be a separate entity in my project
  • 00:00:47 now this is only one way of implementing
  • 00:00:49 this though you could also use the two
  • 00:00:51 existing models and besides a creator on
  • 00:00:55 an event you could also add and rold
  • 00:00:59 users or anything like that and that
  • 00:01:01 could be an array of user IDs of users
  • 00:01:04 who booked this event
  • 00:01:05 now to make fetching easier later and
  • 00:01:09 you have the capability of conveniently
  • 00:01:13 fetching just the bookings I will add a
  • 00:01:17 new model but let me really highlight
  • 00:01:19 that this is only one way of
  • 00:01:21 implementing this and you can definitely
  • 00:01:22 take the other route too now in my
  • 00:01:25 booking model we're not talking about
  • 00:01:27 the graph QL schema this is for Mongoose
  • 00:01:30 instead so here I will import Mongoose
  • 00:01:33 by requiring it and I will import or I
  • 00:01:37 will set up access to that schema
  • 00:01:40 constructor mongoose gives me i'll then
  • 00:01:43 define my booking schema here by calling
  • 00:01:47 new on that schema constructor and
  • 00:01:49 passing a javascript object where we now
  • 00:01:52 defined a schema and in there i will add
  • 00:01:56 two fields the first one is let's say
  • 00:01:59 the event ID or just event and that is
  • 00:02:03 very simple the type here of that field
  • 00:02:07 is of course schema types object ID and
  • 00:02:11 it is a reference to the event module to
  • 00:02:14 the event model now obviously here we
  • 00:02:17 will just store the ID but thanks to
  • 00:02:19 populate and so on we could use the
  • 00:02:21 reference to populate it automatically
  • 00:02:22 the other fields of course the user
  • 00:02:25 because every booking has one event that
  • 00:02:27 was booked and one user who booked the
  • 00:02:30 event
  • 00:02:31 so therefore a user now is schema types
  • 00:02:36 object ID – whoops types object that he
  • 00:02:40 is to type and the reference here of
  • 00:02:42 course is to the user model like that
  • 00:02:46 now I'll add one other thing and that is
  • 00:02:49 a second argument to the schema
  • 00:02:51 constructor
  • 00:02:52 there we can set up some options for
  • 00:02:54 this schema we didn't do that before but
  • 00:02:57 here I will set time stamps to true and
  • 00:02:59 then Mongoose will automatically add I
  • 00:03:02 created at and updated at field to every
  • 00:03:05 entry it adds in the database and this
  • 00:03:07 is quite nice so that we can later find
  • 00:03:09 out when a booking happened when a user
  • 00:03:12 booked an event so now let me export the
  • 00:03:18 model by calling Mongoose model I'll
  • 00:03:21 name this booking and I'll point at my
  • 00:03:23 booking schema of course and that is it
  • 00:03:26 for the Mongoose booking model now we
  • 00:03:29 can use that in our API and I'll start
  • 00:03:32 by adjusting the schema there I will add
  • 00:03:35 a new type here the type is booking and
  • 00:03:39 it will look just as I defined it it
  • 00:03:42 will have an honorable event and a non
  • 00:03:44 nullable user and due to the special
  • 00:03:49 timestamps key or setup which I added it
  • 00:03:51 will also have a created add field which
  • 00:03:54 will give us a string and an updated add
  • 00:03:57 field which will give us a string and
  • 00:03:58 these two fields are added automatically
  • 00:04:01 by Mongoose every booking will also have
  • 00:04:04 an ID of course because that is also
  • 00:04:06 added automatically by Mongoose so now
  • 00:04:09 we have the booking now obviously we can
  • 00:04:14 now fetch this now by the way you could
  • 00:04:16 also add bookings
  • 00:04:18 to your event here and also to your
  • 00:04:20 users I will leave it as it is for now
  • 00:04:23 we might add this later for now adjust
  • 00:04:25 add this as a standalone type which has
  • 00:04:27 references to events and users but I
  • 00:04:30 will not add the inverse of the relation
  • 00:04:32 to the other models instead I rather
  • 00:04:36 complete my schema here by going down to
  • 00:04:39 the queries and the mutation for the
  • 00:04:42 queries I want to have a way of getting
  • 00:04:44 all my bookings so this will give me an
  • 00:04:46 array of bookings which is non nullable
  • 00:04:49 it might be empty but it must not be
  • 00:04:51 null and of course I'll have let's say a
  • 00:04:54 book event and point here where I expect
  • 00:04:58 to get the he went ID as a string or
  • 00:05:02 actually it will be an ID and that is
  • 00:05:05 justice
  • 00:05:06 we pass in here and this is required and
  • 00:05:08 this will return me the booking let's
  • 00:05:10 say and I also want to have a cancel
  • 00:05:14 booking field where I pass in the
  • 00:05:16 booking ID and where I then get back
  • 00:05:21 let's say the event that was booked
  • 00:05:24 before and for which we now cancelled
  • 00:05:26 the booking so now I added three
  • 00:05:28 endpoints bookings book event and cancel
  • 00:05:31 booking and this now means we have to
  • 00:05:33 work on the resolvers next there i will
  • 00:05:36 first of all import the booking model
  • 00:05:40 from the models folder so the booking
  • 00:05:43 model we just added the mongoose model
  • 00:05:44 so that we can interact the database and
  • 00:05:47 then here we have the export for events
  • 00:05:51 i will now also add bookings here as a
  • 00:05:55 async functional like this just as i
  • 00:06:00 have it for events and there i will also
  • 00:06:03 use try-catch so i will already setup
  • 00:06:07 catch here where i will just throw the
  • 00:06:09 error again you could do more there i'll
  • 00:06:11 just keep it simple for now and in the
  • 00:06:14 try block there I now want to try save I
  • 00:06:19 get all the bookings from the database
  • 00:06:21 and therefore I'll just use that booking
  • 00:06:25 object that booking model call find
  • 00:06:28 without arguments and a wait for this
  • 00:06:31 and store the result in bookings thanks
  • 00:06:34 to async await I use this syntax and
  • 00:06:36 then bookings well that is what I want
  • 00:06:38 to return here but it will actually
  • 00:06:41 return a map version so that I can
  • 00:06:44 adjust each booking because obviously
  • 00:06:47 instead of returning the raw data as I
  • 00:06:50 fetch it
  • 00:06:51 I will return an object where I pull out
  • 00:06:55 all the document data and where I then
  • 00:06:57 override the ID with booking dot without
  • 00:07:02 an underscore and where I also replace
  • 00:07:04 created at and updated at to have
  • 00:07:06 timestamps that actually makes sense to
  • 00:07:09 the user so here for created ad I will
  • 00:07:12 use the new date constructor and a well
  • 00:07:14 it will pass in booking talk created
  • 00:07:19 and then it will call to izo string and
  • 00:07:22 it will just quickly duplicate that line
  • 00:07:26 to do the same for updated at so that
  • 00:07:29 this is returned in a readable and
  • 00:07:32 usable way instead of just that
  • 00:07:34 milliseconds timestamp which is
  • 00:07:36 otherwise used so now I have my bookings
  • 00:07:39 but of course to see anything there we
  • 00:07:42 need a way to create a booking and
  • 00:07:45 therefore maybe at the very bottom I
  • 00:07:47 will add the book event resolver because
  • 00:07:52 obviously in my schema I defined that we
  • 00:07:54 would have a book event resolver and
  • 00:07:56 there I will also use async I do expect
  • 00:08:01 an argument here and that is the event
  • 00:08:03 ID um for which I want to make a booking
  • 00:08:06 to be precise I'll get arcs but in the
  • 00:08:08 arcs object I'll have that event ID so
  • 00:08:11 here will now create a new booking by
  • 00:08:14 using that constructor and in there I
  • 00:08:18 need my event ID and I need a user ID
  • 00:08:21 now again we'll work on the user ID
  • 00:08:24 later for now I will hard code it so
  • 00:08:26 we'll grab the one from above and set
  • 00:08:29 user ID equal to that we're just user it
  • 00:08:34 is not user ID right in my model I named
  • 00:08:36 it just user and just event so let's use
  • 00:08:38 that and for the event well there I of
  • 00:08:42 course want to get the event by the ID
  • 00:08:45 which I passed in so we'll actually
  • 00:08:47 first of all get D went by a waiting for
  • 00:08:51 event find one and there I'm looking for
  • 00:08:56 the event where the ID matches my RX
  • 00:08:58 event ID now remember I will get an
  • 00:09:01 argument named event ID because that is
  • 00:09:03 how I define my schema I need that event
  • 00:09:05 ID here so I can use it here get that
  • 00:09:08 single event
  • 00:09:10 thanks to async await I use this syntax
  • 00:09:12 store it in this event here could also
  • 00:09:16 name this fetched event to avoid
  • 00:09:17 confusion and then assign this in my
  • 00:09:20 booking time stamps will be added
  • 00:09:22 automatically by Mongoose and therefore
  • 00:09:25 here I can now
  • 00:09:32 create a result by a waiting for booking
  • 00:09:35 save and then I can return the result
  • 00:09:39 here and it will return it in the same
  • 00:09:41 way I used before result dock the ID
  • 00:09:44 should be result ID and I also want to
  • 00:09:47 work on the time stance and for this we
  • 00:09:49 can really just copy the logic we get up
  • 00:09:53 there where I replace created at and
  • 00:09:55 updated at and again you can refactor
  • 00:09:58 this to not copy code I want to be very
  • 00:10:01 explicit here so I will move in created
  • 00:10:04 add and updated at only differences I'll
  • 00:10:07 not be accessing the booking object but
  • 00:10:09 the result object here otherwise this
  • 00:10:11 should be fine and with this I should be
  • 00:10:13 able to book an event already now let's
  • 00:10:16 give it a try shall we let's go back to
  • 00:10:18 our graphical interface and there I want
  • 00:10:21 to run a mutation
  • 00:10:23 now let's reload here real quick to get
  • 00:10:26 auto completion and load the new schema
  • 00:10:28 definition book event there I need the
  • 00:10:31 event ID now for this I need an event ID
  • 00:10:34 I'll grab one from the database let's
  • 00:10:37 say this one just a string without
  • 00:10:40 object that he's surrounding it like
  • 00:10:42 this and we will get back information
  • 00:10:44 about the booking that shall be shall be
  • 00:10:46 the ID here and let's say create it at
  • 00:10:48 and this doesn't look too shabby now
  • 00:10:51 please note that you'll not be able to
  • 00:10:53 drill into the user for example because
  • 00:10:56 I haven't set up the logic to do so but
  • 00:10:59 booking works I can prove this by
  • 00:11:02 refreshing my collection here in the
  • 00:11:04 collection view we have the bookings
  • 00:11:06 collection and there we do have that
  • 00:11:09 booking here instead and please note I
  • 00:11:11 just made a booking again when I showed
  • 00:11:13 you that we can't drill into the user
  • 00:11:15 hence we got two bookings so we got that
  • 00:11:18 booking logic now to drill into the user
  • 00:11:22 and the event we need to use the logic
  • 00:11:26 we used before with our functions that
  • 00:11:29 could be executed now I got a user
  • 00:11:31 function that only takes a user ID I got
  • 00:11:34 now a single event function and
  • 00:11:36 therefore here I will create a new
  • 00:11:39 function and to avoid naming clashes
  • 00:11:41 with event which I use somewhere else I
  • 00:11:44 will name this
  • 00:11:45 single event here and there I get my
  • 00:11:50 event ID and I want to use try-catch
  • 00:11:53 here too so catch any errors I might be
  • 00:11:58 getting but only throw them back here
  • 00:12:00 obviously you could put more
  • 00:12:01 sophisticated error handling into place
  • 00:12:03 right now I'll do that later I want to
  • 00:12:07 try something here and I want to try
  • 00:12:08 getting that event obviously so here I
  • 00:12:11 will get my event by awaiting for event
  • 00:12:14 find one or find by ID here actually
  • 00:12:22 with my event ID and then I simply
  • 00:12:25 return that event but again with the
  • 00:12:27 tricks I showed you before where I get
  • 00:12:31 all the data in the document then
  • 00:12:34 replace the ID with the event ID and
  • 00:12:37 obviously I want to make sure that for
  • 00:12:39 the Creator here I also again use the
  • 00:12:42 same approach I used before where I bind
  • 00:12:44 that user function with my Creator ID
  • 00:12:49 that is stored in that event I retrieved
  • 00:12:52 so that we can drill into that creator
  • 00:12:54 later too so there is that single event
  • 00:12:57 function only difference to events here
  • 00:12:59 is that I obviously don't fetch multiple
  • 00:13:01 events but just one otherwise it's the
  • 00:13:03 same logic I have returned that single
  • 00:13:05 event here and now we can use that first
  • 00:13:10 of all in bookings here where I retrieve
  • 00:13:13 all bookings there it would be nice to
  • 00:13:15 drill into the user too
  • 00:13:17 so there I can already call user find
  • 00:13:20 this and pass booking talk user and also
  • 00:13:27 set up event and call single event here
  • 00:13:31 with bind this booking doc and then it
  • 00:13:39 is event so basically again using the
  • 00:13:43 logic I explained in an earlier video
  • 00:13:44 where I drill into that I use my event
  • 00:13:47 ID and my user ID which is part of the
  • 00:13:49 booking I retrieve and I have forwards
  • 00:13:51 that to the user and the single event
  • 00:13:53 function which then goes to the database
  • 00:13:55 and gives me all the data and this will
  • 00:13:57 only be executed if I really
  • 00:13:59 access this field if I don't do that
  • 00:14:01 this database query will not be executed
  • 00:14:03 so it doesn't hit the performance if I
  • 00:14:06 don't need this field and of course I
  • 00:14:08 will use that same logic and therefore I
  • 00:14:10 can just copy that I will use that same
  • 00:14:12 logic down there when I book an event
  • 00:14:14 there I also want to be able to drill
  • 00:14:16 into user and event and therefore now if
  • 00:14:19 I book an aviary bent and I want to get
  • 00:14:22 the email of my user this now works and
  • 00:14:24 if on the other hand I write a query and
  • 00:14:28 I want to get all my bookings then I can
  • 00:14:32 get created at but I can also dive into
  • 00:14:35 the event and for the event of the
  • 00:14:38 booking I can get the title and I can
  • 00:14:40 dive into the Creator and get the email
  • 00:14:42 address and this not all works if I
  • 00:14:45 execute this you see I get the free
  • 00:14:47 bookings for the different events I got
  • 00:14:51 here and with the email addresses of the
  • 00:14:53 creator and this is pretty nice now the
  • 00:14:57 missing thing when we look at our schema
  • 00:14:59 is the capability to cancel a booking
  • 00:15:02 and for that I'll go into my resolvers
  • 00:15:06 and after book event I will add cancel
  • 00:15:09 booking so exactly the name I set up
  • 00:15:12 here in my route route mutation and
  • 00:15:14 cancel booking all there's an async
  • 00:15:17 function now as you can tell by looking
  • 00:15:19 in my schema we want to get the booking
  • 00:15:22 ID there so I will get some Oryx here
  • 00:15:25 and in there again try-catch and you
  • 00:15:31 could omit this if you don't do anything
  • 00:15:33 else with the error then throw it of
  • 00:15:34 course here I will try getting my
  • 00:15:38 booking so I will use find by ID and
  • 00:15:41 pass in rx
  • 00:15:43 booking ID this should give me a booking
  • 00:15:46 and I'll a wait for it and then here I
  • 00:15:50 of course want to kind of delete that
  • 00:15:53 booking right now actually we can speed
  • 00:15:55 this up therefore we don't need to get
  • 00:15:57 it first I can instead just use booking
  • 00:16:01 delete one and delete by that ID here so
  • 00:16:06 by rx booking ID
  • 00:16:10 I will still await this but thereafter I
  • 00:16:13 wonder I want to return the event which
  • 00:16:16 belong to this booking so therefore
  • 00:16:17 before calling this
  • 00:16:19 I'll get my booked event by first of all
  • 00:16:23 getting that booking by awaiting for
  • 00:16:29 booking find by ID rx booking ID and
  • 00:16:37 there I'll call populate and I want to
  • 00:16:39 populate the event field so that I got
  • 00:16:41 this event data right so I got the
  • 00:16:45 booking with the rich event data then
  • 00:16:48 here and when I get that event by
  • 00:16:53 accessing booking event that will be my
  • 00:16:57 rich populated data I will spread this
  • 00:17:00 into a new object because there I want
  • 00:17:06 to overwrite the ID to be booking event
  • 00:17:10 ID with that virtual gather and I want
  • 00:17:13 to overwrite the extra Gator here to use
  • 00:17:15 my user function find this and then pass
  • 00:17:19 booking creator so that ID as an
  • 00:17:22 argument so that I can drill into this
  • 00:17:24 event and drill into the creator of the
  • 00:17:26 created event here and now with this
  • 00:17:31 setup I delete my booking and then I can
  • 00:17:34 return this prepared event here for that
  • 00:17:37 I'll save this and now I just need the
  • 00:17:40 ID of a booking so I will actually fetch
  • 00:17:43 my ID here let's grab that first one and
  • 00:17:47 now let's run a mutation where I cancel
  • 00:17:53 a booking and the booking ID is that
  • 00:17:55 idea just grabbed and that for the event
  • 00:17:58 I get back I want to have a title and
  • 00:18:00 that should be testing as you can tell
  • 00:18:03 here and I want to dive into D creator
  • 00:18:06 and get the email address if I hit enter
  • 00:18:09 that fails event title fails lets
  • 00:18:12 console lock that event here and let's
  • 00:18:16 quickly go to Atlas and grab and our
  • 00:18:18 booking because it should have been
  • 00:18:19 deleted actually so yeah only two
  • 00:18:22 bookings left let's grab the other
  • 00:18:24 here try to lead that obviously
  • 00:18:27 which it will still fail if we have a
  • 00:18:29 look here yeah obviously my mistake is
  • 00:18:33 I'm not using duck here I should be
  • 00:18:36 using underscore doc for retrieving my
  • 00:18:39 event data like this now if I save that
  • 00:18:46 can get rid of that console.log now it
  • 00:18:49 should be working
  • 00:18:50 so let's now cancel the last booking
  • 00:18:52 that should be remaining if i refresh a
  • 00:18:54 MongoDB here it is let's move over
  • 00:18:57 let's try canceling this and cannot read
  • 00:19:02 property talk of null and the problem
  • 00:19:06 here is that when I bind the idea of the
  • 00:19:09 Creator to that user function I of
  • 00:19:11 course should also access stock on the
  • 00:19:13 event of course and not on the booking
  • 00:19:14 itself the booking doesn't have a
  • 00:19:16 creator booking has a user but I'm
  • 00:19:18 looking for the creator of the event so
  • 00:19:20 I need to access booking event so now if
  • 00:19:22 that I deleted all my bookings so let's
  • 00:19:25 create a new one hence I'll grab a new
  • 00:19:27 event ID and then again try a mutation
  • 00:19:31 where I call book event for this event
  • 00:19:34 ID here and now there let's immediately
  • 00:19:37 get back the ID obviously and run this
  • 00:19:41 looks good and now let's run an Avenue
  • 00:19:46 tation where I cancel the booking for
  • 00:19:48 that booking ID and this now should
  • 00:19:50 return us the cancel event so there I
  • 00:19:53 want to get the title and also the email
  • 00:19:56 address of the creator and this now
  • 00:19:58 looks better and if we check our Atlas
  • 00:20:00 cluster in the bookings indeed we find
  • 00:20:04 no bookings so this is now the booking
  • 00:20:06 functionality added as I explained you
  • 00:20:09 could also add in worse relations on
  • 00:20:11 event and user so add some fields to the
  • 00:20:14 event and user model to point at the
  • 00:20:16 booking model we might do this later for
  • 00:20:20 now I like this setup which I have here
  • 00:20:22 and we'll see if we need more
  • 00:20:23 flexibility later to query for that
  • 00:20:26 later we can obviously also split the
  • 00:20:29 resolver a bit more because this file is
  • 00:20:31 already getting huge too but we have to
  • 00:20:35 core the very core functionality of our
  • 00:20:37 back
  • 00:20:38 API implemented now obviously
  • 00:20:40 authentication is missing and as I
  • 00:20:42 mentioned cleanup work can be done but
  • 00:20:45 this was another major step forward and
  • 00:20:47 therefore we're not far from finishing
  • 00:20:49 this back and all together so hopefully
  • 00:20:52 see you in the next part of this series
  • 00:20:54 bye