This blog post is part of a multi-part entry with the goal of creating a Full Stack Application using GraphQL. We will be building a catalog of recipes and have authentication in the form of JSON Web Tokens. This first part will cover the modeling of the data that Prisma uses to build the API.
The finished project will look similar to this one! Let's get started 😎
Getting Started
To speed our development, we will begin by starting from a boilerplate by Andrew Mead. This boilerplate is about 18 months old at this point, so some things have changed since he released it.
mkdir recipe-blog-post
cd recipe-blog-post/
git clone https://github.com/andrewjmead/graphql-boilerplate recipes-api
code .
In the above code we use the terminal to create a folder for the project, change our working directory to that folder, clone the repo & rename it, and then open the project in VSCode.
Creating an Account with Prisma
In order to use the awesome services that Prisma offers, let's create an account with them and then install the global Prisma CLI.
Install the Prisma CLI
npm i -g prisma
Then we can initiate a new Prisma service in the Prisma Cloud by using the following command.
Initiate a Prisma Cloud Service from the Command Line
prisma init prisma
Then select the Demo Server hosted in Prisma Cloud and follow the prompts in the cli interface. For the programming language in the generated Prisma client, I'm choosing Javascript.
This gives us a folder in the root directory called prisma
. Within this folder we have generated files and folders. We will concentrate on datamodel.prisma
for this post. There isn't an extension for syntax highlighting in VSCode for files with the .prisma
extension, so let's change this file extension to .graphql
. The file will now have the name datamodel.graphql
. We can install the GraphQL extension from Prisma in VSCode to get syntax highlighting.
The contents of datamodel.graphql
should look like this:
type User {
id: ID! @id
name: String!
}
We will replace the contents of datamodel.graphql
with this:
type User {
id: ID! @id
name: String!
email: String! @unique
password: String!
updatedAt: DateTime! @updatedAt
createdAt: DateTime! @createdAt
recipes: [Recipe!] @relation(name: "UserRecipes", onDelete: SET_NULL)
}
type File {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
filename: String!
mimetype: String!
encoding: String!
url: String! @unique
}
type Recipe {
id: ID! @id
title: String!
handle: String! @unique
description: String
author: User! @relation(name: "UserRecipes", onDelete: SET_NULL)
image: File @relation(name: "RecipeImage", onDelete: CASCADE)
ingredients: [String!]! @scalarList(strategy: RELATION)
directions: String
categories: [Category!]!
@relation(name: "RecipeCategories", onDelete: SET_NULL)
tags: [Tag!]! @relation(name: "RecipeTags", onDelete: SET_NULL)
}
type Tag {
id: ID! @id
name: String!
recipes: [Recipe] @relation(name: "RecipeTags", onDelete: SET_NULL)
}
type Category {
id: ID! @id
name: String!
recipes: [Recipe] @relation(name: "RecipeCategories", onDelete: SET_NULL)
}
Breaking Down the Data Model
The type
keyword gives us a table in the database representing that entity. There are also several directives such as, @unique
and @id
that give Prisma a little more information about that field or type. More info on directives can be found in Prisma's documentation.
The relational data is annotated with the @relation directive. We have used this directive several times in the above code. This creates a relational table showing the connection between two entities. The relational tables are named according to the name
argument in the @relation
directive.
Close Look at the User Type
Let's dig into the User type to better understand the directives and relations.
type User {
id: ID! @id
name: String!
email: String! @unique
password: String!
updatedAt: DateTime! @updatedAt
createdAt: DateTime! @createdAt
recipes: [Recipe!] @relation(name: "UserRecipes", onDelete: SET_NULL)
}
The !
on each field type means that the field cannot be null when the User
is created.
The id
field will automatically be created by Prisma when a new User is created and using the ID! type along with the @id
directive tells Prisma this will be the case.
The name
field accepts a String!
, and similarly the email
and password
fields accept a String!
. The email
field does a @unique
directive meaning that an email address cannot be used on more than one User
.
The updatedAt
and createdAt
fields both accept a DateTime!
type and these are automatically generated by Prisma using the appropriate directives.
Lastly, we have the recipes
field, which is a realtion to the Recipe
type. The plural name of the field recipes
is intentional as a User
can have more than one Recipe
. This is signified in the type portion of the recipes
field as we have a set of [Recipe!]
. The !
inside the square brackets does have significant meaning here. It sets the API up so that the creation of a User
does not have to link to any recipes (the field can be null
), that is why there is no !
outside the square brackets. Having the !
inside the brackets means that when a mutation occurs where a set of recipes is linked to a user, the type must be Recipe
.
The @relation(name: "UserRecipes", onDelete: SET_NULL)
directive sets up a table named UserRecipes
that connects a User
to a Recipe
on each row. The Recipe
entity uses the same directive to connect the Recipe
to the author
. The rest of the datamodel
file follows the same patterns as the User
type.
Changing the Prisma.yml File
Now that the datamodel is ready to go, we need to make a few changes the prisma.yml
file. The contents will look like this:
endpoint: https://eu1.prisma.sh/recipe-catalog/whatever-your-path-is/dev
datamodel: datamodel.graphql
generate:
- generator: graphql-schema
output: ./generated/prisma-client/prisma.graphql
hooks:
post-deploy:
- prisma generate
Deployment
Once the above changes are saved, we can run the command:
prisma delpoy
This will have Prisma generate the GraphQL schema, including all the resolvers. It also gives us an active GraphQL Playground to explore using the auto-generated resolvers, along with docs.
Summary
While we didn't use the boilerplate mentioned above in this post, it did give us a folder structure to work off of in subsequent entries. If you have any questions or comments, please let me know! My twitter handle is @gregleeper.
Top comments (0)