Video details

Build a Code-First, Type-Safe GraphQL API with Nexus & Prisma

GraphQL
11.12.2020
English

Ryan Chenkie

Ryan is a full-stack JavaScript developer working at Prisma in developer relations for GraphQL. He's a course author, teaching on subjects ranging from the basics of front end development to full-stack web security. He also hosts the Entrepreneurial Coder Podcast.
Developing a real-world full-stack app often involves tedious threading of data across multiple layers of the stack. This is particularly undesirable during prototyping phases where the main goal may be just to demonstrate an idea or design. It’s also risky when going to production since data inconsistencies between the layers can lead to bugs. In this talk, I'll show how a well designed GraphQL API can be one solution to this velocity and type-safety dilemma via Nexus combined with Prisma.

Transcript

- You are going to talk to us about a GraphQL Nexus and all the awesome things that you get when you are building a GraphQL API with it, isn't it? - Absolutely. - Awesome. - And I can dive right in, unless you wanted to chat about some of the lead-up? I can share my screen though if you want, and then sorta we can we can roll it along with a demo, if you like. - Yeah, yeah please. I think we warm up this transition. I think we are ready. I think everyone is ready now. - All right. - Thank you. - So, you tell me if you can see. - Yes. - 'Cause I'm in this... - Okay, yes yes yes. - I guess we didn't really talk about who I am or anything like that. - Oh yeah, sorry sorry sorry. - So I should tell people who I am. - Start with a little intro. - Yeah, no worries. Yeah, so I'm Ryan, I'm a Developer Advocate at Prisma. And I worked on a lot of GraphQL stuff at Prisma in my developer advocacy. We are involved in the GraphQL community at Prisma. And if you've been following along with Prisma for a while, if you're a user, if you are a historic user, you may know some of the story of Prisma, what we're all about and, and how we got to where we are now. You might be familiar with how we used to be called Graphcool back in the day. Graphcool was this product, they gave you a GraphQL API, a hosted GraphQL API. So you describe your schema and you get a database, and a GraphQL API to access it. Pivot later to something that you host yourself. That's also when the name change to Prisma came in. Which is you got a GraphQL API but you hosted your own database. And Prisma gave you all sorts of tools to do that. Pivot again to where we are now. This is what we think is the right abstraction, where we are now. Where we give you all the great stuff you need to access your database, to work with databases. We give you a full database toolkit that is especially powerful if you're working with TypeScript and Node.js. And you can be working in JavaScript, if you want, you can also be working in Go, we have a Go client, but if you're working in TS and Node.js specifically, Prisma's really powerful for database access. So that's what we do now. And we're still involved though with the GraphQL community. And one of the areas that we're involved in is with something called Nexus. Nexus Schema, to be specific, because there was this thing called the Nexus Framework, which has now been sunset. We are focusing on Nexus Schema, which is a library. What's it all about? It's a library for you to build GraphQL APIs in a code first fashion and in a type safe fashion. So what does that mean? Well, if you're building a GraphQL API, and you're maybe coming into it with Apollo, you'll see with Apollo and with other tools as well, but Apollo is going to show you how to build a schema using the schema definition language SDL. So that's, you know, if you have something like a post you have a type called post, you would describe it like this: A post has an ID, it's got a title, which is a string, this kind of thing. This is the schema definition language. You write this and then you go and write some resolvers to tell this schema how it should resolve data. That's one way of doing it. That's probably the most common way of doing things when you're first approaching GraphQL APIs. But Nexus gives you another way to do that. In particular, it gives you a way to do it so that your schema definition and your resolvers are written in the same place. You don't have to write those separately. So that's really nice because you get to work in the same spot. You don't have to flip back and forth between your schema definition language and your resolvers. Instead, it all happens in the same place. So it gives you a lot of benefit, not having to context switch in that way. The other thing that Nexus gives you is type generation. You can generate your schema definition from what you write here with Nexus. This is Nexus code here. You can generate your, and we'll see this in a little bit, you can generate your schema definition and you can also generate types from this. So that you can, if you're working in TypeScript, you can go and strongly type your code base. You'll of course have validation at your API, because you're describing your API in certain ways that where you have certain types for fields, but you also get type generation out of the box so that you can go and apply those types to your code base. So I've got this Nexus powered Apollo server powered GraphQL API going right here. This is the way it kind of works. You need a server, you need something like Apollo server. You could use something else, but you need a server to run your API. And then you feed into it your schema, which you create using Nexus. And once you have that, you can go ahead and run your API. So we've got this first query here. Really simple, "Hello, world" query. So that's at the root of it. You're creating object types, you're creating query types, mutation types. And you're doing this all in a code first kind of way. So to kind of illustrate this a little bit further, we'll create something that is like a blog post API for example. We can have a constant called post, and that's going to be equal to object type which I'm going to pull in here, which comes from Nexus Schema. And object type is going to be the kind of first spot here that we define a specific type for our API. So we can give it a name, and we'll give the name of post. And then we need to give it some definition. And so the way that it works here with Nexus Schema is you're defining your schema and you're resolving with data in the same spot generally. And you are always working with this definition method. And on this definition method, I've got these parameters where we would find these specific types we would want for our fields. So for example, if I want an ID type for my post, I say t.id, and then I give it a name. It's that first argument there. I call it ID in this case. And then I can have a string field that I would call title. And then another string field called body for the post. So there's my post type. Since I've created a new type I need to put it in here to my types array. So query post, it goes into the types array. And now I need to resolve some data outs when a call is made for some posts. So to do that, what can we do? We can go into the query type and in the definition block for this one, I can say t.list.field. This means I want to get a fields type and I want to resolve with a list of those types. I will call this post. And then here, I will say that it's going to return with a certain type, and the type that it's going to return with is post, which I've described up here. So when we want to resolve the data out of this, here's what it looks like. We can just, I don't even need to do that. So I can just immediately return a list of posts. So why don't we do this? Id: 1, title 'First post', body: 'First post body'. Something like that. All right, well, let's see if this works. We'll come over here, I'll give this a refresh so we got our latest schema, and we can go to look for posts. ID, title, and body are on there. And there we go, we've got our data. So what we've done here is we've created like a GraphQL API that's got our schema definition language, our resolvers, all in the same place. We didn't have to flip back and forth between different files. And we've got our API running already. And what we can do here too, is we don't have to just supply types. We've got other configuration things that we can do here. One thing we can do is we can do typegen. So we can generate some types. So typegenConfig is going to take this. If we want to get our schema... Let's actually see what we've got here. I'm on this, the wrong spots. Not getting my autocomplete like I would expect. I'm gonna come back to this. I'm going to move on to the Prisma section, just because we are running short on time. But this is where you can define the autogeneration stuff that you would use to generate your schema definition language, and your TypeScript types. So we're gonna circle back to that in just a little bit. What I want to show you now though, is Prisma. Because we've got some data here that is it's just static data. We want to instead use some dynamic data. We want to use the dynamic database to resolve this data. And Prisma gives you database tools like I've mentioned, but it's not inherently tied to GraphQL in any way. You can use Prisma wherever you use node modules, essentially. It gives you a really nice experience though, getting your database wired up. So what I'll do is show you how you wire up Prisma. You want to install a couple of dependencies. I've got them installed already, but you would do NPM install as a dev dependency the Prisma CLI. So that gives you all the commands you need for running Prisma commands and generating your Prisma client, et cetera. You'd also install the Prisma clients. Prisma client is what gives you access to your database. So I've got these installed already. And so I will call upon them. I'll do npx prisma init, that's going to be the first thing. So npx prisma init. When I initialize here, I get this directory called Prisma. And inside I've got an environment file. So that's the first thing I get. I also get this schema.prisma file which uses the Prisma schema language. So this is our own domain specific language which is how you describe all the tables, essentially, that are to go into your database. Prisma supports various databases. Right now it's PostgreS, MySQL, SQL Lite, MSSQL. There are more on the way. Right now, it's all just relational databases. We've got NoSQL databases on the roadmap, but right now it's just relational databases. So let's say we want to work with SQL Lite here. This is going to be the easiest way to get ourselves wired up quickly. We'll point to something called dev.db, which I want you to exist right here in this Prisma directory. And that's going to go in, in just a second. So right here in this schema file is where we described the model for our database. It's where we describe the tables that we want in here. So the first one that we can do is post. We'll have a model called post. And then I want the first field to be ID. And I'm going to say that this is going to be, there's a few different things we can do here. We can just call it an integer, or we could call it a string and make this into some kind of unique ID. In fact, why don't I do this? I'll use a string as the type here, and I'm going to use a CUID. We'll see this in a second. It's a collision resistant unique ID. So really the way that you think about this is you give the field name and then the type that is associated with that field. So a string here, I want to use a couple of decorators here. I've got the ID decorator. This is to say that this is the primary key for this table. And then I've got the default decorator, which I can tell it here what default value, what kind of default value I want to give it? I will use CUID. You've also got the option to use a UUID. So you can use a universal unique ID, which is longer, but collision resistant works out pretty well. Title is a string, and body is a string as well. All right, so there's our post model. That's all we need. That's all we need to do to get our table wired up. There are a couple ways that we can go about getting our table created. So we've defined the model, but we haven't created the table yet. So brand new as of Prisma 2.10 is we've got this command: prisma db push. And we have to run this with a specific flag. I forget what the flag is... Preview feature, that's what it is. So dash dash preview feature, and the flag is there because it is a preview feature. It's, you know, it's under development. You can use it. This is the command that you would use if you're developing against kind of a test database, and you don't want to go through the like actual migrations that you would otherwise run in production. This is useful if you just want to get going quickly with putting things into your database, essentially. But we do have a migrate command as well. I'll just use this for now. So DB push, preview feature. That's okay. And so once we do that, we've got dev.db right there, and we're good to go. Another command we need to do is npx prisma generate. That's going to come in handy in just a second, when we go to actually use our database. And the last thing I'm going to do here, just so we can see it, is npx prisma studio. Prisma Studio is a graphical user interface that you can use to take a look at your database. So we've got this post table, and we've got our three fields here. ID, title, body. Let's put one in right here. My first post body, we'll just add this right in place here. Of course we can do this. Okay, it didn't take, my first post body. Of course do this, and you typically will do this, through the Prisma client programmatically, when you're creating your API to accept data and save it on a database. But of course you've also got this option of using the GUI to do it. Cool, okay. So we've got one record in there. Now let's replace this resolve here with the data coming from the database. Let's do that. So what we would do is import Prisma client from @prisma/client. And then we can new up a new Prisma client. Const prisma equals a new prisma client. And right away what we can see here if we do prisma. is what we've got available. So the first few things are Prisma specific methods, but what's going to be, I guess, primary interest to you is if you take a look down here we've got this field called post. And then, if we do another dot, we've got all these methods on it. And these are all the database access methods that you might be interested in. Things like finding one or finding many or whatever. All the crud operations that you would want. And the best part about all this is all of this is totally type safe, which means that you are going to be guided down the path of how to actually construct your queries. And it's gonna be very hard for you to actually do a query that's not valid. So I'm going to go into the resolver here and get this kind of wired up. And then I'm going to show you a spot within the node modules that you'll be interested in as well. So prisma.resolve, sorry prisma.post.findMany is what I'm going to call here. I don't get that at the end. Let's see if this works. Let's go back over to here, and there we go. So we're getting our posts out of the database. This is the unique ID that comes from right there. So we're accessing our database just fine. If we take a look in the node modules directory, and we go here to this .prisma directory and into index.D.TS, this is kind of where the magic happens for Prisma. So if we look for type post, for example. I'm in the wrong find. I need to do dmf type post. This is automatically generated from the Prisma, when I did npx prisma generate. That command is responsible for going through our data model and then doing all the TypeScript stuff that it needs to do to give us these types for posts, for example. And where this really comes in handy is like, say that this resolver was responsible for taking in some data and saving it. What we can do is prisma.post.create, for example, and we're guided through how to create this data. So if I did like CTRL + Spacebar here, I see the fields that are available here for me to put in. So body would be, you know, my post, something like that. I'm getting a red squiggly here because I haven't supplied all of the fields that are necessary to make that go away. I would need to provide a title because that's required. And then if I tried to do something, you know totally wacky, like if I thought it had an author key, it's not going to let me do that. So I catch all these bugs before I even run my API. These are caught, I can deal with it at build time. Really really powerful for when you're creating your APIs. So with Nexus Schema comes a whole bunch of other stuff. You can do mutations of course. You know, you could create a mutation, take in some arguments, put them into your database via Prisma, like I've shown there. And yeah, that's going to give you a really powerful set of features for working with databases. And I want to just see if I can resolve this issue that I was having with generating. I'm going to switch this back to findMany before I finish up, because I know we're running a little tight here. Make sure that I can show you what I wanted to show you. Outputs, I think that's what I was looking for. Yes, that was it. So schema, the output for this will be as such. I'm going to put it into __dirname /generated/schema.graphQL. And then for typegen, it's going to be the same thing really, but it's going to be a little different. Typegen will be as such, I will do generated and then it's going to be types like that. You can name it whatever you want, but you know, that will suffice. And already I just saved because I'm running ts-node-dev, it just refreshes for me. And in the generated folder now, I've got my schema definition language. So this was generated via Nexus as well as these types. So I can use these types in various places to make the process of writing my API more type safe, if I want to. The schema definition language is useful here, because if I'm working in like a react application or some other front ends, I can plug this SDL into vs code and then get auto-complete when I'm doing my queries on the front end. So doing this gives you this kind of like end-to-end, or the potential anyway, for this to end-to-end type safe experience when you're building your APIs and your databases. It extends all the way through, it'll flow all the way through to your front end, if you want it to, as well. And again, lots of cool tools from Prisma. We saw Studio, that's a really powerful feature that's going to give you a good leg up when you're building your API. And, you know, Nexus, a lot of people ask, like, what are the benefits of doing this over, you know, writing SDL plus your resolvers. Some of the things that I like would be things like you get, as I mentioned, you do your schema and your resolvers all in one spot, that happens in the same spot. I think that's really cool. I think you have a smaller code volume as well. Like you don't have all the scaffolding that goes into making your schema definition and then your resolvers in two different spots and then importing them and tying them together and all that. You've got essentially a smaller code volume, I think. And then finally like the generated stuff that you get here is really great. So yeah, lots to love about Nexus Schema. I'd encourage you to check it out. You can just go searching for Nexus Schema. You can also go to prisma.io and you'll see the stuff there for what you need to get started. And I think with that, I'm going to stop sharing and that'll be about it.