Modern backend with TypeScript, PostgreSQL and Prisma - Part 2: REST, Validation, and Testing

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
okay we are live let's double check that everything is correct technically welcome to the second part of this live stream series that we're doing just going to make sure everything is okay here let's see and we should be good okay going to start this with a poll which you can join via slider and this poll will be using this as a way to verify that we've got everything working correctly and that you can follow along so once you're ready i'm going to activate the poll and this will help me uh guide our way through this um and figure out where we're all at in this second part so i invite you to scan the qr code or go to slider.com and enter the code that we have there that's three three three nine nine nine and so far are we getting anything okay so so far no one's answered yet let's see okay first comment yes okay everything is good all right so one person has said is not watched okay so i'll give a brief overview of what we covered in the first one let's see how about rest concepts and whether the code is legible all right all right so a little bit of background on this series in case you are not familiar um let's see okay so the goal of this series is uh as we sort of covered in the first stream was to demonstrate different patterns problems and architectures for a modern architecture and what we're going to do is we're going to build on the first the content from the first stream so in the first stream we built a grading system for online courses and we focused on building the data model for this we did this with prisma migrate and i can show a bit the prisma schema here this was the prisma schema that we used and we had a bunch of concepts we had users we had courses and then users could be members of the course through this course enrollment and this course enrollment was this explicit what we call an explicit many-to-many relation we use this in order to be able to associate multiple users with multiple courses so a single user could be a member of different courses and um courses can have multiple users in fact they can have the same user in multiple courses then we had tests uh which are also associated with the course so every single course can have multiple tests and each one of these tests has a test result which is associated with the student who sat the test and the teacher who graded that test and just a quick reminder that both students and teachers they're both users and their role is defined through this course enrollment relation table so this course relation this course enrollment relation table has this um role uh field here and that role defines whether the user is associated as a student or as a teacher so can i increase the font size oh thank you for the comment nicholas i'll increase the font size um i also urge you to go and update your streaming quality to full hd uh in the previous streams we had uh some users uh point out that the resolution wasn't high enough so we worked to get this uh full hd working great so besides building this schema what we did in the previous uh stream we um ran a migration using prisma migrate and the result of that was a working database so i'm going to just increase the font size here too so you can see this and i'm going to open up pgcli to connect to the database and here we're able to see all of these different tables that we saw in the prisma schema put the prisma schema here on the side so you can see both and each one of these models um maps to a table so we have a user table a course table course enrollment and we can actually get more information about each one of these tables using this uh back uh backslash d and then the name of the table this is uh the postgres cli that we're using so uh we created the schema we created the database schema using the prism schema and prisma migrate and then lastly at the end of the previous stream what we did is we created this seed script so this was just one big typescript function which had a lot of different prisma client calls and each one of these calls was responsible for populating the database with different data so for those of you who are just joining now welcome and what we did is we created here users we created a course we attached members to that course and we sort of looked at the ergonomics in the way that the prismaclient api works um and what we're going to do in this part of the series is we're going to actually look at three things we are going to look at uh the first thing that we're going to do is we're going to create an http server and this http server will will extend it and will add roots to it so that it actually becomes a rest api and some of you i'm sure are wondering uh why are we using rest why are we not using graphql so just to address address this uh upfront um we've worked a lot on many examples at prisma with graphql and we still believe that rest has its use cases and so this this um stream will focus on uh how to build a rest api and and show some of the different use cases for prisma because prisma is so flexible as a sort of database access layer it can be applied to rest it can be applied to graphql and in this one we're going to focus on rest so rest apis are essentially a way to expose a set of resources over http and uh those resources were as we'll see they correlate to these uh models or these data these tables that we create in the database so for example in our schema we had a user model which corresponds to the user table and we're very likely going to have an endpoint which will get users create users update users and delete users and so in this stream what we're going to do is we're going to actually build all of that and we're going to address two aspects of building a rest api namely validation and testing validation is extremely important in all applications or all apis and the reason for that is when you are exposing an api essentially you're exposing um publicly an endpoint or or a set of endpoints which allow users and and api consumers to interact with the api and so the challenge there is that you never really know what kind of data will be submitted to your api because it's publicly exposed and one aspect of that is adding authentication and ensuring that you know who's doing what but even when you authenticate a user you still need to verify that the input the payload that is providing while interacting with the api matches what you expect and as you handle the data you really want to make sure that it adheres to a certain structure so as i said today we'll talk about the rest api uh validation and then the third thing that we'll look at is testing what we're going to do is we're going to use happy js which is a popular node.js framework and we're going to use that of course with typescript and we'll get more into that soon so we're going to use uh happy together with another framework called jest in case you're not familiar i'll just load it up here so happy js this is where you can find more information about happy and jest is a testing framework which is really nice it's fast and uh it it just makes it so much easier to do testing so we're going to combine the two happy and jest and we're going to use this functionality that happy has in order to test the api by simulating http requests going into the api so a little bit about the prerequisites for this part of the series you should have some basic knowledge of typescript node.js and relational databases in case you're experienced with javascript i don't think you need to worry too much especially if you haven't had a chance to try typescript you'll still be able to follow along and i'll sort of flag some of the concepts as we uh as we go along um as i mentioned before we're going to be using postgres as the database and in case you want to look at the code base you can find that on github and the address for the address to find that is two color slash real dash world dash grading dash app i'll also share this in the comments um and to sort of follow along from this part you can look at branch uh at the branch named part two so i'll just share the link so you can have a look at the code if you feel like um all of the uh the completed part by the end of this uh stream we should be at the point where master is so part two the part two branch specifically has um a lot of what we're gonna cover emitted so that we can actually do that live so that was that and then before we dive in i'm going to make sure so i have postgres running so that's all good and now let's talk a bit about rest so in case you're not so familiar with some of the concepts of rest let's uh sort of cover the base ground here and start with what rest is so rest isn't actually an official standard it's rather just a set of conventions that developers follow to expose state-related operations over http requests this is one of the more standard ways to expose data um or to to build apis and if you're curious about some of these conventions there's a lot of resources on the internet i also highly recommend the github documentation they also have a rest api and you can look at a lot of the different concepts and the different approaches that they use in order to expose this api and so an api has this really important concept called endpoints an endpoint is essentially just an http url that you can access and it has a path it has an http method um eight the http standard has different kinds of uh of um of request types it has get it has post it has delete it has put it has a bunch more and what uh these http methods they're useful because they allow us to uh determine the type of operation that this endpoint exposes so for example what we're going to do is we're going to create a so imagine that the api is called myapi.com and then we're gonna have an endpoint that might be called slash users and it will be a get endpoint and that will be a get users endpoint and similarly we might have a delete where we have here a user id and this is actually this is not a string this is rather a parameter will delete a single user and in addition to that we'll have also a post so that you can actually create users so that will be used to create a user and lastly we might have a put and the put would obviously need also a user id because that will be used in order to update an existing user so this is our plan for today we want to implement these four endpoints this doesn't cover of course the full schema as that's beyond the scope of this stream but in the coming days the repository will be updated with the implementation code for all of these uh end points beyond just these four but these four should be a good uh a good um illustration of the different kind of patterns that you would have for most of your resources um and throughout this stream i'll probably be using two words interchangeably um root and end point but i'm really just referring to the same thing um endpoint is the term that is often used in the context of apis and rest apis and root is the term that is used often in the case of like http servers so when you're actually building the code you're usually defining these routes so let's get into it and look at some of the dependencies that we need in order to build this so the first thing is i'm going to open up here the package json and in this package jason you're going to already see some of these dependencies here we have happy boom happy boom is so a little bit about happy js um happy js is a no js framework for building http servers and it supports validation and testing out of the box the core functionality of happies provided through this happy package and a lot of the other functionality will be through other modules for example this happy joy is a validation library i'll open up the page for joy because uh joy is a really um it's a really nice way to validate objects uh let's see just so you have an example here so um not sure if this is actually the best let's see so there we go so similar to how we define these schema these prisma schemas with joy we define these javascript objects essentially which allow us to validate input data and this can be used independently of happy of course it integrates really well with happy as we'll see very soon and so let's start by um i'll open up this package json again so we spoke a bit about happy about joy and then we have boom boom is just a simple library for uh http errors so as i mentioned before http has different http methods like get post and delete and it also has these status codes and mozilla has a really nice page on response status codes and essentially when you make a request for example when i just loaded this page um a request was made to the mozilla servers and each one of these requests that was made um has this status code and the status quo indicates uh how the request went so generally successful responses range from 200 200 to 299 redirects are in the 300 range client errors of in the 400 range and server errors are in the 500 range i'm going to go back to the comments to see if there's any question okay so we already have a question is there a way to avoid overriding the field mapping added manually in schema prisma when running prisma introspect so oh thank you for that question um yes there is a way there this this is being worked on as part of this feature called range respection we won't be covering that because we're not using the reintrospection the introspection flow uh which prisma supports in this stream but i will share a link later so that you can find that so thank you for the question ayush and now back to the code so these were the dependencies that we had we also had prismacline which we already added and we have here date functions date functions is just a simple date utility which we used in the seed script if you remember we just used it to do some um basic arithmetic with uh with days and then on the dev dependencies what's really important here since we're using typescript so generally when working with typescript um one of the challenges is that uh some libraries are written in javascript and they don't have the types defined and so there's a lot of community efforts to define these types so that you can use these javas these libraries written in javascript in a typescript project and so this is why i added here this this types dependencies for happy and for joy also for jest and it would probably be a good idea to also add the types for a node so i'm just going to do that very quickly the dash d here is uh to mark it to install it and save it as a develop a dev dependency and we should see it show up here shortly okay so we have that for node 14 and i think that's the version of node i'm running great and we also have jest which we'll be using for testing and we have a bunch of scripts here um now let's start with the server so now we get really into it a happy js server really it's it's not that much different than say express or other servers you might be familiar with essentially you instantiate a server and you bind it to a port into a host here we pull those from environment variables and we default it to localhost and 3000 if that's not defined once we deploy this to production we might actually set these environment variables to have control over that and a little bit about environment variables so environment variables are part of this um 12 factor app if you're not familiar with the 12 factor app that was a set of conventions for building applications and you can look at that here so 12 factor.net um and uh yeah so this is generally good practice for a lot of runtime configuration to be stored in the environment variables so that's where this principle comes from and here we define a start function and because we're using typescript i'm also defining the types here so for example for this happy server here i've defined this happy server and this type is in fact defined inside this uh happy this types happy package that we added here the start function also returns the happy server and simply all we're doing here is we're just calling start and we're telling we're logging hey we're running this um we're running the server and here i'm actually calling the start function so if i go to the terminal and i run this and i can do this with npm run dev this is a script that i've defined and because this is uh written in typescript and we're in development mode so typically with typescript you would run the typescript compiler and then the typescript compiler would generate the javascript for you and then you would run the javascript but obviously while we're developing this can be quite cumbersome and so we're using this ts node dev which we're passing at the respawn parameter here and that will ensure i'm sorry about that that will ensure that uh the server is restarted automatically once we make changes and of course it'll automatically transpile so if i just command save here you should see oh it's restarting it and that's the behavior that we're expecting so we've defined this server but it doesn't have any roots defined so that means if i try to make a an http request to it it'll just tell us hey not found because there isn't a root defined so let's start by defining a root and so generally speaking we define roots before we start the server and we do that using this server root and as you can see we're already getting this really nice and rich auto completion this is um really useful and it's all because we're using typescript and the uh uh happy types it's um it sort of like provides us with really handy auto completion here so the first thing that we're going to do is we're going to define the type of method and what i'm going to do here is i'm going to define a status endpoint generally in back ends that are oh and we're already getting some errors here we're also getting these errors here but generally when i'm when i'm building uh when building back-ends it's really it's it's nice to have a status endpoint just to know that the server is running correctly especially if you're working with a database you want to just have a simple status endpoint that doesn't even access the database so that you can make sure that it's running and so this is what we're going to do here we're going to define its path as just the root path and we're going to have to provide a handler here and the handler is the function that will get called every time a request comes in and the signature of a uh off a um off a handler is you usually have the request and we can actually see that with the auto completion here so okay and of course it can be async and so we have here this request and the request type i believe it's happy dot request and the uh and then we have also this uh what's it called the response toolkit so uh usually it's just referred to as h and then happy dot toolkit there we go response toolkit and if i hover over this this is essentially the way the setup it's a collection of properties and utilities that basically that will allow us to send a response to the user and so here all i'm going to do is i'm going to do h dot response and here i can pass it an object and that will be actually in the response and i'll just give it up true and set the code to 200. now our server is running and if i make another call what i should get is i should get a 200 instead of the 404 that we saw before and additionally i should see this up object so that's wonderful that worked all great we also have the content type which is set by happy because it sees that this is json object and so we have this status endpoint but you know if we're going to be defining all of these endpoints that i've described before i'm gonna actually try to get that again so um i'll open that up here in just a simple markdown and then we also had this delete delete endpoint um so we're going to have a lot of endpoints and if we start adding all of these endpoints to this start function it'll start looking really really messy and luckily happy has a really nice abstraction and has a really nice solution for this um and it's called plugins so what are plugins plugins are the main mechanism of breaking up the backend into isolated pieces of business logic so they're essentially just a lean way to keep your code modular and what we're going to do is we're going to actually take this status endpoint and move it into a plugin so for that what i'm going to do is i'm going to first create a plugin it looks like i already have it here i'll delete that so that we can recreate it and let's look at how this plugin will look so we are going to first import happy here and then we're going to just define uh a constant here it's going to be a um just a an object and i can already assign it a type so it's plug-in and i'm just you can pass using generics different options to this plugin but we don't need any so i'm just going to pass undefined here and then it's just an object and these plugins they have a bunch of very simple properties it has name and register really these are the main ones that we care about and so here we pass it a name the name is actually optional but as we'll see it's uh it's useful to have these names and then it has this register so the register function is where the magic happens essentially it's a function that will automatically get called by happy when we register this plugin and it passes it this server object and this server the server object is the same one that we're using here that we instantiated here so we don't need to do any dependency ejection any imports we get it passed as we'll see and that's happy server and here we have this async function we can do whatever we like and what we want to do is we want to define the root so i'm just going to go back to server and i'm going to cut this and i'm going to paste that here so that's all great but nowhere do we tell so far happy that we want to register this so in order to register we need to import the to register the plugin we need to import it um so i'll import status plugin from plugins slash status and i think it can just be camel cased and then here we are gonna add a function call called register and register is the function that we use in order to um register these plugins and and you can pass it simply a a single plugin or you can pass it an array i'm going to use an array because we're going to add more plugins later so that's all nice um let's see how we're doing here let's try to run this oh okay it has no default export so indeed i didn't export anything and so i will add this export here and let's see if that works oh and the server is running so now if i go back here and i try to make a request to it great so we've got the response and that means that we've now successfully defined the plugin and we've modularized the status functionality into its own plugin now i'm gonna pause here and look if there's any questions or comments okay i see that we need to increase the font size um also welcome to exception 360. thank you for joining um sorry about the font size i'll try to keep that big um and i think we are ready now to continue great so we've defined this one plugin and throughout our application we're going to want to use prisma so far there's nothing here that makes use of of prisma and because we are going to define these different endpoints these different endpoints are going to make database calls the same way that we did here they're going to use this prisma this instantiated prismacline and so for that we're going to create a plugin for prisma and this plugin will essentially allow us to access prisma from all of our other plugins so let's take a look at how we can do that um okay we have this already defined i'm just going to delete this and i'm going to just make it one tab here so we have this and we can copy this import line and then we can define here the plugin so we are going to call this the prisma plugin but the name doesn't really matter here um so const prisma plugin is of type happy.plugin undefined there's no special properties and it's equal to that and then here we first give it a name so i'm just going to call this prisma and again we have this register function and here we pass it the server parameter which is of happy type sub and what we're going to do here is similar to how we did in the seed script is we're going to instantiate prisma client so created this prisma instance and now that's only accessible here within this scope but we want to make that accessible to the rest of the application and so for that we can use um a feature that happy has called the server app object so this is a a place to store server specific runtime application data without potential conflicts with this framework internals so this is almost like a global object that is accessible wherever server is accessible and as we'll see server is accessible within um all the root handlers um like status so we we here we define the root and we can actually access this server app through the request object so we are going to assign it here so server.app.prisma is prisma as simple as that and then not sure if you remember but uh one of the things that's important when using prisma with nodejs is to ensure that when the server is stopped that the connection um that we disconnect from the database and and for that prisma has this disconnect function so prisma dot disconnect i believe it is yep um let's see oh we didn't import prismaclient that's why we're not getting the types so i'm going to import prisma here so import prisma client from prisma client that's great if i hover over this i see i have the type for it and essentially that's what the disconnect call would look like um but we don't want to just disconnect here we want to add a what we call an extension function in happy's terms in order to make sure that this happens only once the server is stopped so i'll open up just the happy reference here just so you have the an idea of what this is all about extensions or i believe it might be just ext ah let's see there we go so an extension function is really just like a hook function gets called when a certain event happens and what we want to do is we want to add a hook to the on post stop which will be called after the connection listeners are stopped so this is what this will look like so i can go server dot ext and then open up an object here and then pass it a type and then we wanted it to be on post stop we even have all of these references here again thanks to typescript and the types and we're going to pass it a method this can be an async method which has the server passed into it and what we're going to do here is we're going to call server server.app.prisma.disconnect so that was that but we're seeing these squiggly lines and we're seeing these squiggly lines because vs code has really nice typescript integration built in which means that it will actually run the compiler in the background if you're not using vs code or you're not getting these squiggly lines there's also a script which you can call inside the repo and that is called npm run compile and really all it does is it just runs the typescript compiler and if we run this we should see some kind of an error here so what we have is indeed we're getting this error and it's saying the property prisma does not exist on type server application state yeah that's the one so how can we resolve this uh well we can do this using this technique called module augmentation and apparently this is the standard way to handle these kind of things where you're essentially extending a type that is already predefined and we do that with the following block so here what we do is we sort of declare this module which is the happy module and uh we add to the server application state interface this prism we assign it a type and if i run the compile again it should pass hopefully oh and there we go okay good so that's we're almost done here the only thing that we have left is to register this uh plugin so we can do that from the server type script and what we're going to do is we're just going to copy this line and we're going to call it the prisma plug-in and we're going to import that from prisma and then we're going to pass it here and why are we getting oh i forgot the default export again so export default prisma plugin and let's see if that's a paste and indeed it is okay i'm gonna pause here and i'm gonna see if there's any questions or comments oh can't see increase the font um really sorry about this uh is this the font of the editor that you can't see or is it the font of the terminal i'll try to make it as big as i can let's see really sorry about this um hopefully it should be big enough um in case you're having trouble i would also suggest uh try to make sure that you're running 1080p on youtube okay now we're ready to continue so we've defined this status plugin and this prisma plugin and now we're going to start to define the end points for oh okay that's a good question right yeah good time also for the questions so we had a question from hood watcher is there a reason you use happy instead of express for example just a personal preference or because it's better in combination with prisma well happy js provides built-in testing functionality and validation out of the box and it follows a really declarative approach it also has a sort of plug-in architecture which is really useful so there's nothing wrong with express of course you can do all of this with express both of them would work really well with prisma um but just because of these features which are really important validation testing and modularity they're sort of solved for you so you don't really need to think about that but express.js is also great um and most of what we're doing here applies to all servers or node.js servers um that you would use i mean in principle it's all the same so thank you for that question and now let's start by defining to start the let's start by defining some of the user endpoints i think i already have this users plugin here which i'm going to delete just so we can build it from scratch and i'm going to add here users.ts and in uses.ts what i'm going to do is i'm going to also import happy here and i'll try to make this even bigger in case there's problems and i'm gonna define the plugin users plugin type happy dot plugin undefined and that's an object and this object has a name and for the name i think we're just going to use um i like to prefix it with app generally for like the business logic specific ones but really any name could be used here and let's see what's this about oh yeah it's uh let's already export that uses plugin okay and now we need to define a register function as we did before and this register function will have it's an async function which gets the server passed to it and here we could already start by defining some routes but before we do that let's talk about another thing so we're going to import this into our server and i'll open up server so i can already import it here uses plugin from plugins users and we're going to register it that's all good and if we go to users we have a challenge here because essentially we could already start defining different routes here you know with method as we did here um and handler and waypath she uses you know we're gonna probably start with this post in order to create so the method here will be post the path will be just simply users right i can make this full screen um great so we have this uh method and path and then we obviously need a handler and the handler can either be here we can either write it in line or we can um just give it a name and then define that below so create user handler and here we're going to define a function called an async function a sync function let's see create user handler and that accepts a request which is a happy request and this helper which is like the happy it's called the response happy dot response toolkit is that the one yeah great and if you remember from before we could access this server object dot app dot oh and we have prisma so how do we have prisma here well how is this even typed right and if you're joining a bit later i will show you we did that in the prisma plugin we use this technique this typescript technique called module augmentation in order to add the type for prisma and so that's why in our users plugin we actually can it we can actually uh click on that or you know go to definition and it goes here so that's all wonderful but we have a problem right we define these three plugins in our server and the order that they're registered doesn't matter and what we want to ensure is that when the users plug in loads we want to make sure that prisma is already being loaded for us and so for that we can use this feature that plugins have which really this is i think one of the um the killer features of plugins is that we have these dependencies so in these dependencies we can pass here an array and what i'm going to do is i'm going to open up here the prisma plug-in and as you remember the name you might remember the name was prisma so here i can tell hey this is one of its dependencies i really need you to load prisma before you start loading this plugin and so under the hood pretty happy we'll look at these dependencies and then figure out the correct order for loading all of the different plugins and so now we can be sure that this uh prismaclient instance is accessible within our handler okay i forgot a comma here and that's why i'm getting the squiggly line and so all right now we can already um start playing with this handler so we have a post handler and request in order to re in order to access the request payload we can access it through this so what we're going to do is we're going to use this in order to create a user so might remember in order to create a user we're going to use this prisma instance and i'm going to actually i'm going to actually destroy that so i don't have to type the whole thing along the way okay so we have prisma here and then what we're gonna do is we're gonna do prisma and then here we have the different models in this case we want to create a user so we're going to use the create and here we're going to pass it this data object and then in this data object essentially we can pass all of these different fields so email might be um don't pay load so we can go payload dot well first of all it's worth pointing out here that there is no way for the typescript compiler to know what will be the type of payload and the reason is that payload is a runtime object that will come in from the caller to the api and so there is no way for for types typescript to know its type and so there is a strategy that can help us deal with this and that's called a type assertion so a type assertion is the main mechanism that typescript provides in order to override variable variables inferred type so if i sort of hover over this i assume that it will just be there we go it could be just one of these which are either a string object an internal readable or a buffer and these are the types that are generally accepted by happy in terms of user input but we want to verify that it adheres to a certain structure and we'll look at that in a second but first let us take the email from the payload and then we'll also take first name um last name and i think we also had social rights yeah so payload social or we could even do it this way just to be explicit about what we're persisting um but you know what we'll leave it as that for now and okay now let's let's address these type errors that we're getting here i'm going to close this because we no longer need this sign and in order for us to do a type assertion we need to define a type so i'm going to define here a type using an interface and this interface should represent what we expect to come in so i'll call it just user input and here we expect the first name to be of type string a last name and email and then we also have this social object just for reference i'm going to open up the schema here prisma schema so if you remember we had this json field a quick uh refresher on what adjacent field is so json guild is just a free form json field that you can use this maps actually to the json b type in postgres and i can double check that here by opening up the uh cli yeah exactly so we have this json b field and the great thing about these json fields is that there's some data that we don't really care so much if it adheres to a specific schema so for example email and first name and last name these are all part of the uh database schema and so they they sort of you can't just uh change their type randomly but with social we can pass in an object and for example we're going to maybe pass in i think in the seed script we had facebook and twitter and imagine that tick tock suddenly takes off and we want to add it um we can just add that and that will not require any change to the database schema so if it's important data generally the rule of thumb is if it's some really important data that you care about that you want to make sure is also consistent for every row in the table you probably wouldn't store it in a json field but for things like social information that can be sort of dynamic and they can differ from one row to another you can stick to a json b field and so social is obviously not uh it's not a a native data type i believe it's called but rather it's uh it's it's an object so i'm gonna open up an object here and i'm just gonna make this question mark makes this field optional and maybe we'll have twitter and github and website and maybe tick-tock too because why not let's have fun all right i'm going to pause here see if there's any questions we've got 18 viewers live so thank you for everyone who's already joined um and yeah if you've joined late you can re-watch this as well this will also be available later so you can re-watch it from the beginning and there will also be an article that will accompany this um so if you're more of a reader you can sort of follow that at your own pace so coming back to the code we define this type here and what we're going to do is we're going to use this technique called type assertion so that the compiler is relaxed about and it knows that hey the type that we're providing it is correct so the way that you do a type assertion is using this as syntax and you can see that as soon as i added that the squiggly lines went away now it's worth pointing out that this is just to um a piece the typescript compiler this will not actually do any runtime validation in the resulting javascript code there won't be any of this this is just to make sure that the compiler knows that we tell the compiler hey we know what's going on here you can relax um so this doesn't address validation and so we really care about vali and because we really care about validation we're going to handle that next um but let's give this api end point that we just created a go so just to recap everything that we did here we created this user's plugin we looked at dependencies we defined the dependency on prisma and that's this prisma is refers to the name that we gave the prisma plugin we defined here a new root a post root that has a path uses and we've defined a handler and in this handler we're accessing prisma and we're doing this type assertion and we're making this prisma user create now i forgot to add here and a weight and if i'm already going to add a weight i should probably add a try catch block it's always important to handle your errors and so assuming that this succeeds if it didn't throw we would probably use age.response and we might want to take the user id that we created so that we can return that to the user so i'm going to persist the result of this create call in created user and it's now almost seven so we're about an hour in i think we'll go for about an another half hour and hopefully in this next half hour we'll look at validation some of the other end points for users and um some of the testing functionality so we have this created user we don't even need to define a type for it it will automatically infer it because prisma's written is generated for us in typescript excuse me okay so we take this created user and well we've got this nice auto completion here so we're just gonna pass in we're just gonna respond with a object containing the id of the created user and we can do i think 201 is the http code for when you create um an entity i will double check that but let's see and 201 indeed 201 is the status code for creative great and so i'm not going to do any like advanced error handling you probably address that at some point but for now what i'm going to do is i'm just going to console log the error for our debugging purposes and also it's also important to return essentially a a handler function should return either an object or it should return a promise in this case this will return a promise but obviously here there's we could include the error but that might expose some information that we don't necessarily want to expose so i'm just going to add a 500 now it's a bit cumbersome to keep track of all of these codes sometimes and that's where a happy boom comes in so um let's see what we can import from happy boom there we go so we have all these really nice errors here and is that the name though let's double check that happy boom let's see api oh right yeah we can just import boom as an object and then access it by that so here we can just go boom dot bad implementation is an object necessary here oh right yep i think that just needs to be passed into response we can double check that in fact the quickest way to double check that would be to do this in the status endpoint i'm just going to quickly use that as test for so instead of replying with this up i'm going to reply here with boom dot power implementation let's see if it's smart enough to know how to import it but it isn't and let's see if that works i'm not 100 sure about this and we could do that here simply with a simple http request i'm gonna make this a bit bigger so we can all see it hope the size is okay by now and i'm gonna make a call oh oh yeah i think the server isn't running which is let's see okay it's running and now i can make this http call okay that didn't work let's see if in the documentation we have something on how to let's see i think we can just return this and then happy figures that out so let's try this okay that didn't work maybe it needs to be a function oh there we go okay i wonder if that returns also the it isn't and that's probably due to so there we go that will be used in the logs but um so one of the focuses of the reason that we're not actually seeing this in the http response which we made here so when i made this call we just got this sort of generic error and the reason for this is that um happy was built with security in mind and so a lot of the defaults of the framework um are built sort of this idea of security in mind and that sort of plays out here because it doesn't automatically expose any errors which you might be interested in as sort of the developer of the backend but not necessarily exposing that to the user and so i think there is another another property that we can pass here uh like uh i can't remember it right now let me just sort of look up the reference for that um but there is a way to um give it the fail action but we'll look at that later now we just know that we can use this boom functionality in order to um have a nicer we can pass this message um this will be logged for us okay um let's give this a test ride so i've got postman set up here if you're not familiar with postman it's just um you can look at it as a rest client or a general api client i think it supports different apis by now like uh graphql and grpc i think it does too and i'm just going to try to make a post request to this slash user's endpoint and when i do that oh i got in the response i got this id 28 and [Music] the status code was 201 so that's all good and if i go um to the database um i'll just open up pgcli which is the cli that i'm using and i'm just going to select everything from user and as we see indeed 28 so i can select is equal to 28 i think it's just a number and we can make that nice great so that is the object that we created that's all great if i try to make call again we should probably get some kind of like a unique constraint error and that is because we have if we look at the prisma schema again we have this unique so if we try to send to create another user with the same email it'll fail and then we get indeed we get this 500 and let's see what's the error that is logged here so this is already diving into how so there we go we get this error this error is coming from um prisma and it tells us hey the unique constraint failed on the fields email um and then really we just uh you know this is the message that we created in this row um so in here you could imagine that we could do something along the lines of like making sure uh type of error or if error instance of and then the type here whatever the type of the error is and so that we could do more fine-grained error handling but we'll look at that a bit later or perhaps in a in a follow-up stream so okay so we created this user we just saw that the unique constraint successfully passed but what about validation so if i go back to postman here um what if i don't pass the first name and i'm just going to make the email unique um you see it will get an internal server error but the user won't know so if the user sees this if the api consumer which could be a user it could be another developer developing the front and if they see a 500 they don't know what is the reason for why this failed and so this is where validation comes in handy in fact if we should probably we should probably look at the error that we we had here and it's probably telling us error creating user yep so here we have it it was missing the first name i think it was that i removed exactly the data the first name was missing so how can we prevent this so that most of the database calls that we end up making already um are have gone through the validation well happy comes with this library called joy um and we're going to import that excuse me and before we get into this i'm going to pause here and see if there's any interesting comments okay so frasier jeffrey um has mentioned that vs code has extensions which allow you to use http instead of standalone client like postman so thank you for that yeah i think also nico shared with me recently um another nice tool um and uh mark bennett so you're asking if the questions go here yes it's best to ask the questions here okay increase the browser font size let me see where did i have the browser open okay well i'll be sure to do that whenever i open up the browser um don't think we need any of this now how are you able to use http command in the terminal well good question so http is a is simply a cli called httpi really long but um http you can go to that i'll share the link um it's just a cli tool and it's it's it's almost like curl but it has some nice uh a nicer interface to work with and i think it automatically like uh it automatically um parses the json and like makes it it prettifies the json so that was that um okay hello patrick welcome hello alfie and if there's no more questions we will continue so thank you for all the questions uh thanks for recommending insomniac um i'll check that out exception 360. okay let's continue we have about 20 minutes left and in those 20 minutes what i'd like to cover is validation and testing so validation um let's see so when we define a root we have another property here called the options property which is optional and here we can pass all sorts of different things but what we care about is the validate so let's see if we can find that valid date and that's another object and there's different things that you we could validate here if i just open up the autocompletion i have you know i can validate the parameters the parameters are url parameters which we'll look at in a second when we implement the other routes and then we have payload so in this case we care about payload and what we're going to do is we're going to pass this payload a um an object and that object will be a joy object so this is what it will look like so it's joy dot object and essentially what i'm going to be doing here is i'm going to be defining the structure that i expect it to be so again it's a bit cumbersome but these are these validator objects are actually reusable so we can actually move that out and then reuse that in other end points that accept the same payload um there are different strategies when it comes to defining these validator objects but joy does a really nice way of sort of exposing a declarative kind of like chain api so here i can tell it that hey it's a string and i can tell it that hey it's required and the same thing i want to do for last name and same thing for email but it's not a string it's not a simple string it's an email and social is an object that has maybe facebook and it's optional and then we'll add twitter github and website and i think we at one point we added tick tock let's do that i hope that's the correct spelling if not that's probably an indication of how old i am um all right so we have all of these and i believe website is not just a simple string it's an url i wonder if joy has a rule for you are okay there we go uri exactly that's the right one i believe you can double check that i believe that's the correct rfc okay wonderful so we had all of these we added all of these validate options and now i'm going to try to make that same call that i did before using postman that was missing the first name and let's see what kind of error that we get now oh okay so this is already better we get a 400 and if you might remember that the 400 range of status codes in the http standard refers to errors that are related to the requester's input or to the request so this is already nice but what if we wanted to get really specific with our responses well we have a way to do that and that is with the fail action so we're still within this object right and now we're in this validate and here we can pass it a fail a fail action function which has the request h and an error so this is the error that this is the error object that will represent the validation error so when a valid when joy fails to validate the request we will get access to the request into the response helper kit and we can do different things here um but the simplest way is to just throw this error and what we'll see is that when we throw it we actually get a much much more specific error so that was validation and now we have this nice messages so we can add first name and tada 3 201 and this is the id of the user all right so great we have this create user endpoint why don't we clean this up a little bit and move this object into user input validator move that below input validator that's that that's all great and now let's define another route so uh this route will be the get user so that we can see what users we've created and we're going to touch on a new feature that we haven't covered so far called parameters so parameters can be defined as i'm doing now and hand low get user handler and options were already add some of the validation logic here so here we're validating the parameter and although it's not an object per se it needs to be sort of encapsulated within an object this is a an implementation specific data what are we getting here expected comma oh right yeah missing a comma here this hasn't been defined yet um function get user handler and just to save time i'm just going to copy that from here and close that and da-da we've got a get user handler here and we can already pass it prisma obviously we're not going to have the payload but what we're going to have is we're going to have the request params so of course request.params user id i believe it is and that's a string so we're gonna have to parse that into an integer but we will get to that in a second okay so we've defined here these params and then in these params we have a user id and that is a type of joy dot string and we can even give it a pattern and this will be a regex a regex pattern where we use the digits 0 to 9 and can be multiple of those and that's it it ends with that so it's essentially it's a string that consists of numbers um oh i forgot to add this tiller to make sure that it starts with that and that was that now we have about 10 minutes left let's see what we can get in those 10 minutes um okay i'm glad to hear that uh chmod 777 is uh really liking this um and ayush is asking uh does happy also provide a way to handle authentication raw based authorization and yes it does and we will also cover this in the next episodes so um stay tuned there'll be more um now let's go back to what we were doing okay so we had this get endpoint and let's try to implement it quickly so what stack is here okay so danilo barros asked a question can anyone tell me what stack is he using we're using postgres prisma happy.js uh typescript these are the main things that we're using um and also there's an article associated so you can follow you can read that and get more background thank you for joining though okay um now we want to use prisma dot await user user dot find one where and then here i'm going to pass it an id field and that should be the user id but the user id is of type string and we're getting an error because it's expecting a type number so we're going to parse this as an integer and i always like to add the radix um okay so we have that and now we want to return it so we go h dot response um user code 200. i believe the code isn't it will default to 200. um let's see okay great so this user it could either be a user or null if it's not found and so what we can do here is is if no user then we want to return an empty code 404 or we can use return boom what was it boom dot not found news i can't and then otherwise uh return user or best to do that with the response so user dot code 200 and there we have it let's give this a go with postman and so we created this id30 let's try and get it so here slash users and then i'm going to pass it the thing sending request okay it looks like the server might not be running okay we're just gonna sometimes when a the ts no dev doesn't pick up on the types when it keeps on reloading but let's see once the it's running now we can run this oh and there we go we got the full user object here with the response to okay now let's try a non-existent like three a non-existent user id user not found so indeed we got the 404 as we expected it to now let's try to pass in an invalid parameter because you remember we set a validation oh you see we got the 400 invalid request params input and we can use this fail action here to get a more specific error so instead of just getting this we'll actually see why there we go so user id doesn't with the value aaa fails to match the regex pattern be aware that if you are using this uh if you are re-throwing the validation error that you might expose some details like in this case we're exposing the details of what the regex but in terms of security this should still be fine okay so now we did that now let's look a bit at testing so so far we've tested the logic of these i'm just gonna make this a little bit slower but not uh changing any of the code so we had this post user and get user handler um before we go on to do the other endpoints uh let's look at how to how we could do testing so i've set up jest here and i'm using this uh plugin with ts jest in order to be able to run it with typescript so that the tests are written in typescript and i've sort of created a skeleton here for what the tests could look like um i'm just uh going to give this as an example so here i created just a test for prisma and in it i just instantiate the prismacline connect disconnect before and then after and then i just do a test query and then i expect the data to be truthy and i can run this in the terminal i will make this bigger again i'm sorry if it was not legible um and so i could run this test here by npn run test and there we go it's a bit slow okay so we got some errors here um and that passed so which one failed it failed the user's test why did it fail error cannot find create server okay yeah that was let me just don't save so let's open up that user's test so let's look at the status test because that's a really nice simple end point so what we need is we need to be able in order to test in a happy http server we need access to the server and we need access to the server before it's started so i'm going to refactor this a bit so that it matches our use case and so that we can just import this server object before we start it and so what i'm going to do is i'm going to break this start function into two separate functions um and these two separate functions one of them will be called create server and one of them will be called start server so create server will essentially register the plugins and call await server dot initialize initialize will initialize all of these plugins but not actually open up the server for connections and you'll see in a second why that's relevant we need to return the server and then here in this start you know what we can do we can just take we'll rename it start server and we can instead of using the global we can actually pass it in so the server will be happy.server and then await server.start and then return server great now we will remove this and instead of this being the main entry point for the application i'm going to create a new file oh whoops okay so i'm going to create this new index.ts file and save that in here no i don't want it to be marked down okay so we have this index ts and i will also update package.json here to start index ts and then import create server start server from server and then what we're going to do is we're going to create a server i'm not sure how that happened yes create server and then we're going to start server we can just pass it the function because it will resolve with this happy server which will return here and then it will get passed in here so and then catch uh error dot log error okay so that's our entry point to the application and i'm gonna just uh make sure that it works so in vm run dev that should hopefully start the server okay and that worked well great so what can we do now that we have this create server function that already initializes all of that well in our tests we can before all tell server import create server from uh source slash server and we can assign it the value but it's async so we need to wait and then here we have server that's all nice now let's see do we need to do any cleanup i believe we need to call the server stop so here we create and then after all the tests run we can await server dot stop and stop yeah it will stop accepting the connections and a quick reminder that we had in the prisma plug-in on post stop so this will actually disconnect prisma after that and so we don't really care about prisma in this test but that's just a good a good thing to know in terms of some of the happy specific details so now we have this test and what we're going to do is this is where the real magic happens so there's this inject function which allows you to inject a request into the server that we created even though it's not actually listening to any ports and what that allows us to do is it allows us to do integration testing so in this case what we're testing is we're testing how is the http layer working we're not actually doing unit tests for the specific handlers but we're testing really the application from the perspective of the http api that it exposes and that's why it's an integration test because it actually integrates a lot of different things it integrates happy potentially prisma not in this test but it tests the plugin and it tests the the handlers what we could do is we could actually also test validation rules and so that would prevent us from having to every time use postman in order to test requests to the api and so here i'm going to inject and when you inject you can pass it a url in this case we're testing the status endpoint which has the simple it's in the root and we can tell it the method now if i do post because post isn't defined i expect to get so wait first of all const response equals await server.inject and now we have this response object this response object allows us to now look at what came as a result of this api request this simulated request fonts dot so we have the status code and then we expect that to equal 404 in fact i can tell it call either escape that or just use a backtick instead ah okay and so this test should pass and let's run that i can actually pass the watch here which will get passed down and we'll keep oh let's see what failed that was in users test let's see it uses test right so that failed let's just remove that and try to run that again i thought i had a way to watch tests let's see you run npx just watch there we go now it's in watch mode which is nice because i can just in a single button i can rerun the tests and it's much quicker because i think it doesn't it sort of tracks it sort of keeps a cache of the tests transpiled great so now this is what this is really like test driven development we're a bit over time but i'm just going to continue to cover some more of the tests so here i'm going to introduce here a get this endpoint works change that to get and then we expect that to be 200 in fact we could try to parse the response dot payload and then what we would expect payload and dot up to equal true let's see if that works great that worked now let's look a bit at the test that we might want to introduce here so going to similar to how we did that with the status i'm going to import this create server function and here we create the server we stop it and now we want to actually send a request in order to inject the request in order to test the creation of a user and so here we can do a const response equals away server.inject and then pass it an object method is post url we slash users if this is any easier i will open up here the user's plugin so we're going to test essentially this endpoint that we can successfully create and we want to pass it some payload so i'm just going to copy some payload that i have some prepared ahead and that will be our payload so first name and let's see okay now we can expect response dot status code to equal 201 and what we could also do is we could try to parse the response payload which contains the id and this is why i'm using actually this user id here i'm setting it here so that i can use it in other tests and so here what i'm going to do is i'm going to say user id is equal to json.parse response dot payload and then if that's this dot i d um if you remember that was what we returned here yep so it was this id and expect user id to be truthy i think that should be enough now let's see how our tests doing okay in fact we could filter with this p pattern um users and did that work okay well okay maybe another time we'll figure that out filtering for watching okay so that test passed now what if we pass invalid input so within valid input so here say we miss we remove the first name and last name and then we expect it to be i think 400 or 400 i believe it's 400 invalid and let's see if that test passes there we go so that was um testing and what i've done in the git repositories um i've integrated all of this with github actions so this is the github workflow file and the nice thing about github actions is that now you can actually start these service containers so in this case we're actually starting a postgres container we're telling it hey we want this username we want this password and then what we do is we are running the migration here and we're running the test so if you look at the repository on github you will see actually that this will run the tests so first it will run the migrations so that the database is created with the schema based on the prisma schema and then it will run these tests which is really nice because these are integration tests and they will run on every commit so with that in mind let's look again at our list of end points and maybe we'll wrap up with the delete endpoint which is a fairly lean one and let's do that tdd style so for that i'm going to first just write the test and i'm going to do that simply by just copying this and modifying the inject you know what so delete user and for that we're going to use the method delete and here i'm going to actually use this user id that we created so here we create a user and we assign that user id to this variable so we're going to interpolate that into the url using these template letter rules there's no payload necessary okay and then i believe 204 is the code that is used when deleting um i'll also be sharing some links to some resources on rest if you're interested um this is from stack the stack overflow blog let's see if we have any questions before i run along on this okay so we have another question do you recommend happy for a graphql api or would be nexus be the right choice and do you have plans to do streams about graphql with prisma and maybe together with nexus so i haven't actually used happy with graphql so i can't comment on that um nexus is really great and of course there's many other options out there um and as for plans to do streams about graphql which prism i'm sure that we'll have more of those i can't promise anything concretely right now but i'm sure we'll have a lot more live streams um recently we had ryan chenky join our team um and he's very very experienced also with graphql and so we'll hopefully also get a look at that um and so coming back to this um let's see so we wanted to do the implementation for the this delete user and that will be in the user's plugin so it'll look very similar to the get user so i'll just copy that and i'll make that bigger and so here the method will be delete and log and we'll validate in the params and then we just need to define this delete user handler i'll do that here in fact i will copy this function and base it on that to rename it and then here we have the user id prisma and then here we want to delete where id's user id and let's see i believe 204 is empty which is what we want to do in the case of so i don't think it can so user in this case is always a user otherwise it throws an exception if it fails so let's wrap this in a try catch we don't even need the response and then here we can just go return age dot response dot code 204 [Music] and in this case we return boom dot bad implementation fail to delete user and now i just ran prettier so it's nice and pretty and let's see how the tests are doing it's kind of a shame you know what because the whole point was to show the test driven development approach so i'm just going to make this empty and let's see it should fail now there we go great so it failed and if we re-enable this then the test should pass so indeed we wrote the test before you wrote the implementation and that's kind of like the idea behind test driven development so with that i'm going to wrap up the stream but before i do i'm going to uh take some questions in case there's any thank you for the thank you hood watcher i'm glad that uh you enjoyed this and if there's no more questions we'll wrap up on that note and there will be a follow-up article and also updated code um with more of the endpoints implemented all right well on that note i thank you all for joining us today it's really lovely i hope you learned something um a couple of nodes so to summarize what we did in this stream we uh implemented an http server we looked at typescript and using external types with happyjs we looked at happy js plugins we looked at how we can use them to define dependencies we looked at how happy enables testing and how happy integrates with prisma we look at how to define roots and how to validate payload and parameters with joy and we looked at some of the typescript specific details i think like type assertions to a piece the compiler and also the module augmentation when you're sort of overriding types defined by an external uh package um and we also looked at a lot of these uh prisma crud operations uh in the previous stream we looked at uh mostly create um mostly create and aggregate operations and in this one we did a bit of delete we did well fine then we hit create and of course there's a lot more that you can do with the uh prismaclient api if you haven't already check out the prisma documentation and then before we leave i just ask you to click the subscribe button if you haven't already so thank you for joining us and uh have a nice afternoon morning day depending on where you're joining from
Info
Channel: Prisma
Views: 5,047
Rating: 5 out of 5
Keywords: Prisma, ORM, JavaScript, Database, PostgreSQL, Serverless, FaaS, AWS, Lambda, Node.js, TypeScript, TypeSafety, Serverless Framework, Authentication, Authorization, REST, API, Deployment, DevOps, typescript, Validation, Data, Backend, Passwordless authentication, TypeScript ORM, Object Relational Mapper, continuous integration
Id: d9v7umfMNkM
Channel Id: undefined
Length: 107min 36sec (6456 seconds)
Published: Wed Aug 12 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.