Prisma Client 101
ORMs (Object Relational Mappings) get a ton of hate amongst engineers today. We often look at ORMs with disdain writing them off as “leaky abstractions” over our database. But time and time again, engineering teams still use ORMs! Often they start with an ORM and use them with full force until they start hitting performance issues. Once the bottlenecks hit, many teams fork their current library and add more abstractions on top of it to fit their business need! I think this is because most ORMs abstract naive database operations very well, but leave users to step down into native APIs to handle more complex scenarios.
Martin Fowler addressed this “hate” on ORMs in his post here. I do think the solution he proposes makes sense: own the relational model all the way through your application.
With Prisma, we do just that.
We describe our database models with the GraphQL SDL (Schema Definition Language) and then map this data model to a GraphQL API compliant with the Open CRUD specification. These APIs represent the “data access layer” sitting above your database (SQL, Postgres, Mongo) with the responsibility of making efficient queries against your database. Effectively we have created a “GraphQL ORM” for your database.
Let’s see how this plays out.
We define a type User
using the SDL syntax.
Prisma will then generate the GraphQL API below. I’ve omitted the input types just to show off the core APIs exposed.
As you can see for our type User, we have generated a GraphQL Schema describing Query, Mutation, and Subscription operations. Now we can send GraphQL queries to the data access layer to interact with our database!
Now that we have a general idea of how Prisma abstracts our database, let’s revisit ORMs with a new example.
Prisma Client
Prisma Client represents an ambition. How can we access our database with an ORM-like experience without an actual ORM.
Enter Prisma Client.
We’re going to build out a simple REST API with Prisma to show off the experience of using Prisma Client. The architecture should follow the diagram below.
Project Setup
First let’s bootstrap a new project.
$ mkdir prisma-rest-example
$ cd prisma-rest-example
Next let’s install the Prisma CLI globally on your machine via NPM.
$ npm install -g prisma@1.20.1
Let’s put our Prisma artifacts in a prisma
folder.
$ mkdir prisma
We need three things to get a Prisma API started
- datamodel.prisma — Your database model expressed in SDL syntax
- prisma.yml — Configuration of your Prisma server
- docker-compose.yml — Docker compose file describing the Prisma server image and your database image.
Datamodel
$ cd prisma
$ touch datamodel.prisma
Paste the following datamodel into your datamodel.prisma
file.
We’re going to use a simple example. A User with posts and a Post with an author.
prisma.yml
$ touch prisma.yml
Paste the following configuration into the prisma.yml
file.
Let’s break down what is in this file.
endpoint
— the remote url for the Prisma serverdatamodel
— the datamodel.prisma filegenerate
— The generator for our Prisma Client
The generate
field is the interesting one here. When the Prisma CLI runs generate
, it will output a Prisma Client API based on a generator. In our example here we are using the typescript-client
, but you could use a variety of clients like golang
, flow
, and javascript
.
More client generators will be added as engineers from different language communities adopt Prisma Client!
docker-compose.yml
$ cd ..
$ touch docker-compose.yml
Paste the following content into your docker-compose.yml
file. This file is located in the root of your project.
Here we setup two services: the Prisma base image and MySQL.
Running the server
Now that we have our configuration files in place, let’s deploy a Prisma server.
First in the root of your project, start your containers with docker-compose
.
$ docker-compose up -d
This will pull the public images for prisma
and mysql
and run them on our machine via docker.
Next we need to deploy our datamodel
to our server. Execute the commands below.
$ prisma deploy
You can see that the Prisma server has taken our datamodel
and generated the GraphQL API.
Let’s open up the GraphQL Playground to interact with our database.
Here we create a new User in our database using the createUser
API generated from our datamodel
. We can query this User now!
Generating the client
Okay so now that we have a data access layer on top of our database, the next thing we need to do is generate a client we can use in the application tier.
To generate a Prisma Client execute the command below.
$ prisma generate
This will generate a prisma-client
folder in your src/generated
folder.
Let’s setup our REST API and we will revisit the client shortly.
First thing let’s bootstrap our package.json
with yarn
.
$ yarn init -y
Next in the root of our project let’s install some dev-dependencies.
$ yarn add typescript ts-node -D
Let’s setup a tsconfig.json
in the root of our project.
$ touch tsconfig.json
Copy the following content into your tsconfig.json
file.
To use our client we need to install graphql
and the prisma-client-lib
.
$ yarn add prisma-client-lib@1.20.1 graphql -S
$ yarn add @types/graphql -D
Now we need to install a web server, we’ll use Express.js.
$ yarn add express body-parser -S$ yarn add @types/express @types/body-parser -D
Next create an entry point to the server.
$ cd src
$ touch index.ts
Let’s add a start
script to our package.json.
"scripts": {
"start": "ts-node ./src/index.ts"
}
Time to scaffold our endpoints. Copy the following content into your index.ts
Implementing APIs
Creating a User
First we need to import the client from ./src/generated/prisma-client
and then we can implement the create/user
endpoint.
This is shown below.
Here we get the input arguments from the request body, use the Prisma Client much like an ORM and use the createUser
method. We’ll respond 200
and return the newly created User
.
Fetching a User
Next we can implement a query to find a User by a specific ID.
Just like creating a User
, the client has a simple approach to fetching a User
.
Let’s see if everything works. Open up your terminal in the root of this project and type:
$ yarn start
Now we can open up POSTMAN or a similar tool to make a request.
Sweet that worked! Let’s fetch the newly created User
.
Awesome! You can see how this is a nice developer experience for interacting with your database.
Creating a Post
Now we’ll implement creating a post.
This is where things get a bit more interesting! If we remember the Post
type in our datamodel
has an author
field. This is a relation to the User
. So when we create our Post
we use the connect
syntax to “connect” this Post
to a User
.
Publishing a Post
So we just implemented creating “draft” posts. Let’s implement the endpoint that makes the published
field on a Post
true.
Here we use the updatePost
method which has 2 requirements. First a selector for the item using the where
syntax. Last, a data
object describing the change. We find a Post
where the id
matches the id
of our request and change the published
field to true. Sweet.
Fetching a Post
This looks identical to fetching a user.
And we can test it.
But wait! Our Post type specifies an author
field as well. By default queries against a type does not resolve relations. To fetch the author as well, let’s modify our code.
We can access the relation by calling the author
method once we have a post!
Fetching Posts/Draft Posts
I hope as we are going down the list of endpoints, implementing each one is getting easier for you to understand how easy to use Prisma Client is! Let’s finish up these APIs.
Here we just use the posts
method on the client to fetch posts whether published is true or not. We could further get the authors for each post by mapping the posts array to fetch each author
field but we have omitted that here to keep this example simple.
Conclusion
Using the Prisma Client has been a really great experience for building CLIs, GraphQL Servers, REST APIs, Webhooks, you name it. I love that it abstracts the annoyances of data access and gives you a nice API client with a pretty expressive syntax! We used typescript in this example, but you can generate the client in your favorite languages and start integrating it into your stack.
The source code for this blog post is located below.